mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-17 15:36:35 +00:00
Compare commits
84 Commits
v1.25.1241
...
dev/migrie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0a21a91f6 | ||
|
|
3b8e7236ae | ||
|
|
f49c3fca01 | ||
|
|
ae99ce9c36 | ||
|
|
0e7217d354 | ||
|
|
aea37520b3 | ||
|
|
3c1866ac53 | ||
|
|
48b20de4f4 | ||
|
|
9ff2775122 | ||
|
|
b4e0496eff | ||
|
|
ff333870fc | ||
|
|
866832b665 | ||
|
|
56992296bf | ||
|
|
d053f6cc9e | ||
|
|
a751156fcc | ||
|
|
620ee302fd | ||
|
|
abb847b4c7 | ||
|
|
a3ac32ab25 | ||
|
|
5e9d0b8195 | ||
|
|
62fe8235dc | ||
|
|
5ff9a247d4 | ||
|
|
7e2b371dae | ||
|
|
02e9871f2a | ||
|
|
eee657b502 | ||
|
|
a6e044d91c | ||
|
|
f3738f5c1b | ||
|
|
7e2e4eaf49 | ||
|
|
8635537ebc | ||
|
|
de9dc32aa0 | ||
|
|
64d02f2b2b | ||
|
|
94c4cca176 | ||
|
|
b2796019f8 | ||
|
|
b755eb0f20 | ||
|
|
b1b1befeb9 | ||
|
|
507a48ed68 | ||
|
|
5a8e27e60a | ||
|
|
9b3b9e0109 | ||
|
|
7f9f75cab3 | ||
|
|
d0f05f60e9 | ||
|
|
56850639c5 | ||
|
|
a4acdeb5f2 | ||
|
|
56d5f9ee3a | ||
|
|
d944a68ded | ||
|
|
723037ef99 | ||
|
|
54a002762a | ||
|
|
47d55a8fd0 | ||
|
|
edd71265d8 | ||
|
|
6757452d6d | ||
|
|
4e69a32de7 | ||
|
|
da0cc7bae5 | ||
|
|
51e0473560 | ||
|
|
c106f64bc7 | ||
|
|
6be697221d | ||
|
|
d6c2fb593d | ||
|
|
1e3a319314 | ||
|
|
4da965f901 | ||
|
|
4f697eca92 | ||
|
|
1111d41347 | ||
|
|
6265f4f1d7 | ||
|
|
7854abe0a3 | ||
|
|
9a1cf5ac6e | ||
|
|
aef422d5f1 | ||
|
|
8569211d0f | ||
|
|
721b6367a2 | ||
|
|
5197dc4e50 | ||
|
|
880222dc1b | ||
|
|
447c2f9d4f | ||
|
|
b592155d2b | ||
|
|
64898b1caf | ||
|
|
e5dc64e085 | ||
|
|
58c6646132 | ||
|
|
9b32681ae1 | ||
|
|
7f29e1e268 | ||
|
|
736a351c27 | ||
|
|
631cdf7b18 | ||
|
|
7fb7d64b91 | ||
|
|
1ee3522cd8 | ||
|
|
ed1cf2aeac | ||
|
|
c66a56656e | ||
|
|
14d21f492b | ||
|
|
ccbcb425da | ||
|
|
2857324777 | ||
|
|
eb243f5e11 | ||
|
|
42c3eea136 |
15
.github/actions/spelling/README.md
vendored
Normal file
15
.github/actions/spelling/README.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# check-spelling/check-spelling configuration
|
||||
|
||||
File | Purpose | Format | Info
|
||||
-|-|-|-
|
||||
[allow/*.txt](allow/) | Add words to the dictionary | one word per line (only letters and `'`s allowed) | [allow](https://github.com/check-spelling/check-spelling/wiki/Configuration#allow)
|
||||
[reject.txt](reject.txt) | Remove words from the dictionary (after allow) | grep pattern matching whole dictionary words | [reject](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-reject)
|
||||
[excludes.txt](excludes.txt) | Files to ignore entirely | perl regular expression | [excludes](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-excludes)
|
||||
[patterns/*.txt](patterns/) | Patterns to ignore from checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns)
|
||||
[candidate.patterns](candidate.patterns) | Patterns that might be worth adding to [patterns.txt](patterns.txt) | perl regular expression with optional comment block introductions (all matches will be suggested) | [candidates](https://github.com/check-spelling/check-spelling/wiki/Feature:-Suggest-patterns)
|
||||
[line_forbidden.patterns](line_forbidden.patterns) | Patterns to flag in checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns)
|
||||
[expect/*.txt](expect.txt) | Expected words that aren't in the dictionary | one word per line (sorted, alphabetically) | [expect](https://github.com/check-spelling/check-spelling/wiki/Configuration#expect)
|
||||
[advice.md](advice.md) | Supplement for GitHub comment when unrecognized words are found | GitHub Markdown | [advice](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice)
|
||||
|
||||
Note: you can replace any of these files with a directory by the same name (minus the suffix)
|
||||
and then include multiple files inside that directory (with that suffix) to merge multiple files together.
|
||||
38
.github/actions/spelling/advice.md
vendored
38
.github/actions/spelling/advice.md
vendored
@@ -1,4 +1,4 @@
|
||||
<!-- markdownlint-disable MD033 MD041 -->
|
||||
<!-- See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice --> <!-- markdownlint-disable MD033 MD041 -->
|
||||
<details>
|
||||
<summary>
|
||||
:pencil2: Contributor please read this
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
By default the command suggestion will generate a file named based on your commit. That's generally ok as long as you add the file to your commit. Someone can reorganize it later.
|
||||
|
||||
:warning: The command is written for posix shells. You can copy the contents of each `perl` command excluding the outer `'` marks and dropping any `'"`/`"'` quotation mark pairs into a file and then run `perl file.pl` from the root of the repository to run the code. Alternatively, you can manually insert the items...
|
||||
:warning: The command is written for posix shells. If it doesn't work for you, you can manually _add_ (one word per line) / _remove_ items to `expect.txt` and the `excludes.txt` files.
|
||||
|
||||
If the listed items are:
|
||||
|
||||
@@ -20,31 +20,29 @@ 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:
|
||||
|
||||
<details><summary>:clamp: If you see a bunch of garbage</summary>
|
||||
|
||||
If it relates to a ...
|
||||
<details><summary>well-formed pattern</summary>
|
||||
<details><summary>If the flagged items are :exploding_head: false positives</summary>
|
||||
|
||||
See if there's a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it.
|
||||
If items relate to a ...
|
||||
* binary file (or some other file you wouldn't want to check at all).
|
||||
|
||||
If not, try writing one and adding it to a `patterns/{file}.txt`.
|
||||
Please add a file path to the `excludes.txt` file matching the containing file.
|
||||
|
||||
Patterns are Perl 5 Regular Expressions - you can [test](
|
||||
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines.
|
||||
|
||||
Note that patterns can't match multiline strings.
|
||||
</details>
|
||||
<details><summary>binary-ish string</summary>
|
||||
|
||||
Please add a file path to the `excludes.txt` file instead of just accepting the garbage.
|
||||
|
||||
File paths are Perl 5 Regular Expressions - you can [test](
|
||||
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](
|
||||
`^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md](
|
||||
../tree/HEAD/README.md) (on whichever branch you're using).
|
||||
</details>
|
||||
|
||||
|
||||
* well-formed pattern.
|
||||
|
||||
If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it,
|
||||
try adding it to the `patterns.txt` file.
|
||||
|
||||
Patterns are Perl 5 Regular Expressions - you can [test](
|
||||
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines.
|
||||
|
||||
Note that patterns can't match multiline strings.
|
||||
</details>
|
||||
|
||||
</details>
|
||||
|
||||
43
.github/actions/spelling/allow/allow.txt
vendored
43
.github/actions/spelling/allow/allow.txt
vendored
@@ -1,52 +1,75 @@
|
||||
admins
|
||||
allcolors
|
||||
Apc
|
||||
apc
|
||||
breadcrumb
|
||||
breadcrumbs
|
||||
bsd
|
||||
calt
|
||||
ccmp
|
||||
changelog
|
||||
cybersecurity
|
||||
Apc
|
||||
clickable
|
||||
clig
|
||||
CMMI
|
||||
copyable
|
||||
cybersecurity
|
||||
dalet
|
||||
dcs
|
||||
Dcs
|
||||
dcs
|
||||
dialytika
|
||||
dje
|
||||
downside
|
||||
downsides
|
||||
dze
|
||||
dzhe
|
||||
EDDB
|
||||
EDDC
|
||||
Enum'd
|
||||
Fitt
|
||||
formattings
|
||||
FTCS
|
||||
ftp
|
||||
fvar
|
||||
gantt
|
||||
gcc
|
||||
geeksforgeeks
|
||||
ghe
|
||||
github
|
||||
gje
|
||||
godbolt
|
||||
hostname
|
||||
hostnames
|
||||
https
|
||||
hyperlink
|
||||
hyperlinking
|
||||
hyperlinks
|
||||
iconify
|
||||
img
|
||||
inlined
|
||||
It'd
|
||||
kje
|
||||
libfuzzer
|
||||
libuv
|
||||
liga
|
||||
lje
|
||||
locl
|
||||
lorem
|
||||
Llast
|
||||
llvm
|
||||
Lmid
|
||||
locl
|
||||
lol
|
||||
lorem
|
||||
Lorigin
|
||||
maxed
|
||||
minimalistic
|
||||
mkmk
|
||||
mnt
|
||||
mru
|
||||
noreply
|
||||
nje
|
||||
noreply
|
||||
ogonek
|
||||
ok'd
|
||||
overlined
|
||||
pipeline
|
||||
postmodern
|
||||
ptys
|
||||
qof
|
||||
@@ -61,17 +84,25 @@ runtimes
|
||||
shcha
|
||||
slnt
|
||||
Sos
|
||||
ssh
|
||||
timeline
|
||||
timelines
|
||||
timestamped
|
||||
TLDR
|
||||
tokenizes
|
||||
tonos
|
||||
toolset
|
||||
tshe
|
||||
ubuntu
|
||||
uiatextrange
|
||||
UIs
|
||||
und
|
||||
unregister
|
||||
versioned
|
||||
vsdevcmd
|
||||
We'd
|
||||
wildcards
|
||||
XBox
|
||||
YBox
|
||||
yeru
|
||||
zhe
|
||||
|
||||
60
.github/actions/spelling/allow/apis.txt
vendored
60
.github/actions/spelling/allow/apis.txt
vendored
@@ -1,30 +1,43 @@
|
||||
ACCEPTFILES
|
||||
ACCESSDENIED
|
||||
acl
|
||||
aclapi
|
||||
alignas
|
||||
alignof
|
||||
APPLYTOSUBMENUS
|
||||
appxrecipe
|
||||
bitfield
|
||||
bitfields
|
||||
BUILDBRANCH
|
||||
BUILDMSG
|
||||
BUILDNUMBER
|
||||
BYCOMMAND
|
||||
BYPOSITION
|
||||
charconv
|
||||
CLASSNOTAVAILABLE
|
||||
CLOSEAPP
|
||||
cmdletbinding
|
||||
COLORPROPERTY
|
||||
colspan
|
||||
COMDLG
|
||||
commandlinetoargv
|
||||
comparand
|
||||
cstdint
|
||||
CXICON
|
||||
CYICON
|
||||
Dacl
|
||||
dataobject
|
||||
dcomp
|
||||
DERR
|
||||
dlldata
|
||||
DNE
|
||||
DONTADDTORECENT
|
||||
DWMSBT
|
||||
DWMWA
|
||||
DWMWA
|
||||
DWORDLONG
|
||||
endfor
|
||||
ENDSESSION
|
||||
enumset
|
||||
environstrings
|
||||
EXPCMDFLAGS
|
||||
@@ -38,12 +51,16 @@ fullkbd
|
||||
futex
|
||||
GETDESKWALLPAPER
|
||||
GETHIGHCONTRAST
|
||||
GETMOUSEHOVERTIME
|
||||
Hashtable
|
||||
HIGHCONTRASTON
|
||||
HIGHCONTRASTW
|
||||
hotkeys
|
||||
href
|
||||
hrgn
|
||||
HTCLOSE
|
||||
hwinsta
|
||||
HWINSTA
|
||||
IActivation
|
||||
IApp
|
||||
IAppearance
|
||||
@@ -60,19 +77,22 @@ IDirect
|
||||
IExplorer
|
||||
IFACEMETHOD
|
||||
IFile
|
||||
IGraphics
|
||||
IInheritable
|
||||
IMap
|
||||
IMonarch
|
||||
IObject
|
||||
iosfwd
|
||||
IPackage
|
||||
IPeasant
|
||||
isspace
|
||||
ISetup
|
||||
isspace
|
||||
IStorage
|
||||
istream
|
||||
IStringable
|
||||
ITab
|
||||
ITaskbar
|
||||
itow
|
||||
IUri
|
||||
IVirtual
|
||||
KEYSELECT
|
||||
@@ -81,17 +101,27 @@ llabs
|
||||
llu
|
||||
localtime
|
||||
lround
|
||||
Lsa
|
||||
lsass
|
||||
LSHIFT
|
||||
LTGRAY
|
||||
MAINWINDOW
|
||||
memchr
|
||||
memicmp
|
||||
MENUCOMMAND
|
||||
MENUDATA
|
||||
MENUINFO
|
||||
memicmp
|
||||
mptt
|
||||
MENUITEMINFOW
|
||||
mmeapi
|
||||
MOUSELEAVE
|
||||
mov
|
||||
mptt
|
||||
msappx
|
||||
MULTIPLEUSE
|
||||
NCHITTEST
|
||||
NCLBUTTONDBLCLK
|
||||
NCMOUSELEAVE
|
||||
NCMOUSEMOVE
|
||||
NCRBUTTONDBLCLK
|
||||
NIF
|
||||
NIN
|
||||
@@ -109,26 +139,36 @@ oaidl
|
||||
ocidl
|
||||
ODR
|
||||
offsetof
|
||||
ofstream
|
||||
onefuzz
|
||||
osver
|
||||
OSVERSIONINFOEXW
|
||||
otms
|
||||
OUTLINETEXTMETRICW
|
||||
overridable
|
||||
PACL
|
||||
PAGESCROLL
|
||||
PATINVERT
|
||||
PEXPLICIT
|
||||
PICKFOLDERS
|
||||
pmr
|
||||
ptstr
|
||||
QUERYENDSESSION
|
||||
rcx
|
||||
REGCLS
|
||||
RETURNCMD
|
||||
rfind
|
||||
ROOTOWNER
|
||||
roundf
|
||||
RSHIFT
|
||||
SACL
|
||||
schandle
|
||||
semver
|
||||
serializer
|
||||
SETVERSION
|
||||
SHELLEXECUTEINFOW
|
||||
shobjidl
|
||||
SHOWHIDE
|
||||
SHOWMINIMIZED
|
||||
SHOWTIP
|
||||
SINGLEUSE
|
||||
@@ -149,23 +189,37 @@ Stubless
|
||||
Subheader
|
||||
Subpage
|
||||
syscall
|
||||
SYSTEMBACKDROP
|
||||
TABROW
|
||||
TASKBARCREATED
|
||||
TBPF
|
||||
THEMECHANGED
|
||||
tlg
|
||||
TME
|
||||
tmp
|
||||
tmpdir
|
||||
tolower
|
||||
toupper
|
||||
TRACKMOUSEEVENT
|
||||
TTask
|
||||
TVal
|
||||
UChar
|
||||
UFIELD
|
||||
ULARGE
|
||||
UOI
|
||||
UPDATEINIFILE
|
||||
userenv
|
||||
USEROBJECTFLAGS
|
||||
Viewbox
|
||||
virtualalloc
|
||||
wcsstr
|
||||
wcstoui
|
||||
winmain
|
||||
winsta
|
||||
winstamin
|
||||
wmemcmp
|
||||
wpc
|
||||
WSF
|
||||
wsregex
|
||||
wwinmain
|
||||
xchg
|
||||
|
||||
8
.github/actions/spelling/allow/math.txt
vendored
8
.github/actions/spelling/allow/math.txt
vendored
@@ -1,3 +1,11 @@
|
||||
atan
|
||||
CPrime
|
||||
HBar
|
||||
HPrime
|
||||
isnan
|
||||
LPrime
|
||||
LStep
|
||||
powf
|
||||
RSub
|
||||
sqrtf
|
||||
ULP
|
||||
|
||||
10
.github/actions/spelling/allow/microsoft.txt
vendored
10
.github/actions/spelling/allow/microsoft.txt
vendored
@@ -1,5 +1,6 @@
|
||||
ACLs
|
||||
ADMINS
|
||||
advapi
|
||||
altform
|
||||
altforms
|
||||
appendwttlogging
|
||||
@@ -15,8 +16,10 @@ CPLs
|
||||
cpptools
|
||||
cppvsdbg
|
||||
CPRs
|
||||
cryptbase
|
||||
DACL
|
||||
DACLs
|
||||
defaultlib
|
||||
diffs
|
||||
disposables
|
||||
dotnetfeed
|
||||
@@ -25,6 +28,8 @@ DWINRT
|
||||
enablewttlogging
|
||||
Intelli
|
||||
IVisual
|
||||
libucrt
|
||||
libucrtd
|
||||
LKG
|
||||
LOCKFILE
|
||||
Lxss
|
||||
@@ -34,8 +39,10 @@ microsoftonline
|
||||
MSAA
|
||||
msixbundle
|
||||
MSVC
|
||||
MSVCP
|
||||
muxc
|
||||
netcore
|
||||
Onefuzz
|
||||
osgvsowi
|
||||
PFILETIME
|
||||
pgc
|
||||
@@ -46,6 +53,7 @@ powershell
|
||||
propkey
|
||||
pscustomobject
|
||||
QWORD
|
||||
regedit
|
||||
robocopy
|
||||
SACLs
|
||||
sdkddkver
|
||||
@@ -59,6 +67,8 @@ systemroot
|
||||
taskkill
|
||||
tasklist
|
||||
tdbuildteamid
|
||||
ucrt
|
||||
ucrtd
|
||||
unvirtualized
|
||||
VCRT
|
||||
vcruntime
|
||||
|
||||
11
.github/actions/spelling/allow/names.txt
vendored
11
.github/actions/spelling/allow/names.txt
vendored
@@ -1,14 +1,18 @@
|
||||
Anup
|
||||
austdi
|
||||
arkthur
|
||||
Ballmer
|
||||
bhoj
|
||||
Bhojwani
|
||||
Bluloco
|
||||
carlos
|
||||
dhowett
|
||||
Diviness
|
||||
dsafa
|
||||
duhowett
|
||||
DXP
|
||||
ekg
|
||||
eryksun
|
||||
ethanschoonover
|
||||
Firefox
|
||||
Gatta
|
||||
@@ -20,6 +24,7 @@ Hernan
|
||||
Howett
|
||||
Illhardt
|
||||
iquilezles
|
||||
italo
|
||||
jantari
|
||||
jerrysh
|
||||
Kaiyu
|
||||
@@ -33,7 +38,9 @@ leonmsft
|
||||
Lepilleur
|
||||
lhecker
|
||||
lukesampson
|
||||
Macbook
|
||||
Manandhar
|
||||
masserano
|
||||
mbadolato
|
||||
Mehrain
|
||||
menger
|
||||
@@ -62,12 +69,16 @@ Rincewind
|
||||
rprichard
|
||||
Schoonover
|
||||
shadertoy
|
||||
Shomnipotence
|
||||
simioni
|
||||
Somuah
|
||||
sonph
|
||||
sonpham
|
||||
stakx
|
||||
talo
|
||||
thereses
|
||||
Walisch
|
||||
WDX
|
||||
Wellons
|
||||
Wirt
|
||||
Wojciech
|
||||
|
||||
523
.github/actions/spelling/candidate.patterns
vendored
Normal file
523
.github/actions/spelling/candidate.patterns
vendored
Normal file
@@ -0,0 +1,523 @@
|
||||
# marker to ignore all code on line
|
||||
^.*/\* #no-spell-check-line \*/.*$
|
||||
# marker for ignoring a comment to the end of the line
|
||||
// #no-spell-check.*$
|
||||
|
||||
# patch hunk comments
|
||||
^\@\@ -\d+(?:,\d+|) \+\d+(?:,\d+|) \@\@ .*
|
||||
# git index header
|
||||
index [0-9a-z]{7,40}\.\.[0-9a-z]{7,40}
|
||||
|
||||
# cid urls
|
||||
(['"])cid:.*?\g{-1}
|
||||
|
||||
# data url in parens
|
||||
\(data:[^)]*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})[^)]*\)
|
||||
# data url in quotes
|
||||
([`'"])data:.*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1}
|
||||
# data url
|
||||
data:[-a-zA-Z=;:/0-9+]*,\S*
|
||||
|
||||
# mailto urls
|
||||
mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,}
|
||||
|
||||
# magnet urls
|
||||
magnet:[?=:\w]+
|
||||
|
||||
# magnet urls
|
||||
"magnet:[^"]+"
|
||||
|
||||
# obs:
|
||||
"obs:[^"]*"
|
||||
|
||||
# The `\b` here means a break, it's the fancy way to handle urls, but it makes things harder to read
|
||||
# In this examples content, I'm using a number of different ways to match things to show various approaches
|
||||
# asciinema
|
||||
\basciinema\.org/a/[0-9a-zA-Z]+
|
||||
|
||||
# apple
|
||||
\bdeveloper\.apple\.com/[-\w?=/]+
|
||||
# Apple music
|
||||
\bembed\.music\.apple\.com/fr/playlist/usr-share/[-\w.]+
|
||||
|
||||
# appveyor api
|
||||
\bci\.appveyor\.com/api/projects/status/[0-9a-z]+
|
||||
# appveyor project
|
||||
\bci\.appveyor\.com/project/(?:[^/\s"]*/){2}builds?/\d+/job/[0-9a-z]+
|
||||
|
||||
# Amazon
|
||||
|
||||
# Amazon
|
||||
\bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|)
|
||||
# AWS S3
|
||||
\b\w*\.s3[^.]*\.amazonaws\.com/[-\w/&#%_?:=]*
|
||||
# AWS execute-api
|
||||
\b[0-9a-z]{10}\.execute-api\.[-0-9a-z]+\.amazonaws\.com\b
|
||||
# AWS ELB
|
||||
\b\w+\.[-0-9a-z]+\.elb\.amazonaws\.com\b
|
||||
# AWS SNS
|
||||
\bsns\.[-0-9a-z]+.amazonaws\.com/[-\w/&#%_?:=]*
|
||||
# AWS VPC
|
||||
vpc-\w+
|
||||
|
||||
# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there
|
||||
# YouTube url
|
||||
\b(?:(?:www\.|)youtube\.com|youtu.be)/(?:channel/|embed/|user/|playlist\?list=|watch\?v=|v/|)[-a-zA-Z0-9?&=_%]*
|
||||
# YouTube music
|
||||
\bmusic\.youtube\.com/youtubei/v1/browse(?:[?&]\w+=[-a-zA-Z0-9?&=_]*)
|
||||
# YouTube tag
|
||||
<\s*youtube\s+id=['"][-a-zA-Z0-9?_]*['"]
|
||||
# YouTube image
|
||||
\bimg\.youtube\.com/vi/[-a-zA-Z0-9?&=_]*
|
||||
# Google Accounts
|
||||
\baccounts.google.com/[-_/?=.:;+%&0-9a-zA-Z]*
|
||||
# Google Analytics
|
||||
\bgoogle-analytics\.com/collect.[-0-9a-zA-Z?%=&_.~]*
|
||||
# Google APIs
|
||||
\bgoogleapis\.(?:com|dev)/[a-z]+/(?:v\d+/|)[a-z]+/[-@:./?=\w+|&]+
|
||||
# Google Storage
|
||||
\b[-a-zA-Z0-9.]*\bstorage\d*\.googleapis\.com(?:/\S*|)
|
||||
# Google Calendar
|
||||
\bcalendar\.google\.com/calendar(?:/u/\d+|)/embed\?src=[@./?=\w&%]+
|
||||
\w+\@group\.calendar\.google\.com\b
|
||||
# Google DataStudio
|
||||
\bdatastudio\.google\.com/(?:(?:c/|)u/\d+/|)(?:embed/|)(?:open|reporting|datasources|s)/[-0-9a-zA-Z]+(?:/page/[-0-9a-zA-Z]+|)
|
||||
# The leading `/` here is as opposed to the `\b` above
|
||||
# ... a short way to match `https://` or `http://` since most urls have one of those prefixes
|
||||
# Google Docs
|
||||
/docs\.google\.com/[a-z]+/(?:ccc\?key=\w+|(?:u/\d+|d/(?:e/|)[0-9a-zA-Z_-]+/)?(?:edit\?[-\w=#.]*|/\?[\w=&]*|))
|
||||
# Google Drive
|
||||
\bdrive\.google\.com/(?:file/d/|open)[-0-9a-zA-Z_?=]*
|
||||
# Google Groups
|
||||
\bgroups\.google\.com/(?:(?:forum/#!|d/)(?:msg|topics?|searchin)|a)/[^/\s"]+/[-a-zA-Z0-9$]+(?:/[-a-zA-Z0-9]+)*
|
||||
# Google Maps
|
||||
\bmaps\.google\.com/maps\?[\w&;=]*
|
||||
# Google themes
|
||||
themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+.
|
||||
# Google CDN
|
||||
\bclients2\.google(?:usercontent|)\.com[-0-9a-zA-Z/.]*
|
||||
# Goo.gl
|
||||
/goo\.gl/[a-zA-Z0-9]+
|
||||
# Google Chrome Store
|
||||
\bchrome\.google\.com/webstore/detail/[-\w]*(?:/\w*|)
|
||||
# Google Books
|
||||
\bgoogle\.(?:\w{2,4})/books(?:/\w+)*\?[-\w\d=&#.]*
|
||||
# Google Fonts
|
||||
\bfonts\.(?:googleapis|gstatic)\.com/[-/?=:;+&0-9a-zA-Z]*
|
||||
# Google Forms
|
||||
\bforms\.gle/\w+
|
||||
# Google Scholar
|
||||
\bscholar\.google\.com/citations\?user=[A-Za-z0-9_]+
|
||||
# Google Colab Research Drive
|
||||
\bcolab\.research\.google\.com/drive/[-0-9a-zA-Z_?=]*
|
||||
|
||||
# GitHub SHAs (api)
|
||||
\bapi.github\.com/repos(?:/[^/\s"]+){3}/[0-9a-f]+\b
|
||||
# GitHub SHAs (markdown)
|
||||
(?:\[`?[0-9a-f]+`?\]\(https:/|)/(?:www\.|)github\.com(?:/[^/\s"]+){2,}(?:/[^/\s")]+)(?:[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b|)
|
||||
# GitHub SHAs
|
||||
\bgithub\.com(?:/[^/\s"]+){2}[@#][0-9a-f]+\b
|
||||
# GitHub wiki
|
||||
\bgithub\.com/(?:[^/]+/){2}wiki/(?:(?:[^/]+/|)_history|[^/]+(?:/_compare|)/[0-9a-f.]{40,})\b
|
||||
# githubusercontent
|
||||
/[-a-z0-9]+\.githubusercontent\.com/[-a-zA-Z0-9?&=_\/.]*
|
||||
# githubassets
|
||||
\bgithubassets.com/[0-9a-f]+(?:[-/\w.]+)
|
||||
# gist github
|
||||
\bgist\.github\.com/[^/\s"]+/[0-9a-f]+
|
||||
# git.io
|
||||
\bgit\.io/[0-9a-zA-Z]+
|
||||
# GitHub JSON
|
||||
"node_id": "[-a-zA-Z=;:/0-9+]*"
|
||||
# Contributor
|
||||
\[[^\]]+\]\(https://github\.com/[^/\s"]+\)
|
||||
# GHSA
|
||||
GHSA(?:-[0-9a-z]{4}){3}
|
||||
|
||||
# GitLab commit
|
||||
\bgitlab\.[^/\s"]*/\S+/\S+/commit/[0-9a-f]{7,16}#[0-9a-f]{40}\b
|
||||
# GitLab merge requests
|
||||
\bgitlab\.[^/\s"]*/\S+/\S+/-/merge_requests/\d+/diffs#[0-9a-f]{40}\b
|
||||
# GitLab uploads
|
||||
\bgitlab\.[^/\s"]*/uploads/[-a-zA-Z=;:/0-9+]*
|
||||
# GitLab commits
|
||||
\bgitlab\.[^/\s"]*/(?:[^/\s"]+/){2}commits?/[0-9a-f]+\b
|
||||
|
||||
# binanace
|
||||
accounts.binance.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]*
|
||||
|
||||
# bitbucket diff
|
||||
\bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}diff(?:stat|)(?:/[^/\s"]+){2}:[0-9a-f]+
|
||||
# bitbucket repositories commits
|
||||
\bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}commits?/[0-9a-f]+
|
||||
# bitbucket commits
|
||||
\bbitbucket\.org/(?:[^/\s"]+/){2}commits?/[0-9a-f]+
|
||||
|
||||
# bit.ly
|
||||
\bbit\.ly/\w+
|
||||
|
||||
# bitrise
|
||||
\bapp\.bitrise\.io/app/[0-9a-f]*/[\w.?=&]*
|
||||
|
||||
# bootstrapcdn.com
|
||||
\bbootstrapcdn\.com/[-./\w]+
|
||||
|
||||
# cdn.cloudflare.com
|
||||
\bcdnjs\.cloudflare\.com/[./\w]+
|
||||
|
||||
# circleci
|
||||
\bcircleci\.com/gh(?:/[^/\s"]+){1,5}.[a-z]+\?[-0-9a-zA-Z=&]+
|
||||
|
||||
# gitter
|
||||
\bgitter\.im(?:/[^/\s"]+){2}\?at=[0-9a-f]+
|
||||
|
||||
# gravatar
|
||||
\bgravatar\.com/avatar/[0-9a-f]+
|
||||
|
||||
# ibm
|
||||
[a-z.]*ibm\.com/[-_#=:%!?~.\\/\d\w]*
|
||||
|
||||
# imgur
|
||||
\bimgur\.com/[^.]+
|
||||
|
||||
# Internet Archive
|
||||
\barchive\.org/web/\d+/(?:[-\w.?,'/\\+&%$#_:]*)
|
||||
|
||||
# discord
|
||||
/discord(?:app\.com|\.gg)/(?:invite/)?[a-zA-Z0-9]{7,}
|
||||
|
||||
# Disqus
|
||||
\bdisqus\.com/[-\w/%.()!?&=_]*
|
||||
|
||||
# medium link
|
||||
\blink\.medium\.com/[a-zA-Z0-9]+
|
||||
# medium
|
||||
\bmedium\.com/\@?[^/\s"]+/[-\w]+
|
||||
|
||||
# microsoft
|
||||
\b(?:https?://|)(?:(?:download\.visualstudio|docs|msdn2?|research)\.microsoft|blogs\.msdn)\.com/[-_a-zA-Z0-9()=./%]*
|
||||
# powerbi
|
||||
\bapp\.powerbi\.com/reportEmbed/[^"' ]*
|
||||
# vs devops
|
||||
\bvisualstudio.com(?::443|)/[-\w/?=%&.]*
|
||||
# microsoft store
|
||||
\bmicrosoft\.com/store/apps/\w+
|
||||
|
||||
# mvnrepository.com
|
||||
\bmvnrepository\.com/[-0-9a-z./]+
|
||||
|
||||
# now.sh
|
||||
/[0-9a-z-.]+\.now\.sh\b
|
||||
|
||||
# oracle
|
||||
\bdocs\.oracle\.com/[-0-9a-zA-Z./_?#&=]*
|
||||
|
||||
# chromatic.com
|
||||
/\S+.chromatic.com\S*[")]
|
||||
|
||||
# codacy
|
||||
\bapi\.codacy\.com/project/badge/Grade/[0-9a-f]+
|
||||
|
||||
# compai
|
||||
\bcompai\.pub/v1/png/[0-9a-f]+
|
||||
|
||||
# mailgun api
|
||||
\.api\.mailgun\.net/v3/domains/[0-9a-z]+\.mailgun.org/messages/[0-9a-zA-Z=@]*
|
||||
# mailgun
|
||||
\b[0-9a-z]+.mailgun.org
|
||||
|
||||
# /message-id/
|
||||
/message-id/[-\w@./%]+
|
||||
|
||||
# Reddit
|
||||
\breddit\.com/r/[/\w_]*
|
||||
|
||||
# requestb.in
|
||||
\brequestb\.in/[0-9a-z]+
|
||||
|
||||
# sched
|
||||
\b[a-z0-9]+\.sched\.com\b
|
||||
|
||||
# Slack url
|
||||
slack://[a-zA-Z0-9?&=]+
|
||||
# Slack
|
||||
\bslack\.com/[-0-9a-zA-Z/_~?&=.]*
|
||||
# Slack edge
|
||||
\bslack-edge\.com/[-a-zA-Z0-9?&=%./]+
|
||||
# Slack images
|
||||
\bslack-imgs\.com/[-a-zA-Z0-9?&=%.]+
|
||||
|
||||
# shields.io
|
||||
\bshields\.io/[-\w/%?=&.:+;,]*
|
||||
|
||||
# stackexchange -- https://stackexchange.com/feeds/sites
|
||||
\b(?:askubuntu|serverfault|stack(?:exchange|overflow)|superuser).com/(?:questions/\w+/[-\w]+|a/)
|
||||
|
||||
# Sentry
|
||||
[0-9a-f]{32}\@o\d+\.ingest\.sentry\.io\b
|
||||
|
||||
# Twitter markdown
|
||||
\[\@[^[/\]:]*?\]\(https://twitter.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|)\)
|
||||
# Twitter hashtag
|
||||
\btwitter\.com/hashtag/[\w?_=&]*
|
||||
# Twitter status
|
||||
\btwitter\.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|)
|
||||
# Twitter profile images
|
||||
\btwimg\.com/profile_images/[_\w./]*
|
||||
# Twitter media
|
||||
\btwimg\.com/media/[-_\w./?=]*
|
||||
# Twitter link shortened
|
||||
\bt\.co/\w+
|
||||
|
||||
# facebook
|
||||
\bfburl\.com/[0-9a-z_]+
|
||||
# facebook CDN
|
||||
\bfbcdn\.net/[\w/.,]*
|
||||
# facebook watch
|
||||
\bfb\.watch/[0-9A-Za-z]+
|
||||
|
||||
# dropbox
|
||||
\bdropbox\.com/sh?/[^/\s"]+/[-0-9A-Za-z_.%?=&;]+
|
||||
|
||||
# ipfs protocol
|
||||
ipfs://[0-9a-z]*
|
||||
# ipfs url
|
||||
/ipfs/[0-9a-z]*
|
||||
|
||||
# w3
|
||||
\bw3\.org/[-0-9a-zA-Z/#.]+
|
||||
|
||||
# loom
|
||||
\bloom\.com/embed/[0-9a-f]+
|
||||
|
||||
# regex101
|
||||
\bregex101\.com/r/[^/\s"]+/\d+
|
||||
|
||||
# figma
|
||||
\bfigma\.com/file(?:/[0-9a-zA-Z]+/)+
|
||||
|
||||
# freecodecamp.org
|
||||
\bfreecodecamp\.org/[-\w/.]+
|
||||
|
||||
# image.tmdb.org
|
||||
\bimage\.tmdb\.org/[/\w.]+
|
||||
|
||||
# mermaid
|
||||
\bmermaid\.ink/img/[-\w]+|\bmermaid-js\.github\.io/mermaid-live-editor/#/edit/[-\w]+
|
||||
|
||||
# Wikipedia
|
||||
\ben\.wikipedia\.org/wiki/[-\w%.#]+
|
||||
|
||||
# gitweb
|
||||
[^"\s]+/gitweb/\S+;h=[0-9a-f]+
|
||||
|
||||
# HyperKitty lists
|
||||
/archives/list/[^@/]+\@[^/\s"]*/message/[^/\s"]*/
|
||||
|
||||
# lists
|
||||
/thread\.html/[^"\s]+
|
||||
|
||||
# list-management
|
||||
\blist-manage\.com/subscribe(?:[?&](?:u|id)=[0-9a-f]+)+
|
||||
|
||||
# kubectl.kubernetes.io/last-applied-configuration
|
||||
"kubectl.kubernetes.io/last-applied-configuration": ".*"
|
||||
|
||||
# pgp
|
||||
\bgnupg\.net/pks/lookup[?&=0-9a-zA-Z]*
|
||||
|
||||
# Spotify
|
||||
\bopen\.spotify\.com/embed/playlist/\w+
|
||||
|
||||
# Mastodon
|
||||
\bmastodon\.[-a-z.]*/(?:media/|\@)[?&=0-9a-zA-Z_]*
|
||||
|
||||
# scastie
|
||||
\bscastie\.scala-lang\.org/[^/]+/\w+
|
||||
|
||||
# images.unsplash.com
|
||||
\bimages\.unsplash\.com/(?:(?:flagged|reserve)/|)[-\w./%?=%&.;]+
|
||||
|
||||
# pastebin
|
||||
\bpastebin\.com/[\w/]+
|
||||
|
||||
# heroku
|
||||
\b\w+\.heroku\.com/source/archive/\w+
|
||||
|
||||
# quip
|
||||
\b\w+\.quip\.com/\w+(?:(?:#|/issues/)\w+)?
|
||||
|
||||
# badgen.net
|
||||
\bbadgen\.net/badge/[^")\]'\s]+
|
||||
|
||||
# statuspage.io
|
||||
\w+\.statuspage\.io\b
|
||||
|
||||
# media.giphy.com
|
||||
\bmedia\.giphy\.com/media/[^/]+/[\w.?&=]+
|
||||
|
||||
# tinyurl
|
||||
\btinyurl\.com/\w+
|
||||
|
||||
# getopts
|
||||
\bgetopts\s+(?:"[^"]+"|'[^']+')
|
||||
|
||||
# ANSI color codes
|
||||
(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m
|
||||
|
||||
# URL escaped characters
|
||||
\%[0-9A-F][A-F]
|
||||
# IPv6
|
||||
\b(?:[0-9a-fA-F]{0,4}:){3,7}[0-9a-fA-F]{0,4}\b
|
||||
# c99 hex digits (not the full format, just one I've seen)
|
||||
0x[0-9a-fA-F](?:\.[0-9a-fA-F]*|)[pP]
|
||||
# Punycode
|
||||
\bxn--[-0-9a-z]+
|
||||
# sha
|
||||
sha\d+:[0-9]*[a-f]{3,}[0-9a-f]*
|
||||
# sha-... -- uses a fancy capture
|
||||
(['"]|")[0-9a-f]{40,}\g{-1}
|
||||
# hex runs
|
||||
\b[0-9a-fA-F]{16,}\b
|
||||
# hex in url queries
|
||||
=[0-9a-fA-F]*?(?:[A-F]{3,}|[a-f]{3,})[0-9a-fA-F]*?&
|
||||
# ssh
|
||||
(?:ssh-\S+|-nistp256) [-a-zA-Z=;:/0-9+]{12,}
|
||||
|
||||
# PGP
|
||||
\b(?:[0-9A-F]{4} ){9}[0-9A-F]{4}\b
|
||||
# GPG keys
|
||||
\b(?:[0-9A-F]{4} ){5}(?: [0-9A-F]{4}){5}\b
|
||||
# Well known gpg keys
|
||||
.well-known/openpgpkey/[\w./]+
|
||||
|
||||
# uuid:
|
||||
\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b
|
||||
# hex digits including css/html color classes:
|
||||
(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|u\d+)\b
|
||||
# integrity
|
||||
integrity="sha\d+-[-a-zA-Z=;:/0-9+]{40,}"
|
||||
|
||||
# https://www.gnu.org/software/groff/manual/groff.html
|
||||
# man troff content
|
||||
\\f[BCIPR]
|
||||
# '
|
||||
\\\(aq
|
||||
|
||||
# .desktop mime types
|
||||
^MimeTypes?=.*$
|
||||
# .desktop localized entries
|
||||
^[A-Z][a-z]+\[[a-z]+\]=.*$
|
||||
# Localized .desktop content
|
||||
Name\[[^\]]+\]=.*
|
||||
|
||||
# IServiceProvider
|
||||
\bI(?=(?:[A-Z][a-z]{2,})+\b)
|
||||
|
||||
# crypt
|
||||
"\$2[ayb]\$.{56}"
|
||||
|
||||
# scrypt / argon
|
||||
\$(?:scrypt|argon\d+[di]*)\$\S+
|
||||
|
||||
# Input to GitHub JSON
|
||||
content: "[-a-zA-Z=;:/0-9+]*="
|
||||
|
||||
# Python stringprefix / binaryprefix
|
||||
# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings
|
||||
(?<!')\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)'(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})
|
||||
|
||||
# Regular expressions for (P|p)assword
|
||||
\([A-Z]\|[a-z]\)[a-z]+
|
||||
|
||||
# JavaScript regular expressions
|
||||
# javascript test regex
|
||||
/.*/[gim]*\.test\(
|
||||
# javascript match regex
|
||||
\.match\(/[^/\s"]*/[gim]*\s*
|
||||
# javascript match regex
|
||||
\.match\(/\\[b].*?/[gim]*\s*\)(?:;|$)
|
||||
# javascript regex
|
||||
^\s*/\\[b].*/[gim]*\s*(?:\)(?:;|$)|,$)
|
||||
# javascript replace regex
|
||||
\.replace\(/[^/\s"]*/[gim]*\s*,
|
||||
|
||||
# Go regular expressions
|
||||
regexp?\.MustCompile\(`[^`]*`\)
|
||||
|
||||
# sed regular expressions
|
||||
sed 's/(?:[^/]*?[a-zA-Z]{3,}[^/]*?/){2}
|
||||
|
||||
# go install
|
||||
go install(?:\s+[a-z]+\.[-@\w/.]+)+
|
||||
|
||||
# kubernetes pod status lists
|
||||
# https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase
|
||||
\w+(?:-\w+)+\s+\d+/\d+\s+(?:Running|Pending|Succeeded|Failed|Unknown)\s+
|
||||
|
||||
# kubectl - pods in CrashLoopBackOff
|
||||
\w+-[0-9a-f]+-\w+\s+\d+/\d+\s+CrashLoopBackOff\s+
|
||||
|
||||
# kubernetes object suffix
|
||||
-[0-9a-f]{10}-\w{5}\s
|
||||
|
||||
# posthog secrets
|
||||
posthog\.init\((['"])phc_[^"',]+\g{-1},
|
||||
|
||||
# xcode
|
||||
|
||||
# xcodeproject scenes
|
||||
(?:Controller|ID|id)="\w{3}-\w{2}-\w{3}"
|
||||
|
||||
# xcode api botches
|
||||
customObjectInstantitationMethod
|
||||
|
||||
# font awesome classes
|
||||
\.fa-[-a-z0-9]+
|
||||
|
||||
# Update Lorem based on your content (requires `ge` and `w` from https://github.com/jsoref/spelling; and `review` from https://github.com/check-spelling/check-spelling/wiki/Looking-for-items-locally )
|
||||
# grep '^[^#].*lorem' .github/actions/spelling/patterns.txt|perl -pne 's/.*i..\?://;s/\).*//' |tr '|' "\n"|sort -f |xargs -n1 ge|perl -pne 's/^[^:]*://'|sort -u|w|sed -e 's/ .*//'|w|review -
|
||||
# Warning, while `(?i)` is very neat and fancy, if you have some binary files that aren't proper unicode, you might run into:
|
||||
## Operation "substitution (s///)" returns its argument for non-Unicode code point 0x1C19AE (the code point will vary).
|
||||
## You could manually change `(?i)X...` to use `[Xx]...`
|
||||
## or you could add the files to your `excludes` file (a version after 0.0.19 should identify the file path)
|
||||
# Lorem
|
||||
(?:\w|\s|[,.])*\b(?i)(?:amet|consectetur|cursus|dolor|eros|ipsum|lacus|libero|ligula|lorem|magna|neque|nulla|suscipit|tempus)\b(?:\w|\s|[,.])*
|
||||
|
||||
# Non-English
|
||||
[a-zA-Z]*[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*
|
||||
|
||||
# French
|
||||
# This corpus only had capital letters, but you probably want lowercase ones as well.
|
||||
\b[LN]'+[a-z]{2,}\b
|
||||
|
||||
# latex
|
||||
\\(?:n(?:ew|ormal|osub)|r(?:enew)|t(?:able(?:of|)|he|itle))(?=[a-z]+)
|
||||
|
||||
# the negative lookahead here is to allow catching 'templatesz' as a misspelling
|
||||
# but to otherwise recognize a Windows path with \templates\foo.template or similar:
|
||||
\\(?:necessary|r(?:eport|esolve[dr]?|esult)|t(?:arget|emplates?))(?![a-z])
|
||||
# ignore long runs of a single character:
|
||||
\b([A-Za-z])\g{-1}{3,}\b
|
||||
# Note that the next example is no longer necessary if you are using
|
||||
# to match a string starting with a `#`, use a character-class:
|
||||
[#]backwards
|
||||
# version suffix <word>v#
|
||||
(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))
|
||||
# Compiler flags (Scala)
|
||||
(?:^|[\t ,>"'`=(])-J-[DPWXY](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
|
||||
# Compiler flags
|
||||
#(?:^|[\t ,"'`=(])-[DPWXYLlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
|
||||
|
||||
# Compiler flags (linker)
|
||||
,-B
|
||||
# curl arguments
|
||||
\b(?:\\n|)curl(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)*
|
||||
# set arguments
|
||||
\bset(?:\s+-[abefimouxE]{1,2})*\s+-[abefimouxE]{3,}(?:\s+-[abefimouxE]+)*
|
||||
# tar arguments
|
||||
\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
|
||||
# tput arguments -- https://man7.org/linux/man-pages/man5/terminfo.5.html -- technically they can be more than 5 chars long...
|
||||
\btput\s+(?:(?:-[SV]|-T\s*\w+)\s+)*\w{3,5}\b
|
||||
# macOS temp folders
|
||||
/var/folders/\w\w/[+\w]+/(?:T|-Caches-)/
|
||||
48
.github/actions/spelling/excludes.txt
vendored
48
.github/actions/spelling/excludes.txt
vendored
@@ -1,28 +1,39 @@
|
||||
# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-excludes
|
||||
(?:(?i)\.png$)
|
||||
(?:^|/)(?i)COPYRIGHT
|
||||
(?:^|/)(?i)LICEN[CS]E
|
||||
(?:^|/)3rdparty/
|
||||
(?:^|/)dirs$
|
||||
(?:^|/)go\.mod$
|
||||
(?:^|/)go\.sum$
|
||||
(?:^|/)package-lock\.json$
|
||||
(?:^|/)package(?:-lock|)\.json$
|
||||
(?:^|/)sources(?:|\.dep)$
|
||||
SUMS$
|
||||
(?:^|/)vendor/
|
||||
\.a$
|
||||
\.ai$
|
||||
\.avi$
|
||||
\.bmp$
|
||||
\.bz2$
|
||||
\.cer$
|
||||
\.class$
|
||||
\.crl$
|
||||
\.crt$
|
||||
\.csr$
|
||||
\.dll$
|
||||
\.docx?$
|
||||
\.drawio$
|
||||
\.DS_Store$
|
||||
\.eot$
|
||||
\.eps$
|
||||
\.exe$
|
||||
\.gif$
|
||||
\.gitattributes$
|
||||
\.graffle$
|
||||
\.gz$
|
||||
\.icns$
|
||||
\.ico$
|
||||
\.jar$
|
||||
\.jks$
|
||||
\.jpeg$
|
||||
\.jpg$
|
||||
\.key$
|
||||
@@ -30,28 +41,53 @@ SUMS$
|
||||
\.lock$
|
||||
\.map$
|
||||
\.min\..
|
||||
\.mod$
|
||||
\.mp3$
|
||||
\.mp4$
|
||||
\.o$
|
||||
\.ocf$
|
||||
\.otf$
|
||||
\.pbxproj$
|
||||
\.pdf$
|
||||
\.pem$
|
||||
\.png$
|
||||
\.psd$
|
||||
\.pyc$
|
||||
\.runsettings$
|
||||
\.s$
|
||||
\.sig$
|
||||
\.so$
|
||||
\.svg$
|
||||
\.svgz$
|
||||
\.svgz?$
|
||||
\.tar$
|
||||
\.tgz$
|
||||
\.tiff?$
|
||||
\.ttf$
|
||||
\.vsdx$
|
||||
\.wav$
|
||||
\.webm$
|
||||
\.webp$
|
||||
\.woff
|
||||
\.woff2?$
|
||||
\.xcf$
|
||||
\.xls
|
||||
\.xlsx?$
|
||||
\.xpm$
|
||||
\.yml$
|
||||
\.zip$
|
||||
^\.github/actions/spelling/
|
||||
^\.github/fabricbot.json$
|
||||
^\.gitignore$
|
||||
^\Q.git-blame-ignore-revs\E$
|
||||
^\Q.github/workflows/spelling.yml\E$
|
||||
^\Qdoc/reference/windows-terminal-logo.ans\E$
|
||||
^\Qsamples/ConPTY/EchoCon/EchoCon/EchoCon.vcxproj.filters\E$
|
||||
^\Qsrc/host/exe/Host.EXE.vcxproj.filters\E$
|
||||
^\Qsrc/host/ft_host/chafa.txt\E$
|
||||
^\Qsrc/tools/closetest/CloseTest.vcxproj.filters\E$
|
||||
^\XamlStyler.json$
|
||||
^build/config/
|
||||
^consolegit2gitfilters\.json$
|
||||
^dep/
|
||||
^doc/reference/master-sequence-list.csv$
|
||||
@@ -61,12 +97,14 @@ SUMS$
|
||||
^src/host/runft\.bat$
|
||||
^src/host/runut\.bat$
|
||||
^src/interactivity/onecore/BgfxEngine\.
|
||||
^src/renderer/atlas/
|
||||
^src/renderer/wddmcon/WddmConRenderer\.
|
||||
^src/terminal/adapter/ut_adapter/run\.bat$
|
||||
^src/terminal/parser/delfuzzpayload\.bat$
|
||||
^src/terminal/parser/ft_fuzzer/run\.bat$
|
||||
^src/terminal/parser/ft_fuzzer/VTCommandFuzzer\.cpp$
|
||||
^src/terminal/parser/ft_fuzzwrapper/run\.bat$
|
||||
^src/terminal/parser/ut_parser/Base64Test.cpp$
|
||||
^src/terminal/parser/ut_parser/run\.bat$
|
||||
^src/tools/integrity/packageuwp/ConsoleUWP\.appxSources$
|
||||
^src/tools/lnkd/lnkd\.bat$
|
||||
@@ -74,6 +112,6 @@ SUMS$
|
||||
^src/tools/texttests/fira\.txt$
|
||||
^src/tools/U8U16Test/(?:fr|ru|zh)\.txt$
|
||||
^src/types/ut_types/UtilsTests.cpp$
|
||||
^\.github/actions/spelling/
|
||||
^\.gitignore$
|
||||
^\XamlStyler.json$
|
||||
^tools/ReleaseEngineering/ServicingPipeline.ps1$
|
||||
ignore$
|
||||
SUMS$
|
||||
|
||||
8
.github/actions/spelling/expect/alphabet.txt
vendored
8
.github/actions/spelling/expect/alphabet.txt
vendored
@@ -5,26 +5,19 @@ AAAAAABBBBBBCCC
|
||||
AAAAABBBBBBCCC
|
||||
abcd
|
||||
abcd
|
||||
abcde
|
||||
abcdef
|
||||
ABCDEFG
|
||||
ABCDEFGH
|
||||
ABCDEFGHIJ
|
||||
abcdefghijk
|
||||
ABCDEFGHIJKLMNO
|
||||
abcdefghijklmnop
|
||||
ABCDEFGHIJKLMNOPQRST
|
||||
abcdefghijklmnopqrstuvwxyz
|
||||
ABCG
|
||||
ABE
|
||||
abf
|
||||
BBBBB
|
||||
BBBBBBBB
|
||||
BBBBBBBBBBBBBBDDDD
|
||||
BBBBBCCC
|
||||
BBBBCCCCC
|
||||
BBGGRR
|
||||
CCE
|
||||
EFG
|
||||
EFGh
|
||||
QQQQQQQQQQABCDEFGHIJ
|
||||
@@ -33,7 +26,6 @@ QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ
|
||||
QQQQQQQQQQABCDEFGHIJPQRSTQQQQQQQQQQ
|
||||
qrstuvwxyz
|
||||
qwerty
|
||||
QWERTYUIOP
|
||||
qwertyuiopasdfg
|
||||
YYYYYYYDDDDDDDDDDD
|
||||
ZAAZZ
|
||||
|
||||
918
.github/actions/spelling/expect/expect.txt
vendored
918
.github/actions/spelling/expect/expect.txt
vendored
File diff suppressed because it is too large
Load Diff
11
.github/actions/spelling/expect/web.txt
vendored
11
.github/actions/spelling/expect/web.txt
vendored
@@ -1,17 +1,6 @@
|
||||
http
|
||||
www
|
||||
ecma
|
||||
rapidtables
|
||||
WCAG
|
||||
freedesktop
|
||||
ycombinator
|
||||
robertelder
|
||||
kovidgoyal
|
||||
leonerd
|
||||
fixterms
|
||||
winui
|
||||
appshellintegration
|
||||
mdtauk
|
||||
cppreference
|
||||
gfycat
|
||||
Guake
|
||||
|
||||
62
.github/actions/spelling/line_forbidden.patterns
vendored
Normal file
62
.github/actions/spelling/line_forbidden.patterns
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
# reject `m_data` as there's a certain OS which has evil defines that break things if it's used elsewhere
|
||||
# \bm_data\b
|
||||
|
||||
# If you have a framework that uses `it()` for testing and `fit()` for debugging a specific test,
|
||||
# you might not want to check in code where you were debugging w/ `fit()`, in which case, you might want
|
||||
# to use this:
|
||||
#\bfit\(
|
||||
|
||||
# s.b. GitHub
|
||||
\bGithub\b
|
||||
|
||||
# s.b. GitLab
|
||||
\bGitlab\b
|
||||
|
||||
# s.b. JavaScript
|
||||
\bJavascript\b
|
||||
|
||||
# s.b. Microsoft
|
||||
\bMicroSoft\b
|
||||
|
||||
# s.b. another
|
||||
\ban[- ]other\b
|
||||
|
||||
# s.b. greater than
|
||||
\bgreater then\b
|
||||
|
||||
# s.b. into
|
||||
#\sin to\s
|
||||
|
||||
# s.b. opt-in
|
||||
\sopt in\s
|
||||
|
||||
# s.b. less than
|
||||
\bless then\b
|
||||
|
||||
# s.b. otherwise
|
||||
\bother[- ]wise\b
|
||||
|
||||
# s.b. nonexistent
|
||||
\bnon existing\b
|
||||
\b[Nn]o[nt][- ]existent\b
|
||||
|
||||
# s.b. preexisting
|
||||
[Pp]re[- ]existing
|
||||
|
||||
# s.b. preempt
|
||||
[Pp]re[- ]empt\b
|
||||
|
||||
# s.b. preemptively
|
||||
[Pp]re[- ]emptively
|
||||
|
||||
# s.b. reentrancy
|
||||
[Rr]e[- ]entrancy
|
||||
|
||||
# s.b. reentrant
|
||||
[Rr]e[- ]entrant
|
||||
|
||||
# s.b. workaround(s)
|
||||
#\bwork[- ]arounds?\b
|
||||
|
||||
# Reject duplicate words
|
||||
\s([A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})\s\g{-1}\s
|
||||
85
.github/actions/spelling/patterns/patterns.txt
vendored
85
.github/actions/spelling/patterns/patterns.txt
vendored
@@ -1,11 +1,6 @@
|
||||
https://(?:(?:[-a-zA-Z0-9?&=]*\.|)microsoft\.com)/[-a-zA-Z0-9?&=_#\/.]*
|
||||
https://aka\.ms/[-a-zA-Z0-9?&=\/_]*
|
||||
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-]+\.|)github(?:usercontent|)\.com/[-a-zA-Z0-9?%&=_\/.]*
|
||||
https://www.xfree86.org/[-a-zA-Z0-9?&=\/_#]*
|
||||
# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns
|
||||
|
||||
https?://\S+
|
||||
[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)
|
||||
(?:0[Xx]|\\x|U\+|#)[a-f0-9A-FGgRr]{2,}[Uu]?[Ll]{0,2}\b
|
||||
@@ -25,3 +20,77 @@ std::memory_order_[\w]+
|
||||
D2DERR_SHADER_COMPILE_FAILED
|
||||
TIL_FEATURE_[0-9A-Z_]+
|
||||
vcvars\w*
|
||||
ROY\sG\.\sBIV
|
||||
!(?:(?i)ESC)!\[
|
||||
!(?:(?i)CSI)!(?:\d+(?:;\d+|)m|[ABCDF])
|
||||
|
||||
# Python stringprefix / binaryprefix
|
||||
\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)'
|
||||
|
||||
# Automatically suggested patterns
|
||||
# hit-count: 3831 file-count: 582
|
||||
# IServiceProvider
|
||||
\bI(?=(?:[A-Z][a-z]{2,})+\b)
|
||||
|
||||
# hit-count: 71 file-count: 35
|
||||
# Compiler flags
|
||||
(?:^|[\t ,"'`=(])-[D](?=[A-Z]{2,}|[A-Z][a-z])
|
||||
(?:^|[\t ,"'`=(])-[X](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
|
||||
|
||||
# hit-count: 41 file-count: 28
|
||||
# version suffix <word>v#
|
||||
(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))
|
||||
|
||||
# hit-count: 20 file-count: 9
|
||||
# hex runs
|
||||
\b[0-9a-fA-F]{16,}\b
|
||||
|
||||
# hit-count: 10 file-count: 7
|
||||
# uuid:
|
||||
\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b
|
||||
|
||||
# hit-count: 4 file-count: 4
|
||||
# mailto urls
|
||||
mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,}
|
||||
|
||||
# hit-count: 4 file-count: 1
|
||||
# ANSI color codes
|
||||
(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m
|
||||
|
||||
# hit-count: 2 file-count: 1
|
||||
# latex
|
||||
\\(?:n(?:ew|ormal|osub)|r(?:enew)|t(?:able(?:of|)|he|itle))(?=[a-z]+)
|
||||
|
||||
# hit-count: 1 file-count: 1
|
||||
# hex digits including css/html color classes:
|
||||
(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|u\d+)\b
|
||||
|
||||
# hit-count: 1 file-count: 1
|
||||
# Non-English
|
||||
[a-zA-Z]*[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*
|
||||
|
||||
# hit-count: 1 file-count: 1
|
||||
# French
|
||||
# This corpus only had capital letters, but you probably want lowercase ones as well.
|
||||
\b[LN]'+[a-z]{2,}\b
|
||||
|
||||
# acceptable duplicates
|
||||
# ls directory listings
|
||||
[-bcdlpsw](?:[-r][-w][-sx]){3}\s+\d+\s+(\S+)\s+\g{-1}\s+\d+\s+
|
||||
# C/idl types + English ...
|
||||
\s(Guid|long|LONG|that) \g{-1}\s
|
||||
|
||||
# javadoc / .net
|
||||
(?:[\\@](?:groupname|param)|(?:public|private)(?:\s+static|\s+readonly)*)\s+(\w+)\s+\g{-1}\s
|
||||
|
||||
# Commit message -- Signed-off-by and friends
|
||||
^\s*(?:(?:Based-on-patch|Co-authored|Helped|Mentored|Reported|Reviewed|Signed-off)-by|Thanks-to): (?:[^<]*<[^>]*>|[^<]*)\s*$
|
||||
|
||||
# Autogenerated revert commit message
|
||||
^This reverts commit [0-9a-f]{40}\.$
|
||||
|
||||
# vtmode
|
||||
--vtmode\s+(\w+)\s+\g{-1}\s
|
||||
|
||||
# ignore long runs of a single character:
|
||||
\b([A-Za-z])\g{-1}{3,}\b
|
||||
|
||||
28
.github/actions/spelling/reject.txt
vendored
28
.github/actions/spelling/reject.txt
vendored
@@ -1,22 +1,12 @@
|
||||
^attache$
|
||||
^attacher$
|
||||
^attachers$
|
||||
^spae$
|
||||
^spaebook$
|
||||
^spaecraft$
|
||||
^spaed$
|
||||
^spaedom$
|
||||
^spaeing$
|
||||
^spaeings$
|
||||
^spae-man$
|
||||
^spaeman$
|
||||
^spaer$
|
||||
^Spaerobee$
|
||||
^spaes$
|
||||
^spaewife$
|
||||
^spaewoman$
|
||||
^spaework$
|
||||
^spaewright$
|
||||
^wether$
|
||||
^wethers$
|
||||
^wetherteg$
|
||||
benefitting
|
||||
occurences?
|
||||
^dependan.*
|
||||
^oer$
|
||||
Sorce
|
||||
^[Ss]pae.*
|
||||
^untill$
|
||||
^untilling$
|
||||
^wether.*
|
||||
|
||||
132
.github/workflows/spelling2.yml
vendored
132
.github/workflows/spelling2.yml
vendored
@@ -1,20 +1,134 @@
|
||||
# spelling.yml is blocked per https://github.com/check-spelling/check-spelling/security/advisories/GHSA-g86g-chm8-7r2p
|
||||
name: Spell checking
|
||||
|
||||
# Comment management is handled through a secondary job, for details see:
|
||||
# https://github.com/check-spelling/check-spelling/wiki/Feature%3A-Restricted-Permissions
|
||||
#
|
||||
# `jobs.comment-push` runs when a push is made to a repository and the `jobs.spelling` job needs to make a comment
|
||||
# (in odd cases, it might actually run just to collapse a commment, but that's fairly rare)
|
||||
# it needs `contents: write` in order to add a comment.
|
||||
#
|
||||
# `jobs.comment-pr` runs when a pull_request is made to a repository and the `jobs.spelling` job needs to make a comment
|
||||
# or collapse a comment (in the case where it had previously made a comment and now no longer needs to show a comment)
|
||||
# it needs `pull-requests: write` in order to manipulate those comments.
|
||||
|
||||
# Updating pull request branches is managed via comment handling.
|
||||
# For details, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Update-expect-list
|
||||
#
|
||||
# These elements work together to make it happen:
|
||||
#
|
||||
# `on.issue_comment`
|
||||
# This event listens to comments by users asking to update the metadata.
|
||||
#
|
||||
# `jobs.update`
|
||||
# This job runs in response to an issue_comment and will push a new commit
|
||||
# to update the spelling metadata.
|
||||
#
|
||||
# `with.experimental_apply_changes_via_bot`
|
||||
# Tells the action to support and generate messages that enable it
|
||||
# to make a commit to update the spelling metadata.
|
||||
#
|
||||
# `with.ssh_key`
|
||||
# In order to trigger workflows when the commit is made, you can provide a
|
||||
# secret (typically, a write-enabled github deploy key).
|
||||
#
|
||||
# For background, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Update-with-deploy-key
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
tags-ignore:
|
||||
- "**"
|
||||
pull_request_target:
|
||||
branches:
|
||||
- "**"
|
||||
tags-ignore:
|
||||
- "**"
|
||||
types:
|
||||
- 'opened'
|
||||
- 'reopened'
|
||||
- 'synchronize'
|
||||
issue_comment:
|
||||
types:
|
||||
- 'created'
|
||||
|
||||
jobs:
|
||||
spelling:
|
||||
name: Spell checking
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
actions: read
|
||||
outputs:
|
||||
followup: ${{ steps.spelling.outputs.followup }}
|
||||
runs-on: ubuntu-latest
|
||||
if: "contains(github.event_name, 'pull_request') || github.event_name == 'push'"
|
||||
concurrency:
|
||||
group: spelling-${{ github.event.pull_request.number || github.ref }}
|
||||
# note: If you use only_check_changed_files, you do not want cancel-in-progress
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: checkout-merge
|
||||
if: "contains(github.event_name, 'pull_request')"
|
||||
uses: actions/checkout@v2
|
||||
- name: check-spelling
|
||||
id: spelling
|
||||
uses: check-spelling/check-spelling@v0.0.21
|
||||
with:
|
||||
ref: refs/pull/${{github.event.pull_request.number}}/merge
|
||||
- name: checkout
|
||||
if: "!contains(github.event_name, 'pull_request')"
|
||||
uses: actions/checkout@v2
|
||||
- uses: check-spelling/check-spelling@v0.0.19
|
||||
suppress_push_for_open_pull_request: 1
|
||||
checkout: true
|
||||
check_file_names: 1
|
||||
spell_check_this: check-spelling/spell-check-this@prerelease
|
||||
post_comment: 0
|
||||
use_magic_file: 1
|
||||
extra_dictionary_limit: 10
|
||||
extra_dictionaries:
|
||||
cspell:software-terms/src/software-terms.txt
|
||||
cspell:python/src/python/python-lib.txt
|
||||
cspell:node/node.txt
|
||||
cspell:cpp/src/stdlib-c.txt
|
||||
cspell:cpp/src/stdlib-cpp.txt
|
||||
cspell:fullstack/fullstack.txt
|
||||
cspell:filetypes/filetypes.txt
|
||||
cspell:html/html.txt
|
||||
cspell:cpp/src/compiler-msvc.txt
|
||||
cspell:python/src/common/extra.txt
|
||||
cspell:powershell/powershell.txt
|
||||
cspell:aws/aws.txt
|
||||
cspell:cpp/src/lang-keywords.txt
|
||||
cspell:npm/npm.txt
|
||||
cspell:dotnet/dotnet.txt
|
||||
cspell:python/src/python/python.txt
|
||||
cspell:css/css.txt
|
||||
cspell:cpp/src/stdlib-cmath.txt
|
||||
check_extra_dictionaries: ''
|
||||
|
||||
comment-push:
|
||||
name: Report (Push)
|
||||
# If your workflow isn't running on push, you can remove this job
|
||||
runs-on: ubuntu-latest
|
||||
needs: spelling
|
||||
permissions:
|
||||
contents: write
|
||||
if: (success() || failure()) && needs.spelling.outputs.followup && github.event_name == 'push'
|
||||
steps:
|
||||
- name: comment
|
||||
uses: check-spelling/check-spelling@v0.0.21
|
||||
with:
|
||||
checkout: true
|
||||
spell_check_this: check-spelling/spell-check-this@prerelease
|
||||
task: ${{ needs.spelling.outputs.followup }}
|
||||
|
||||
comment-pr:
|
||||
name: Report (PR)
|
||||
# If you workflow isn't running on pull_request*, you can remove this job
|
||||
runs-on: ubuntu-latest
|
||||
needs: spelling
|
||||
permissions:
|
||||
pull-requests: write
|
||||
if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request')
|
||||
steps:
|
||||
- name: comment
|
||||
uses: check-spelling/check-spelling@v0.0.21
|
||||
with:
|
||||
checkout: true
|
||||
spell_check_this: check-spelling/spell-check-this@prerelease
|
||||
task: ${{ needs.spelling.outputs.followup }}
|
||||
|
||||
@@ -61,6 +61,9 @@ Author(s):
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#include "til.h"
|
||||
|
||||
#include <til/mutex.h>
|
||||
#include <til/throttled_func.h>
|
||||
|
||||
// Common includes for most tests:
|
||||
#include "../../inc/argb.h"
|
||||
#include "../../inc/conattrs.hpp"
|
||||
|
||||
88
src/cascadia/TerminalApp/AdminWarningPlaceholder.cpp
Normal file
88
src/cascadia/TerminalApp/AdminWarningPlaceholder.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pch.h"
|
||||
#include "AdminWarningPlaceholder.h"
|
||||
#include "AdminWarningPlaceholder.g.cpp"
|
||||
#include <UIAutomationCore.h>
|
||||
#include <LibraryResources.h>
|
||||
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Xaml::Automation::Peers;
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
AdminWarningPlaceholder::AdminWarningPlaceholder(const winrt::Microsoft::Terminal::Control::TermControl& control, const winrt::hstring& cmdline) :
|
||||
_control{ control },
|
||||
_Commandline{ cmdline }
|
||||
{
|
||||
InitializeComponent();
|
||||
// If the content we're hosting is a TermControl, then use the control's
|
||||
// BG as our BG, to give the impression that it's there, under the
|
||||
// dialog.
|
||||
if (const auto termControl{ control.try_as<winrt::Microsoft::Terminal::Control::TermControl>() })
|
||||
{
|
||||
RootGrid().Background(termControl.BackgroundBrush());
|
||||
}
|
||||
|
||||
_layoutUpdatedRevoker = RootGrid().LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) {
|
||||
// Only let this succeed once.
|
||||
_layoutUpdatedRevoker.revoke();
|
||||
|
||||
if (auto automationPeer{ FrameworkElementAutomationPeer::FromElement(ApproveCommandlineWarningTitle()) })
|
||||
{
|
||||
// automationPeer.try_as<FrameworkElementAutomationPeer>().RaiseStructureChangedEvent(Automation::Peers::AutomationStructureChangeType::ChildrenBulkAdded, ApproveCommandlineWarningPrefixTextBlock());
|
||||
|
||||
automationPeer.RaiseNotificationEvent(
|
||||
AutomationNotificationKind::ActionCompleted,
|
||||
AutomationNotificationProcessing::CurrentThenMostRecent,
|
||||
L"Foo",
|
||||
L"ApproveCommandlineWarningTitle" /* unique name for this notification category */
|
||||
);
|
||||
}
|
||||
CancelButton().Focus(FocusState::Programmatic);
|
||||
});
|
||||
}
|
||||
void AdminWarningPlaceholder::_primaryButtonClick(winrt::Windows::Foundation::IInspectable const& /*sender*/,
|
||||
RoutedEventArgs const& e)
|
||||
{
|
||||
if (auto automationPeer{ FrameworkElementAutomationPeer::FromElement(PrimaryButton()) })
|
||||
{
|
||||
automationPeer.RaiseNotificationEvent(
|
||||
AutomationNotificationKind::ActionCompleted,
|
||||
AutomationNotificationProcessing::CurrentThenMostRecent,
|
||||
L"PrimaryButton",
|
||||
L"_primaryButtonClick" /* unique name for this notification category */
|
||||
);
|
||||
}
|
||||
|
||||
_PrimaryButtonClickedHandlers(*this, e);
|
||||
}
|
||||
void AdminWarningPlaceholder::_cancelButtonClick(winrt::Windows::Foundation::IInspectable const& /*sender*/,
|
||||
RoutedEventArgs const& e)
|
||||
{
|
||||
if (auto automationPeer{ FrameworkElementAutomationPeer::FromElement(CancelButton()) })
|
||||
{
|
||||
automationPeer.RaiseNotificationEvent(
|
||||
AutomationNotificationKind::ActionCompleted,
|
||||
AutomationNotificationProcessing::CurrentThenMostRecent,
|
||||
L"CancelButton",
|
||||
L"_cancelButtonClick" /* unique name for this notification category */
|
||||
);
|
||||
}
|
||||
|
||||
_CancelButtonClickedHandlers(*this, e);
|
||||
}
|
||||
winrt::Windows::UI::Xaml::Controls::UserControl AdminWarningPlaceholder::Control()
|
||||
{
|
||||
return _control;
|
||||
}
|
||||
|
||||
winrt::hstring AdminWarningPlaceholder::ControlName() const
|
||||
{
|
||||
return RS_(L"AdminWarningPlaceholderControlName");
|
||||
}
|
||||
|
||||
}
|
||||
33
src/cascadia/TerminalApp/AdminWarningPlaceholder.h
Normal file
33
src/cascadia/TerminalApp/AdminWarningPlaceholder.h
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AdminWarningPlaceholder.g.h"
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct AdminWarningPlaceholder : AdminWarningPlaceholderT<AdminWarningPlaceholder>
|
||||
{
|
||||
AdminWarningPlaceholder(const winrt::Microsoft::Terminal::Control::TermControl& control, const winrt::hstring& cmdline);
|
||||
winrt::hstring ControlName() const;
|
||||
winrt::Windows::UI::Xaml::Controls::UserControl Control();
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, Commandline, _PropertyChangedHandlers);
|
||||
TYPED_EVENT(PrimaryButtonClicked, TerminalApp::AdminWarningPlaceholder, winrt::Windows::UI::Xaml::RoutedEventArgs);
|
||||
TYPED_EVENT(CancelButtonClicked, TerminalApp::AdminWarningPlaceholder, winrt::Windows::UI::Xaml::RoutedEventArgs);
|
||||
|
||||
private:
|
||||
friend struct AdminWarningPlaceholderT<AdminWarningPlaceholder>; // friend our parent so it can bind private event handlers
|
||||
|
||||
winrt::Microsoft::Terminal::Control::TermControl _control{ nullptr };
|
||||
|
||||
void _primaryButtonClick(winrt::Windows::Foundation::IInspectable const& sender,
|
||||
winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
void _cancelButtonClick(winrt::Windows::Foundation::IInspectable const& sender,
|
||||
winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::Grid::LayoutUpdated_revoker _layoutUpdatedRevoker;
|
||||
};
|
||||
}
|
||||
12
src/cascadia/TerminalApp/AdminWarningPlaceholder.idl
Normal file
12
src/cascadia/TerminalApp/AdminWarningPlaceholder.idl
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass AdminWarningPlaceholder : Windows.UI.Xaml.Controls.UserControl,
|
||||
Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
String Commandline { get; };
|
||||
String ControlName { get; };
|
||||
}
|
||||
}
|
||||
79
src/cascadia/TerminalApp/AdminWarningPlaceholder.xaml
Normal file
79
src/cascadia/TerminalApp/AdminWarningPlaceholder.xaml
Normal file
@@ -0,0 +1,79 @@
|
||||
<!--
|
||||
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
|
||||
the MIT License. See LICENSE in the project root for license information.
|
||||
-->
|
||||
<UserControl x:Class="TerminalApp.AdminWarningPlaceholder"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:TerminalApp"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
|
||||
AutomationProperties.Name="{x:Bind ControlName, Mode=OneWay}"
|
||||
IsTabStop="True"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<!--
|
||||
We have to use two grids to be tricky here:
|
||||
- The outer grid will get its background from the control it hosts, and
|
||||
expands to fit its container. This will make it look like the background
|
||||
fills the space available.
|
||||
- The inner grid is set to Center,Center, so that the "dialog" appears
|
||||
centered
|
||||
-->
|
||||
|
||||
<Grid x:Name="RootGrid"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<Border Margin="8,8,8,8"
|
||||
Padding="16,8,16,8"
|
||||
Background="{ThemeResource SystemControlBackgroundAltHighBrush}"
|
||||
BorderBrush="{ThemeResource SystemAccentColor}"
|
||||
BorderThickness="2,2,2,2"
|
||||
CornerRadius="{ThemeResource OverlayCornerRadius}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock x:Uid="ApproveCommandlineWarningTitle"
|
||||
x:Name="ApproveCommandlineWarningTitle"
|
||||
Padding="0,0,0,16"
|
||||
HorizontalAlignment="Left"
|
||||
FontSize="20"
|
||||
FontWeight="Normal"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
|
||||
<TextBlock x:Uid="ApproveCommandlineWarningPrefixTextBlock"
|
||||
x:Name="ApproveCommandlineWarningPrefixTextBlock"
|
||||
HorizontalAlignment="Left"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
|
||||
<TextBlock Margin="0,8,0,8"
|
||||
HorizontalAlignment="Center"
|
||||
FontFamily="Cascadia Code"
|
||||
Text="{x:Bind Commandline, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
|
||||
<TextBlock x:Uid="ApproveCommandlineWarningSuffixTextBlock"
|
||||
HorizontalAlignment="Left"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<StackPanel HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<Button x:Name="PrimaryButton"
|
||||
x:Uid="ApproveCommandlineWarning_PrimaryButton"
|
||||
Margin="8"
|
||||
IsTabStop="True"
|
||||
HorizontalAlignment="Right"
|
||||
Click="_primaryButtonClick"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
<Button x:Name="CancelButton"
|
||||
x:Uid="ApproveCommandlineWarning_CancelButton"
|
||||
IsTabStop="True"
|
||||
HorizontalAlignment="Right"
|
||||
Click="_cancelButtonClick" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <WtExeUtils.h>
|
||||
#include <wil/token_helpers.h >
|
||||
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
using namespace winrt::Windows::ApplicationModel;
|
||||
using namespace winrt::Windows::ApplicationModel::DataTransfer;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
@@ -139,28 +141,8 @@ static Documents::Run _BuildErrorRun(const winrt::hstring& text, const ResourceD
|
||||
// Return Value:
|
||||
// - true if the user is an administrator
|
||||
static bool _isUserAdmin() noexcept
|
||||
try
|
||||
{
|
||||
wil::unique_handle processToken{ GetCurrentProcessToken() };
|
||||
const auto elevationType = wil::get_token_information<TOKEN_ELEVATION_TYPE>(processToken.get());
|
||||
const auto elevationState = wil::get_token_information<TOKEN_ELEVATION>(processToken.get());
|
||||
if (elevationType == TokenElevationTypeDefault && elevationState.TokenIsElevated)
|
||||
{
|
||||
// In this case, the user has UAC entirely disabled. This is sort of
|
||||
// weird, we treat this like the user isn't an admin at all. There's no
|
||||
// separation of powers, so the things we normally want to gate on
|
||||
// "having special powers" doesn't apply.
|
||||
//
|
||||
// See GH#7754, GH#11096
|
||||
return false;
|
||||
}
|
||||
|
||||
return wil::test_token_membership(nullptr, SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
return false;
|
||||
return Microsoft::Console::Utils::IsElevated();
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
@@ -895,8 +877,6 @@ namespace winrt::TerminalApp::implementation
|
||||
void AppLogic::_RegisterSettingsChange()
|
||||
{
|
||||
const std::filesystem::path settingsPath{ std::wstring_view{ CascadiaSettings::SettingsPath() } };
|
||||
const std::filesystem::path statePath{ std::wstring_view{ ApplicationState::SharedInstance().FilePath() } };
|
||||
|
||||
_reader.create(
|
||||
settingsPath.parent_path().c_str(),
|
||||
false,
|
||||
@@ -905,14 +885,20 @@ namespace winrt::TerminalApp::implementation
|
||||
// editors, who will write a temp file, then rename it to be the
|
||||
// actual file you wrote. So listen for that too.
|
||||
wil::FolderChangeEvents::FileName | wil::FolderChangeEvents::LastWriteTime,
|
||||
[this, settingsBasename = settingsPath.filename(), stateBasename = statePath.filename()](wil::FolderChangeEvent, PCWSTR fileModified) {
|
||||
const auto modifiedBasename = std::filesystem::path{ fileModified }.filename();
|
||||
[this, settingsBasename = settingsPath.filename()](wil::FolderChangeEvent, PCWSTR fileModified) {
|
||||
// DO NOT create a static reference to
|
||||
// ApplicationState::SharedInstance here. See
|
||||
// https://github.com/microsoft/terminal/pull/11222/files/9ff2775122a496fb8b1bcc7a0b83a64ce5b26c5f#r719627541
|
||||
// for why. ApplicationState::SharedInstance already caches it's
|
||||
// own static ref.
|
||||
|
||||
const winrt::hstring modifiedBasename{ std::filesystem::path{ fileModified }.filename().c_str() };
|
||||
|
||||
if (modifiedBasename == settingsBasename)
|
||||
{
|
||||
_reloadSettings->Run();
|
||||
}
|
||||
else if (modifiedBasename == stateBasename)
|
||||
else if (ApplicationState::SharedInstance().IsStatePath(modifiedBasename))
|
||||
{
|
||||
_reloadState();
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ static const Duration AnimationDuration = DurationHelper::FromTimeSpan(winrt::Wi
|
||||
winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_focusedBorderBrush = { nullptr };
|
||||
winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_unfocusedBorderBrush = { nullptr };
|
||||
|
||||
Pane::Pane(const Profile& profile, const TermControl& control, const bool lastFocused) :
|
||||
Pane::Pane(const Profile& profile, const Controls::UserControl& control, const bool lastFocused) :
|
||||
_control{ control },
|
||||
_lastActive{ lastFocused },
|
||||
_profile{ profile }
|
||||
@@ -42,8 +42,11 @@ Pane::Pane(const Profile& profile, const TermControl& control, const bool lastFo
|
||||
_root.Children().Append(_borderFirst);
|
||||
_borderFirst.Child(_control);
|
||||
|
||||
_connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler });
|
||||
_warningBellToken = _control.WarningBell({ this, &Pane::_ControlWarningBellHandler });
|
||||
if (const auto& termControl{ _control.try_as<TermControl>() })
|
||||
{
|
||||
_connectionStateChangedToken = termControl.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler });
|
||||
_warningBellToken = termControl.WarningBell({ this, &Pane::_ControlWarningBellHandler });
|
||||
}
|
||||
|
||||
// On the first Pane's creation, lookup resources we'll use to theme the
|
||||
// Pane, including the brushed to use for the focused/unfocused border
|
||||
@@ -125,7 +128,19 @@ NewTerminalArgs Pane::GetTerminalArgsForPane() const
|
||||
assert(_IsLeaf());
|
||||
|
||||
NewTerminalArgs args{};
|
||||
auto controlSettings = _control.Settings().as<TerminalSettings>();
|
||||
auto termControl{ _control.try_as<TermControl>() };
|
||||
if (!termControl)
|
||||
{
|
||||
if (auto adminWarning{ _control.try_as<AdminWarningPlaceholder>() })
|
||||
{
|
||||
termControl = adminWarning.Content().try_as<TermControl>();
|
||||
}
|
||||
}
|
||||
if (!termControl)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
auto controlSettings = termControl.Settings().as<TerminalSettings>();
|
||||
|
||||
args.Profile(controlSettings.ProfileName());
|
||||
args.StartingDirectory(controlSettings.StartingDirectory());
|
||||
@@ -824,6 +839,31 @@ bool Pane::SwapPanes(std::shared_ptr<Pane> first, std::shared_ptr<Pane> second)
|
||||
return false;
|
||||
}
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::UserControl Pane::ReplaceControl(const winrt::Windows::UI::Xaml::Controls::UserControl& control)
|
||||
{
|
||||
if (!_IsLeaf())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto& oldControl = _control;
|
||||
if (const auto& oldTermControl{ _control.try_as<TermControl>() })
|
||||
{
|
||||
oldTermControl.ConnectionStateChanged(_connectionStateChangedToken);
|
||||
oldTermControl.WarningBell(_warningBellToken);
|
||||
}
|
||||
|
||||
_control = control;
|
||||
_borderFirst.Child(_control);
|
||||
if (const auto& termControl{ _control.try_as<TermControl>() })
|
||||
{
|
||||
_connectionStateChangedToken = termControl.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler });
|
||||
_warningBellToken = termControl.WarningBell({ this, &Pane::_ControlWarningBellHandler });
|
||||
}
|
||||
|
||||
return oldControl;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Given two panes' offsets, test whether the `direction` side of first is adjacent to second.
|
||||
// Arguments:
|
||||
@@ -1071,8 +1111,12 @@ void Pane::_ControlConnectionStateChangedHandler(const winrt::Windows::Foundatio
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto newConnectionState = _control.ConnectionState();
|
||||
const auto& termControl{ _control.try_as<TermControl>() };
|
||||
if (!termControl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const auto newConnectionState = termControl.ConnectionState();
|
||||
const auto previousConnectionState = std::exchange(_connectionState, newConnectionState);
|
||||
|
||||
if (newConnectionState < ConnectionState::Closed)
|
||||
@@ -1115,7 +1159,9 @@ void Pane::_ControlWarningBellHandler(const winrt::Windows::Foundation::IInspect
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_profile)
|
||||
|
||||
const auto& termControl{ _control.try_as<TermControl>() };
|
||||
if (_profile && termControl)
|
||||
{
|
||||
// We don't want to do anything if nothing is set, so check for that first
|
||||
if (static_cast<int>(_profile.BellStyle()) != 0)
|
||||
@@ -1129,7 +1175,7 @@ void Pane::_ControlWarningBellHandler(const winrt::Windows::Foundation::IInspect
|
||||
|
||||
if (WI_IsFlagSet(_profile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Window))
|
||||
{
|
||||
_control.BellLightOn();
|
||||
termControl.BellLightOn();
|
||||
}
|
||||
|
||||
// raise the event with the bool value corresponding to the taskbar flag
|
||||
@@ -1189,7 +1235,11 @@ void Pane::Shutdown()
|
||||
std::unique_lock lock{ _createCloseLock };
|
||||
if (_IsLeaf())
|
||||
{
|
||||
_control.Close();
|
||||
const auto& termControl{ _control.try_as<TermControl>() };
|
||||
if (termControl)
|
||||
{
|
||||
termControl.Close();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1199,7 +1249,7 @@ void Pane::Shutdown()
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the root UIElement of this pane. There may be a single TermControl as a
|
||||
// - Get the root UIElement of this pane. There may be a single UserControl as a
|
||||
// child, or an entire tree of grids and panes as children of this element.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
@@ -1258,7 +1308,7 @@ TermControl Pane::GetLastFocusedTerminalControl()
|
||||
{
|
||||
if (p->_IsLeaf())
|
||||
{
|
||||
return p->_control;
|
||||
return p->GetTerminalControl();
|
||||
}
|
||||
pane = p;
|
||||
}
|
||||
@@ -1266,7 +1316,7 @@ TermControl Pane::GetLastFocusedTerminalControl()
|
||||
}
|
||||
return _firstChild->GetLastFocusedTerminalControl();
|
||||
}
|
||||
return _control;
|
||||
return GetTerminalControl();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -1275,8 +1325,14 @@ TermControl Pane::GetLastFocusedTerminalControl()
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - nullptr if this Pane is a parent, otherwise the TermControl of this Pane.
|
||||
TermControl Pane::GetTerminalControl()
|
||||
// - nullptr if this Pane is a parent or isn't hosting a Terminal, otherwise the
|
||||
// TermControl of this Pane.
|
||||
TermControl Pane::GetTerminalControl() const
|
||||
{
|
||||
return _IsLeaf() ? _control.try_as<TermControl>() : nullptr;
|
||||
}
|
||||
|
||||
Controls::UserControl Pane::GetUserControl() const
|
||||
{
|
||||
return _IsLeaf() ? _control : nullptr;
|
||||
}
|
||||
@@ -1449,9 +1505,13 @@ void Pane::_FocusFirstChild()
|
||||
void Pane::UpdateSettings(const TerminalSettingsCreateResult& settings, const Profile& profile)
|
||||
{
|
||||
assert(_IsLeaf());
|
||||
|
||||
const auto& termControl{ _control.try_as<TermControl>() };
|
||||
if (!termControl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_profile = profile;
|
||||
auto controlSettings = _control.Settings().as<TerminalSettings>();
|
||||
auto controlSettings = termControl.Settings().as<TerminalSettings>();
|
||||
// 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
|
||||
controlSettings.SetParent(settings.DefaultSettings());
|
||||
@@ -1464,8 +1524,8 @@ void Pane::UpdateSettings(const TerminalSettingsCreateResult& settings, const Pr
|
||||
// sure the unfocused settings inherit from that.
|
||||
unfocusedSettings.SetParent(controlSettings);
|
||||
}
|
||||
_control.UnfocusedAppearance(unfocusedSettings);
|
||||
_control.UpdateSettings();
|
||||
termControl.UnfocusedAppearance(unfocusedSettings);
|
||||
termControl.UpdateSettings();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -1606,8 +1666,12 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
|
||||
_id = remainingChild->Id();
|
||||
|
||||
// Add our new event handler before revoking the old one.
|
||||
_connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler });
|
||||
_warningBellToken = _control.WarningBell({ this, &Pane::_ControlWarningBellHandler });
|
||||
const auto& termControl{ _control.try_as<TermControl>() };
|
||||
if (termControl)
|
||||
{
|
||||
_connectionStateChangedToken = termControl.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler });
|
||||
_warningBellToken = termControl.WarningBell({ this, &Pane::_ControlWarningBellHandler });
|
||||
}
|
||||
|
||||
// Revoke the old event handlers. Remove both the handlers for the panes
|
||||
// themselves closing, and remove their handlers for their controls
|
||||
@@ -1621,8 +1685,11 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
|
||||
closedChild->WalkTree([](auto p) {
|
||||
if (p->_IsLeaf())
|
||||
{
|
||||
p->_control.ConnectionStateChanged(p->_connectionStateChangedToken);
|
||||
p->_control.WarningBell(p->_warningBellToken);
|
||||
if (const auto& closedControl{ p->_control.try_as<TermControl>() })
|
||||
{
|
||||
closedControl.ConnectionStateChanged(p->_connectionStateChangedToken);
|
||||
closedControl.WarningBell(p->_warningBellToken);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
@@ -1630,15 +1697,19 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
|
||||
|
||||
closedChild->Closed(closedChildClosedToken);
|
||||
remainingChild->Closed(remainingChildClosedToken);
|
||||
remainingChild->_control.ConnectionStateChanged(remainingChild->_connectionStateChangedToken);
|
||||
remainingChild->_control.WarningBell(remainingChild->_warningBellToken);
|
||||
|
||||
if (const auto& remainingControl{ remainingChild->_control.try_as<TermControl>() })
|
||||
{
|
||||
remainingControl.ConnectionStateChanged(remainingChild->_connectionStateChangedToken);
|
||||
remainingControl.WarningBell(remainingChild->_warningBellToken);
|
||||
}
|
||||
|
||||
// If we or either of our children was focused, we want to take that
|
||||
// focus from them.
|
||||
_lastActive = _lastActive || _firstChild->_lastActive || _secondChild->_lastActive;
|
||||
|
||||
// Remove all the ui elements of the remaining child. This'll make sure
|
||||
// we can re-attach the TermControl to our Grid.
|
||||
// we can re-attach the UserControl to our Grid.
|
||||
remainingChild->_root.Children().Clear();
|
||||
remainingChild->_borderFirst.Child(nullptr);
|
||||
|
||||
@@ -1649,7 +1720,7 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
|
||||
_root.ColumnDefinitions().Clear();
|
||||
_root.RowDefinitions().Clear();
|
||||
|
||||
// Reattach the TermControl to our grid.
|
||||
// Reattach the UserControl to our grid.
|
||||
_root.Children().Append(_borderFirst);
|
||||
_borderFirst.Child(_control);
|
||||
|
||||
@@ -1711,8 +1782,11 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
|
||||
closedChild->WalkTree([](auto p) {
|
||||
if (p->_IsLeaf())
|
||||
{
|
||||
p->_control.ConnectionStateChanged(p->_connectionStateChangedToken);
|
||||
p->_control.WarningBell(p->_warningBellToken);
|
||||
if (const auto& closedControl{ p->_control.try_as<TermControl>() })
|
||||
{
|
||||
closedControl.ConnectionStateChanged(p->_connectionStateChangedToken);
|
||||
closedControl.WarningBell(p->_warningBellToken);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
@@ -2344,18 +2418,18 @@ std::optional<bool> Pane::PreCalculateCanSplit(const std::shared_ptr<Pane> targe
|
||||
|
||||
// Method Description:
|
||||
// - Split the focused pane in our tree of panes, and place the given
|
||||
// TermControl into the newly created pane. If we're the focused pane, then
|
||||
// UserControl into the newly created pane. If we're the focused pane, then
|
||||
// we'll create two new children, and place them side-by-side in our Grid.
|
||||
// Arguments:
|
||||
// - splitType: what type of split we want to create.
|
||||
// - profile: The profile to associate with the newly created pane.
|
||||
// - control: A TermControl to use in the new pane.
|
||||
// - control: A UserControl to use in the new pane.
|
||||
// Return Value:
|
||||
// - The two newly created Panes, with the original pane first
|
||||
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::Split(SplitDirection splitType,
|
||||
const float splitSize,
|
||||
const Profile& profile,
|
||||
const TermControl& control)
|
||||
const Controls::UserControl& control)
|
||||
{
|
||||
if (!_lastActive)
|
||||
{
|
||||
@@ -2463,13 +2537,16 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect
|
||||
|
||||
if (_IsLeaf())
|
||||
{
|
||||
// revoke our handler - the child will take care of the control now.
|
||||
_control.ConnectionStateChanged(_connectionStateChangedToken);
|
||||
_connectionStateChangedToken.value = 0;
|
||||
_control.WarningBell(_warningBellToken);
|
||||
_warningBellToken.value = 0;
|
||||
if (const auto& termControl{ _control.try_as<TermControl>() })
|
||||
{
|
||||
// revoke our handler - the child will take care of the control now.
|
||||
termControl.ConnectionStateChanged(_connectionStateChangedToken);
|
||||
termControl.WarningBell(_warningBellToken);
|
||||
_connectionStateChangedToken.value = 0;
|
||||
_warningBellToken.value = 0;
|
||||
}
|
||||
|
||||
// Remove our old GotFocus handler from the control. We don't want the
|
||||
// Remove our old GotFocus handler from the control. We don't what the
|
||||
// control telling us that it's now focused, we want it telling its new
|
||||
// parent.
|
||||
_gotFocusRevoker.revoke();
|
||||
@@ -2477,7 +2554,7 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect
|
||||
}
|
||||
|
||||
// Remove any children we currently have. We can't add the existing
|
||||
// TermControl to a new grid until we do this.
|
||||
// UserControl to a new grid until we do this.
|
||||
_root.Children().Clear();
|
||||
_borderFirst.Child(nullptr);
|
||||
_borderSecond.Child(nullptr);
|
||||
@@ -2855,8 +2932,13 @@ float Pane::CalcSnappedDimension(const bool widthOrHeight, const float dimension
|
||||
// If requested size is already snapped, then both returned values equal this value.
|
||||
Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const float dimension) const
|
||||
{
|
||||
const auto& termControl{ _control.try_as<TermControl>() };
|
||||
if (_IsLeaf())
|
||||
{
|
||||
if (!termControl)
|
||||
{
|
||||
return { dimension, dimension };
|
||||
}
|
||||
// If we're a leaf pane, align to the grid of controlling terminal
|
||||
|
||||
const auto minSize = _GetMinSize();
|
||||
@@ -2867,7 +2949,7 @@ Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const
|
||||
return { minDimension, minDimension };
|
||||
}
|
||||
|
||||
float lower = _control.SnapDimensionToGrid(widthOrHeight, dimension);
|
||||
float lower = termControl.SnapDimensionToGrid(widthOrHeight, dimension);
|
||||
if (widthOrHeight)
|
||||
{
|
||||
lower += WI_IsFlagSet(_borders, Borders::Left) ? PaneBorderSize : 0;
|
||||
@@ -2887,7 +2969,7 @@ Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto cellSize = _control.CharacterDimensions();
|
||||
const auto cellSize = termControl.CharacterDimensions();
|
||||
const auto higher = lower + (widthOrHeight ? cellSize.Width : cellSize.Height);
|
||||
return { lower, higher };
|
||||
}
|
||||
@@ -2932,26 +3014,39 @@ Pane::SnapSizeResult Pane::_CalcSnappedDimension(const bool widthOrHeight, const
|
||||
// - <none>
|
||||
void Pane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const
|
||||
{
|
||||
const auto& termControl{ _control.try_as<TermControl>() };
|
||||
if (_IsLeaf())
|
||||
{
|
||||
// We're a leaf pane, so just add one more row or column (unless isMinimumSize
|
||||
// is true, see below).
|
||||
|
||||
if (sizeNode.isMinimumSize)
|
||||
if (termControl)
|
||||
{
|
||||
// If the node is of its minimum size, this size might not be snapped (it might
|
||||
// be, say, half a character, or fixed 10 pixels), so snap it upward. It might
|
||||
// however be already snapped, so add 1 to make sure it really increases
|
||||
// (not strictly necessary but to avoid surprises).
|
||||
sizeNode.size = _CalcSnappedDimension(widthOrHeight, sizeNode.size + 1).higher;
|
||||
// We're a leaf pane, so just add one more row or column (unless isMinimumSize
|
||||
// is true, see below).
|
||||
|
||||
if (sizeNode.isMinimumSize)
|
||||
{
|
||||
// If the node is of its minimum size, this size might not be snapped (it might
|
||||
// be, say, half a character, or fixed 10 pixels), so snap it upward. It might
|
||||
// however be already snapped, so add 1 to make sure it really increases
|
||||
// (not strictly necessary but to avoid surprises).
|
||||
sizeNode.size = _CalcSnappedDimension(widthOrHeight, sizeNode.size + 1).higher;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto cellSize = termControl.CharacterDimensions();
|
||||
sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto cellSize = _control.CharacterDimensions();
|
||||
sizeNode.size += widthOrHeight ? cellSize.Width : cellSize.Height;
|
||||
// If we're a leaf that didn't have a TermControl, then just increment
|
||||
// by one. We have to increment by _some_ value, because this is used in
|
||||
// a while() loop to find the next bigger size we can snap to. But since
|
||||
// a non-terminal control doesn't really care what size it's snapped to,
|
||||
// we can just say "one pixel larger is the next snap point"
|
||||
sizeNode.size += 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
else // !_IsLeaf()
|
||||
{
|
||||
// We're a parent pane, so we have to advance dimension of our children panes. In
|
||||
// fact, we advance only one child (chosen later) to keep the growth fine-grained.
|
||||
@@ -3053,7 +3148,8 @@ Size Pane::_GetMinSize() const
|
||||
{
|
||||
if (_IsLeaf())
|
||||
{
|
||||
auto controlSize = _control.MinimumSize();
|
||||
const auto& termControl{ _control.try_as<TermControl>() };
|
||||
auto controlSize = termControl ? termControl.MinimumSize() : Size{ 1, 1 };
|
||||
auto newWidth = controlSize.Width;
|
||||
auto newHeight = controlSize.Height;
|
||||
|
||||
@@ -3239,7 +3335,10 @@ std::optional<SplitDirection> Pane::PreCalculateAutoSplit(const std::shared_ptr<
|
||||
// - 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());
|
||||
const auto& termControl{ GetTerminalControl() };
|
||||
return termControl ?
|
||||
termControl.ReadOnly() :
|
||||
(_IsLeaf() ? false : (_firstChild->ContainsReadOnly() || _secondChild->ContainsReadOnly()));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -3252,13 +3351,14 @@ bool Pane::ContainsReadOnly() const
|
||||
// - <none>
|
||||
void Pane::CollectTaskbarStates(std::vector<winrt::TerminalApp::TaskbarState>& states)
|
||||
{
|
||||
if (_IsLeaf())
|
||||
const auto& termControl{ GetTerminalControl() };
|
||||
if (termControl)
|
||||
{
|
||||
auto tbState{ winrt::make<winrt::TerminalApp::implementation::TaskbarState>(_control.TaskbarState(),
|
||||
_control.TaskbarProgress()) };
|
||||
auto tbState{ winrt::make<winrt::TerminalApp::implementation::TaskbarState>(termControl.TaskbarState(),
|
||||
termControl.TaskbarProgress()) };
|
||||
states.push_back(tbState);
|
||||
}
|
||||
else
|
||||
else if (!_IsLeaf())
|
||||
{
|
||||
_firstChild->CollectTaskbarStates(states);
|
||||
_secondChild->CollectTaskbarStates(states);
|
||||
|
||||
@@ -56,7 +56,7 @@ class Pane : public std::enable_shared_from_this<Pane>
|
||||
{
|
||||
public:
|
||||
Pane(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile,
|
||||
const winrt::Microsoft::Terminal::Control::TermControl& control,
|
||||
const winrt::Windows::UI::Xaml::Controls::UserControl& control,
|
||||
const bool lastFocused = false);
|
||||
|
||||
Pane(std::shared_ptr<Pane> first,
|
||||
@@ -66,8 +66,9 @@ public:
|
||||
const bool lastFocused = false);
|
||||
|
||||
std::shared_ptr<Pane> GetActivePane();
|
||||
winrt::Windows::UI::Xaml::Controls::UserControl GetUserControl() const;
|
||||
winrt::Microsoft::Terminal::Control::TermControl GetLastFocusedTerminalControl();
|
||||
winrt::Microsoft::Terminal::Control::TermControl GetTerminalControl();
|
||||
winrt::Microsoft::Terminal::Control::TermControl GetTerminalControl() const;
|
||||
winrt::Microsoft::Terminal::Settings::Model::Profile GetFocusedProfile();
|
||||
|
||||
// Method Description:
|
||||
@@ -111,7 +112,7 @@ public:
|
||||
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Split(winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType,
|
||||
const float splitSize,
|
||||
const winrt::Microsoft::Terminal::Settings::Model::Profile& profile,
|
||||
const winrt::Microsoft::Terminal::Control::TermControl& control);
|
||||
const winrt::Windows::UI::Xaml::Controls::UserControl& control);
|
||||
bool ToggleSplitOrientation();
|
||||
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
|
||||
std::optional<winrt::Microsoft::Terminal::Settings::Model::SplitDirection> PreCalculateAutoSplit(const std::shared_ptr<Pane> target,
|
||||
@@ -127,6 +128,8 @@ public:
|
||||
winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType);
|
||||
std::shared_ptr<Pane> DetachPane(std::shared_ptr<Pane> pane);
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::UserControl ReplaceControl(const winrt::Windows::UI::Xaml::Controls::UserControl& control);
|
||||
|
||||
int GetLeafPaneCount() const noexcept;
|
||||
|
||||
void Maximize(std::shared_ptr<Pane> zoomedPane);
|
||||
@@ -201,7 +204,8 @@ private:
|
||||
winrt::Windows::UI::Xaml::Controls::Grid _root{};
|
||||
winrt::Windows::UI::Xaml::Controls::Border _borderFirst{};
|
||||
winrt::Windows::UI::Xaml::Controls::Border _borderSecond{};
|
||||
winrt::Microsoft::Terminal::Control::TermControl _control{ nullptr };
|
||||
winrt::Windows::UI::Xaml::Controls::UserControl _control{ nullptr };
|
||||
|
||||
winrt::Microsoft::Terminal::TerminalConnection::ConnectionState _connectionState{ winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::NotConnected };
|
||||
static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_focusedBorderBrush;
|
||||
static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_unfocusedBorderBrush;
|
||||
|
||||
@@ -504,6 +504,21 @@
|
||||
<data name="MultiLinePasteDialog.Title" xml:space="preserve">
|
||||
<value>Warning</value>
|
||||
</data>
|
||||
<data name="ApproveCommandlineWarning_CancelButton.Content" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="ApproveCommandlineWarningPrefixTextBlock.Text" xml:space="preserve">
|
||||
<value>You are about to execute the following commandline:</value>
|
||||
</data>
|
||||
<data name="ApproveCommandlineWarningSuffixTextBlock.Text" xml:space="preserve">
|
||||
<value>Do you wish to continue?</value>
|
||||
</data>
|
||||
<data name="ApproveCommandlineWarning_PrimaryButton.Content" xml:space="preserve">
|
||||
<value>Allow Commandline</value>
|
||||
</data>
|
||||
<data name="ApproveCommandlineWarningTitle.Text" xml:space="preserve">
|
||||
<value>Warning</value>
|
||||
</data>
|
||||
<data name="CommandPalette_SearchBox.PlaceholderText" xml:space="preserve">
|
||||
<value>Type a command name...</value>
|
||||
</data>
|
||||
@@ -549,6 +564,9 @@
|
||||
<value>Enter a wt commandline to run</value>
|
||||
<comment>{Locked="wt"} </comment>
|
||||
</data>
|
||||
<data name="AdminWarningPlaceholderControlName" xml:space="preserve">
|
||||
<value>Admin Warning Dialog</value>
|
||||
</data>
|
||||
<data name="CrimsonColorButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Crimson</value>
|
||||
</data>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <LibraryResources.h>
|
||||
|
||||
#include "TabRowControl.h"
|
||||
#include "AdminWarningPlaceholder.h"
|
||||
#include "ColorHelper.h"
|
||||
#include "DebugTapConnection.h"
|
||||
#include "SettingsTab.h"
|
||||
@@ -248,7 +249,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// - profile: profile settings for this connection
|
||||
// - settings: the TerminalSettings object to use to create the TerminalControl with.
|
||||
// - existingConnection: optionally receives a connection from the outside world instead of attempting to create one
|
||||
void TerminalPage::_CreateNewTabWithProfileAndSettings(const Profile& profile, const TerminalSettingsCreateResult& settings, TerminalConnection::ITerminalConnection existingConnection)
|
||||
void TerminalPage::_CreateNewTabWithProfileAndSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult settings, TerminalConnection::ITerminalConnection existingConnection)
|
||||
{
|
||||
// Initialize the new tab
|
||||
// Create a connection based on the values in our settings object if we weren't given one.
|
||||
@@ -279,8 +280,19 @@ namespace winrt::TerminalApp::implementation
|
||||
// Give term control a child of the settings so that any overrides go in the child
|
||||
// This way, when we do a settings reload we just update the parent and the overrides remain
|
||||
auto term = _InitControl(settings, connection);
|
||||
WUX::Controls::UserControl controlToAdd{ term };
|
||||
|
||||
auto newTabImpl = winrt::make_self<TerminalTab>(profile, term);
|
||||
const auto& cmdline{ settings.DefaultSettings().Commandline() };
|
||||
const bool doAdminWarning = _shouldPromptForCommandline(cmdline);
|
||||
if (doAdminWarning)
|
||||
{
|
||||
auto warningControl{ winrt::make_self<implementation::AdminWarningPlaceholder>(term, cmdline) };
|
||||
warningControl->PrimaryButtonClicked({ get_weak(), &TerminalPage::_adminWarningPrimaryClicked });
|
||||
warningControl->CancelButtonClicked({ get_weak(), &TerminalPage::_adminWarningCancelClicked });
|
||||
controlToAdd = *warningControl;
|
||||
}
|
||||
|
||||
auto newTabImpl = winrt::make_self<TerminalTab>(profile, controlToAdd);
|
||||
_RegisterTerminalEvents(term);
|
||||
_InitializeTab(newTabImpl);
|
||||
|
||||
|
||||
@@ -63,6 +63,9 @@
|
||||
<Page Include="CommandPalette.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="AdminWarningPlaceholder.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!-- ========================= Headers ======================== -->
|
||||
<ItemGroup>
|
||||
@@ -141,6 +144,9 @@
|
||||
<ClInclude Include="AppLogic.h">
|
||||
<DependentUpon>AppLogic.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="AdminWarningPlaceholder.h">
|
||||
<DependentUpon>AdminWarningPlaceholder.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Toast.h" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= Cpp Files ======================== -->
|
||||
@@ -234,6 +240,9 @@
|
||||
<ClCompile Include="AppLogic.cpp">
|
||||
<DependentUpon>AppLogic.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="AdminWarningPlaceholder.cpp">
|
||||
<DependentUpon>AdminWarningPlaceholder.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
|
||||
<ClCompile Include="Toast.cpp" />
|
||||
</ItemGroup>
|
||||
@@ -295,6 +304,10 @@
|
||||
<DependentUpon>CommandPalette.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="AdminWarningPlaceholder.idl">
|
||||
<DependentUpon>AdminWarningPlaceholder.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="FilteredCommand.idl" />
|
||||
<Midl Include="EmptyStringVisibilityConverter.idl" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "DebugTapConnection.h"
|
||||
#include "SettingsTab.h"
|
||||
#include "RenameWindowRequestedArgs.g.cpp"
|
||||
#include "AdminWarningPlaceholder.h"
|
||||
#include "../inc/WindowingBehavior.h"
|
||||
|
||||
#include <til/latch.h>
|
||||
@@ -298,10 +299,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// - true if the ApplicationState should be used.
|
||||
bool TerminalPage::ShouldUsePersistedLayout(CascadiaSettings& settings) const
|
||||
{
|
||||
// GH#5000 Until there is a separate state file for elevated sessions we should just not
|
||||
// save at all while in an elevated window.
|
||||
return Feature_PersistedWindowLayout::IsEnabled() &&
|
||||
!IsElevated() &&
|
||||
settings.GlobalSettings().FirstWindowPreference() == FirstWindowPreference::PersistedWindowLayout;
|
||||
}
|
||||
|
||||
@@ -655,6 +653,15 @@ namespace winrt::TerminalApp::implementation
|
||||
co_return ContentDialogResult::None;
|
||||
}
|
||||
|
||||
winrt::Windows::Foundation::IAsyncOperation<ContentDialogResult> TerminalPage::_ShowCommandlineApproveWarning()
|
||||
{
|
||||
if (auto presenter{ _dialogPresenter.get() })
|
||||
{
|
||||
co_return co_await presenter.ShowDialog(FindName(L"ApproveCommandlineWarning").try_as<WUX::Controls::ContentDialog>());
|
||||
}
|
||||
co_return ContentDialogResult::None;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Builds the flyout (dropdown) attached to the new tab button, and
|
||||
// attaches it to the button. Populates the flyout with one entry per
|
||||
@@ -1481,6 +1488,252 @@ namespace winrt::TerminalApp::implementation
|
||||
return true;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Returns true if this commandline is precisely an executable in
|
||||
// system32. We can use this to bypass the elevated state check, because
|
||||
// we're confident that executables in that path won't have been hijacked.
|
||||
// - TECHNICALLY a user can take ownership of a file in system32 and
|
||||
// replace it as the system administrator. You could say it's OK though
|
||||
// because you'd already have to have had admin rights to mess that
|
||||
// folder up or something.
|
||||
// - Will attempt to resolve environment strings.
|
||||
// - Will also manually allow commandlines as generated for the default WSL
|
||||
// distros.
|
||||
// Arguments:
|
||||
// - commandLine: the command to check.
|
||||
// Return Value (example):
|
||||
// - C:\windows\system32\cmd.exe -> returns true
|
||||
// - cmd.exe -> returns false
|
||||
// - C:\windows\system32\cmd.exe /k echo sneaky sneak -> returns false
|
||||
// - %SystemRoot%\System32\cmd.exe -> returns true
|
||||
// - %SystemRoot%\System32\wsl.exe -d <distro name> -> returns true
|
||||
static bool _isInSystem32(std::wstring_view commandLine)
|
||||
{
|
||||
// use C++11 magic statics to make sure we only do this once.
|
||||
static std::wstring systemDirectory = []() -> std::wstring {
|
||||
// *** THIS IS A SINGLETON ***
|
||||
static std::wstring sys32{};
|
||||
if (FAILED(wil::GetSystemDirectoryW(sys32)))
|
||||
{
|
||||
// we couldn't look up where system32 is?? Then it's definitely not
|
||||
// in System32
|
||||
return {};
|
||||
}
|
||||
return sys32;
|
||||
}();
|
||||
|
||||
if (systemDirectory.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::filesystem::path fullCommandlinePath{
|
||||
wil::ExpandEnvironmentStringsW<std::wstring>(commandLine.data())
|
||||
};
|
||||
|
||||
if (fullCommandlinePath.wstring().size() > systemDirectory.size())
|
||||
{
|
||||
// Get the first part of the executable path
|
||||
const auto start = fullCommandlinePath.wstring().substr(0, systemDirectory.size());
|
||||
// Doing this as an ASCII only check might be wrong, but I'm
|
||||
// guessing if system32 isn't at X:\windows\system32... this isn't
|
||||
// the only thing that is going to be sad in Windows.
|
||||
const auto pathEquals = til::equals_insensitive_ascii(start, systemDirectory);
|
||||
if (pathEquals && std::filesystem::exists(fullCommandlinePath))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Also, if the path is literally
|
||||
// %SystemRoot%\System32\wsl.exe -d <distro name>
|
||||
// then allow it.
|
||||
|
||||
// Largely stolen from _tryMangleStartingDirectoryForWSL in ConptyConnection.
|
||||
// Find the first space, quote or the end of the string -- we'll look
|
||||
// for wsl before that.
|
||||
const auto terminator{ commandLine.find_first_of(LR"(" )", 1) }; // look past the first character in case it starts with "
|
||||
const auto start{ til::at(commandLine, 0) == L'"' ? 1 : 0 };
|
||||
const std::filesystem::path executablePath{ commandLine.substr(start, terminator - start) };
|
||||
const auto executableFilename{ executablePath.filename().wstring() };
|
||||
|
||||
if (executableFilename == L"wsl" || executableFilename == L"wsl.exe")
|
||||
{
|
||||
// We've got a WSL -- let's just make sure it's the right one.
|
||||
if (executablePath.has_parent_path())
|
||||
{
|
||||
if (executablePath.parent_path().wstring() != systemDirectory)
|
||||
{
|
||||
return false; // it wasn't in system32!
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unqualified WSL, this is dangerous, so return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get everything after the wsl.exe
|
||||
const auto arguments{ terminator == std::wstring_view::npos ?
|
||||
std::wstring_view{} :
|
||||
commandLine.substr(terminator + 1) };
|
||||
const auto dashD{ arguments.find(L"-d ") };
|
||||
|
||||
// If we found a "-d " IMMEDIATELY AFTER wsl.exe. If it wasn't
|
||||
// immediately after, it could have been `wsl.exe --doSomethingEvil`
|
||||
if (dashD == 0)
|
||||
{
|
||||
// Using the string following "-d "...
|
||||
const auto afterDashD{ arguments.substr(dashD + 3) };
|
||||
// Find the next space
|
||||
const auto afterFirstWord = afterDashD.substr(dashD + 3).find(L" ");
|
||||
// if that space _wasn't_ at the end of the commandline, then
|
||||
// there were some other args. That means it was `wsl -d distro
|
||||
// anything`, and we should ask the user.
|
||||
//
|
||||
// So if it was at the end of the commandline, then there were
|
||||
// no other args besides the distro name.
|
||||
if (afterFirstWord == std::wstring::npos)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - For a given commandline, determines if we should prompt the user for
|
||||
// approval. We only do this check when elevated. This will check the
|
||||
// AllowedCommandlines in `elevated-state.json`, to see if the commandline
|
||||
// already exists in that list.
|
||||
// Arguments:
|
||||
// - cmdline: The commandline to check
|
||||
// Return Value:
|
||||
// - true if we should prompt the user for approval.
|
||||
bool TerminalPage::_shouldPromptForCommandline(const winrt::hstring& cmdline) const
|
||||
{
|
||||
// NOTE: For debugging purposes, changing this to `true || IsElevated()`
|
||||
// is a handy way of forcing the elevation logic, even when unelevated.
|
||||
if (true || IsElevated())
|
||||
{
|
||||
// If the cmdline is EXACTLY an executable in
|
||||
// `C:\WINDOWS\System32`, then ignore this check.
|
||||
if (_isInSystem32(cmdline))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (const auto& allowedCommandlines{ ApplicationState::SharedInstance().AllowedCommandlines() })
|
||||
{
|
||||
for (const auto& approved : allowedCommandlines)
|
||||
{
|
||||
if (approved == cmdline)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void TerminalPage::_adminWarningPrimaryClicked(const TerminalApp::AdminWarningPlaceholder& sender,
|
||||
const winrt::Windows::UI::Xaml::RoutedEventArgs& /*args*/)
|
||||
{
|
||||
auto warningControl{ winrt::get_self<AdminWarningPlaceholder>(sender) };
|
||||
const auto& cmdline{ warningControl->Commandline() };
|
||||
// Look through the tabs and panes to look for us. Whichever pane had us
|
||||
// as content - replace their content with the TermControl we were
|
||||
// holding on to.
|
||||
for (const auto& tab : _tabs)
|
||||
{
|
||||
if (const auto& tabImpl{ _GetTerminalTabImpl(tab) })
|
||||
{
|
||||
tabImpl->GetRootPane()->WalkTree([warningControl, cmdline, tabImpl](std::shared_ptr<Pane> pane) -> bool {
|
||||
if (pane->GetUserControl() == *warningControl)
|
||||
{
|
||||
// Hooray, we found us!
|
||||
pane->ReplaceControl(warningControl->Control());
|
||||
// Update the title, because replacing the control like
|
||||
// this is a little weird, and doesn't actually trigger
|
||||
// a TitleChanged by itself.
|
||||
tabImpl->UpdateTitle();
|
||||
// Don't return true here. We want to make sure to check
|
||||
// all the panes for the same commandline we just
|
||||
// approved.
|
||||
}
|
||||
else if (const auto& otherWarning{ winrt::get_self<AdminWarningPlaceholder>(pane->GetUserControl().try_as<TerminalApp::AdminWarningPlaceholder>()) })
|
||||
{
|
||||
// This pane wasn't us, but it did have a warning in it.
|
||||
// Was it a warning for the same commandline that we
|
||||
// just approved?
|
||||
if (otherWarning->Commandline() == cmdline)
|
||||
{
|
||||
// Go ahead and allow them as well.
|
||||
pane->ReplaceControl(otherWarning->Control());
|
||||
tabImpl->UpdateTitle();
|
||||
}
|
||||
}
|
||||
// return false so we make sure to iterate on every leaf.
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update the list of approved commandlines.
|
||||
auto allowedCommandlines{ ApplicationState::SharedInstance().AllowedCommandlines() };
|
||||
if (!allowedCommandlines)
|
||||
{
|
||||
allowedCommandlines = winrt::single_threaded_vector<winrt::hstring>();
|
||||
}
|
||||
|
||||
// But of course, we don't need to add this commandline if it's already
|
||||
// in the list of approved commandlines.
|
||||
bool foundCopy = false;
|
||||
for (const auto& approved : allowedCommandlines)
|
||||
{
|
||||
if (approved == cmdline)
|
||||
{
|
||||
foundCopy = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundCopy)
|
||||
{
|
||||
allowedCommandlines.Append(cmdline);
|
||||
}
|
||||
ApplicationState::SharedInstance().AllowedCommandlines(allowedCommandlines);
|
||||
}
|
||||
|
||||
void TerminalPage::_adminWarningCancelClicked(const TerminalApp::AdminWarningPlaceholder& sender,
|
||||
const winrt::Windows::UI::Xaml::RoutedEventArgs& /*args*/)
|
||||
{
|
||||
auto warningControl{ winrt::get_self<AdminWarningPlaceholder>(sender) };
|
||||
|
||||
for (const auto& tab : _tabs)
|
||||
{
|
||||
if (const auto& tabImpl{ _GetTerminalTabImpl(tab) })
|
||||
{
|
||||
tabImpl->GetRootPane()->WalkTree([warningControl](std::shared_ptr<Pane> pane) -> bool {
|
||||
if (pane->GetUserControl() == *warningControl)
|
||||
{
|
||||
pane->Close();
|
||||
return true;
|
||||
}
|
||||
// We're not going to auto-close all the other panes with
|
||||
// the same commandline warning, akin to what we're doing in
|
||||
// the approve handler. If they want to reject one pane, but
|
||||
// accept the next one, that's okay.
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Split the focused pane either horizontally or vertically, and place the
|
||||
// given TermControl into the newly created pane.
|
||||
@@ -1582,20 +1835,33 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
|
||||
auto newControl = _InitControl(controlSettings, controlConnection);
|
||||
WUX::Controls::UserControl controlToAdd{ newControl };
|
||||
|
||||
const auto& cmdline{ controlSettings.DefaultSettings().Commandline() };
|
||||
if (_shouldPromptForCommandline(cmdline))
|
||||
{
|
||||
auto warningControl{ winrt::make_self<implementation::AdminWarningPlaceholder>(newControl, cmdline) };
|
||||
warningControl->PrimaryButtonClicked({ get_weak(), &TerminalPage::_adminWarningPrimaryClicked });
|
||||
warningControl->CancelButtonClicked({ get_weak(), &TerminalPage::_adminWarningCancelClicked });
|
||||
controlToAdd = *warningControl;
|
||||
}
|
||||
|
||||
// Hookup our event handlers to the new terminal
|
||||
_RegisterTerminalEvents(newControl);
|
||||
|
||||
_UnZoomIfNeeded();
|
||||
|
||||
tab.SplitPane(realSplitType, splitSize, profile, newControl);
|
||||
tab.SplitPane(realSplitType, splitSize, profile, controlToAdd);
|
||||
|
||||
// After GH#6586, the control will no longer focus itself
|
||||
// automatically when it's finished being laid out. Manually focus
|
||||
// the control here instead.
|
||||
if (_startupState == StartupState::Initialized)
|
||||
{
|
||||
_GetActiveControl().Focus(FocusState::Programmatic);
|
||||
if (const auto& activeControl{ _GetActiveControl() })
|
||||
{
|
||||
activeControl.Focus(FocusState::Programmatic);
|
||||
}
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
@@ -203,12 +203,13 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowCloseReadOnlyDialog();
|
||||
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowMultiLinePasteWarningDialog();
|
||||
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowLargePasteWarningDialog();
|
||||
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowCommandlineApproveWarning();
|
||||
|
||||
void _CreateNewTabFlyout();
|
||||
void _OpenNewTabDropdown();
|
||||
HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
|
||||
void _CreateNewTabFromPane(std::shared_ptr<Pane> pane);
|
||||
void _CreateNewTabWithProfileAndSettings(const Microsoft::Terminal::Settings::Model::Profile& profile, const Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
|
||||
void _CreateNewTabWithProfileAndSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult settings, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
|
||||
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettings settings);
|
||||
|
||||
winrt::fire_and_forget _OpenNewWindow(const bool elevate, const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
|
||||
@@ -400,6 +401,12 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::Model::Profile GetClosestProfileForDuplicationOfProfile(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile) const noexcept;
|
||||
|
||||
bool _shouldPromptForCommandline(const winrt::hstring& cmdline) const;
|
||||
void _adminWarningPrimaryClicked(const winrt::TerminalApp::AdminWarningPlaceholder& sender,
|
||||
const winrt::Windows::UI::Xaml::RoutedEventArgs& args);
|
||||
void _adminWarningCancelClicked(const winrt::TerminalApp::AdminWarningPlaceholder& sender,
|
||||
const winrt::Windows::UI::Xaml::RoutedEventArgs& args);
|
||||
|
||||
winrt::fire_and_forget _ConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args) const;
|
||||
void _CloseOnExitInfoDismissHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args) const;
|
||||
void _KeyboardServiceWarningInfoDismissHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args) const;
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace winrt
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
TerminalTab::TerminalTab(const Profile& profile, const TermControl& control)
|
||||
TerminalTab::TerminalTab(const Profile& profile, const WUX::Controls::UserControl& control)
|
||||
{
|
||||
_rootPane = std::make_shared<Pane>(profile, control, true);
|
||||
|
||||
@@ -437,7 +437,10 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::fire_and_forget TerminalTab::Scroll(const int delta)
|
||||
{
|
||||
auto control = GetActiveTerminalControl();
|
||||
|
||||
if (!control)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
co_await winrt::resume_foreground(control.Dispatcher());
|
||||
|
||||
const auto currentOffset = control.ScrollOffset();
|
||||
@@ -513,7 +516,7 @@ namespace winrt::TerminalApp::implementation
|
||||
void TerminalTab::SplitPane(SplitDirection splitType,
|
||||
const float splitSize,
|
||||
const Profile& profile,
|
||||
TermControl& control)
|
||||
const WUX::Controls::UserControl& control)
|
||||
{
|
||||
// Make sure to take the ID before calling Split() - Split() will clear out the active pane's ID
|
||||
const auto activePaneId = _activePane->Id();
|
||||
@@ -533,7 +536,10 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
// Add a event handlers to the new panes' GotFocus event. When the pane
|
||||
// gains focus, we'll mark it as the new active pane.
|
||||
_AttachEventHandlersToControl(newPane->Id().value(), control);
|
||||
const auto& termControl{ control.try_as<TermControl>() };
|
||||
// _AttachEventHandlersToControl will immediately bail if the provided
|
||||
// control is null (READ: if the pane didn't have a TermControl)
|
||||
_AttachEventHandlersToControl(newPane->Id().value(), termControl);
|
||||
_AttachEventHandlersToPane(original);
|
||||
_AttachEventHandlersToPane(newPane);
|
||||
|
||||
@@ -858,6 +864,10 @@ namespace winrt::TerminalApp::implementation
|
||||
// - <none>
|
||||
void TerminalTab::_AttachEventHandlersToControl(const uint32_t paneId, const TermControl& control)
|
||||
{
|
||||
if (!control)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto weakThis{ get_weak() };
|
||||
auto dispatcher = TabViewItem().Dispatcher();
|
||||
ControlEventTokens events{};
|
||||
|
||||
@@ -21,7 +21,8 @@ namespace winrt::TerminalApp::implementation
|
||||
struct TerminalTab : TerminalTabT<TerminalTab, TabBase>
|
||||
{
|
||||
public:
|
||||
TerminalTab(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile, const winrt::Microsoft::Terminal::Control::TermControl& control);
|
||||
TerminalTab(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile,
|
||||
const winrt::Windows::UI::Xaml::Controls::UserControl& control);
|
||||
TerminalTab(std::shared_ptr<Pane> rootPane);
|
||||
|
||||
// Called after construction to perform the necessary setup, which relies on weak_ptr
|
||||
@@ -41,7 +42,7 @@ namespace winrt::TerminalApp::implementation
|
||||
void SplitPane(winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType,
|
||||
const float splitSize,
|
||||
const winrt::Microsoft::Terminal::Settings::Model::Profile& profile,
|
||||
winrt::Microsoft::Terminal::Control::TermControl& control);
|
||||
const winrt::Windows::UI::Xaml::Controls::UserControl& control);
|
||||
|
||||
void ToggleSplitOrientation();
|
||||
winrt::fire_and_forget UpdateIcon(const winrt::hstring iconPath);
|
||||
|
||||
@@ -2590,4 +2590,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
return _core.ReadEntireBuffer();
|
||||
}
|
||||
|
||||
Media::Brush TermControl::BackgroundBrush()
|
||||
{
|
||||
return RootGrid().Background();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
hstring ReadEntireBuffer() const;
|
||||
|
||||
Windows::UI::Xaml::Media::Brush BackgroundBrush();
|
||||
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
// clang-format off
|
||||
WINRT_CALLBACK(FontSizeChanged, Control::FontSizeChangedEventArgs);
|
||||
|
||||
@@ -71,5 +71,7 @@ namespace Microsoft.Terminal.Control
|
||||
void ToggleReadOnly();
|
||||
|
||||
String ReadEntireBuffer();
|
||||
|
||||
Windows.UI.Xaml.Media.Brush BackgroundBrush { get; };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,11 @@
|
||||
#include "ActionAndArgs.h"
|
||||
#include "JsonUtils.h"
|
||||
#include "FileUtils.h"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
static constexpr std::wstring_view stateFileName{ L"state.json" };
|
||||
static constexpr std::wstring_view elevatedStateFileName{ L"elevated-state.json" };
|
||||
|
||||
static constexpr std::string_view TabLayoutKey{ "tabLayout" };
|
||||
static constexpr std::string_view InitialPositionKey{ "initialPosition" };
|
||||
static constexpr std::string_view InitialSizeKey{ "initialSize" };
|
||||
@@ -85,15 +88,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
return trait.FromJson(root);
|
||||
}
|
||||
|
||||
// Returns the application-global ApplicationState object.
|
||||
Microsoft::Terminal::Settings::Model::ApplicationState ApplicationState::SharedInstance()
|
||||
{
|
||||
static auto state = winrt::make_self<ApplicationState>(GetBaseSettingsPath() / stateFileName);
|
||||
return *state;
|
||||
}
|
||||
|
||||
ApplicationState::ApplicationState(std::filesystem::path path) noexcept :
|
||||
_path{ std::move(path) },
|
||||
ApplicationState::ApplicationState(const std::filesystem::path& stateRoot) noexcept :
|
||||
_sharedPath{ stateRoot / stateFileName },
|
||||
_elevatedPath{ stateRoot / elevatedStateFileName },
|
||||
_throttler{ std::chrono::seconds(1), [this]() { _write(); } }
|
||||
{
|
||||
_read();
|
||||
@@ -102,9 +99,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// The destructor ensures that the last write is flushed to disk before returning.
|
||||
ApplicationState::~ApplicationState()
|
||||
{
|
||||
TraceLoggingWrite(g_hSettingsModelProvider,
|
||||
"ApplicationState_Dtor_Start",
|
||||
TraceLoggingDescription("Event at the start of the ApplicationState destructor"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
// This will ensure that we not just cancel the last outstanding timer,
|
||||
// but instead force it to run as soon as possible and wait for it to complete.
|
||||
_throttler.flush();
|
||||
|
||||
TraceLoggingWrite(g_hSettingsModelProvider,
|
||||
"ApplicationState_Dtor_End",
|
||||
TraceLoggingDescription("Event at the end of the ApplicationState destructor"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
|
||||
// Re-read the state.json from disk.
|
||||
@@ -113,80 +122,59 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
_read();
|
||||
}
|
||||
|
||||
// Returns the state.json path on the disk.
|
||||
winrt::hstring ApplicationState::FilePath() const noexcept
|
||||
bool ApplicationState::IsStatePath(const winrt::hstring& filename)
|
||||
{
|
||||
return winrt::hstring{ _path.wstring() };
|
||||
static const auto sharedPath{ _sharedPath.filename() };
|
||||
static const auto elevatedPath{ _elevatedPath.filename() };
|
||||
return filename == sharedPath || filename == elevatedPath;
|
||||
}
|
||||
|
||||
// Generate all getter/setters
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \
|
||||
type ApplicationState::name() const noexcept \
|
||||
{ \
|
||||
const auto state = _state.lock_shared(); \
|
||||
const auto& value = state->name; \
|
||||
return value ? *value : type{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
void ApplicationState::name(const type& value) noexcept \
|
||||
{ \
|
||||
{ \
|
||||
auto state = _state.lock(); \
|
||||
state->name.emplace(value); \
|
||||
state->name##Changed = true; \
|
||||
} \
|
||||
\
|
||||
_throttler(); \
|
||||
}
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
|
||||
Json::Value ApplicationState::_getRoot(const locked_hfile& file) const noexcept
|
||||
{
|
||||
Json::Value root;
|
||||
try
|
||||
{
|
||||
const auto data = ReadUTF8FileLocked(file);
|
||||
if (data.empty())
|
||||
{
|
||||
return root;
|
||||
}
|
||||
|
||||
std::string errs;
|
||||
std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
|
||||
|
||||
if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs))
|
||||
{
|
||||
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
|
||||
}
|
||||
}
|
||||
CATCH_LOG()
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
// Deserializes the state.json at _path into this ApplicationState.
|
||||
// Deserializes the state.json and user-state (or elevated-state if
|
||||
// elevated) into this ApplicationState.
|
||||
// * ANY errors during app state will result in the creation of a new empty state.
|
||||
// * ANY errors during runtime will result in changes being partially ignored.
|
||||
void ApplicationState::_read() const noexcept
|
||||
try
|
||||
{
|
||||
auto state = _state.lock();
|
||||
const auto file = OpenFileReadSharedLocked(_path);
|
||||
std::string errs;
|
||||
std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
|
||||
|
||||
auto root = _getRoot(file);
|
||||
// GetValueForKey() comes in two variants:
|
||||
// * take a std::optional<T> reference
|
||||
// * return std::optional<T> by value
|
||||
// At the time of writing the former version skips missing fields in the json,
|
||||
// but we want to explicitly clear state fields that were removed from state.json.
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \
|
||||
if (!state->name##Changed) \
|
||||
{ \
|
||||
state->name = JsonUtils::GetValueForKey<std::optional<type>>(root, key); \
|
||||
}
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
// First get shared state out of `state.json`.
|
||||
const auto sharedData = _readSharedContents().value_or(std::string{});
|
||||
if (!sharedData.empty())
|
||||
{
|
||||
Json::Value root;
|
||||
if (!reader->parse(sharedData.data(), sharedData.data() + sharedData.size(), &root, &errs))
|
||||
{
|
||||
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
|
||||
}
|
||||
|
||||
// - If we're elevated, we want to only load the Shared properties
|
||||
// from state.json. We'll then load the Local props from
|
||||
// `elevated-state.json`
|
||||
// - If we're unelevated, then load _everything_ from state.json.
|
||||
if (::Microsoft::Console::Utils::IsElevated())
|
||||
{
|
||||
// Only load shared properties if we're elevated
|
||||
FromJson(root, FileSource::Shared);
|
||||
|
||||
// Then, try and get anything in elevated-state
|
||||
if (const auto localData{ _readLocalContents().value_or(std::string{}) }; !localData.empty())
|
||||
{
|
||||
Json::Value root;
|
||||
if (!reader->parse(localData.data(), localData.data() + localData.size(), &root, &errs))
|
||||
{
|
||||
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
|
||||
}
|
||||
FromJson(root, FileSource::Local);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we're unelevated, then load everything.
|
||||
FromJson(root, FileSource::Shared | FileSource::Local);
|
||||
}
|
||||
}
|
||||
}
|
||||
CATCH_LOG()
|
||||
|
||||
@@ -194,29 +182,191 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// * Errors are only logged.
|
||||
// * _state->_writeScheduled is set to false, signaling our
|
||||
// setters that _synchronize() needs to be called again.
|
||||
void ApplicationState::_write() noexcept
|
||||
void ApplicationState::_write() const noexcept
|
||||
try
|
||||
{
|
||||
// re-read the state so that we can only update the properties that were changed.
|
||||
Json::Value root{};
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
|
||||
// When we're elevated, we've got to be tricky. We don't want to write
|
||||
// our window state, allowed commandlines, and other Local properties
|
||||
// into the shared `state.json`. But, if we only serialize the Shared
|
||||
// properties to a json blob, then we'll omit windowState entirely,
|
||||
// _removing_ the window state of the unelevated instance. Oh no!
|
||||
//
|
||||
// So, to be tricky, we'll first _load_ the shared state to a json blob.
|
||||
// We'll then serialize our view of the shared properties on top of that
|
||||
// blob. Then we'll write that blob back to the file. This will
|
||||
// round-trip the Local properties for the unelevated instances
|
||||
// untouched in state.json
|
||||
//
|
||||
// After that's done, we'll write our Local properties into
|
||||
// elevated-state.json.
|
||||
if (::Microsoft::Console::Utils::IsElevated())
|
||||
{
|
||||
auto state = _state.lock();
|
||||
const auto file = OpenFileRWExclusiveLocked(_path);
|
||||
root = _getRoot(file);
|
||||
std::string errs;
|
||||
std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
|
||||
Json::Value root;
|
||||
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \
|
||||
if (state->name##Changed) \
|
||||
{ \
|
||||
JsonUtils::SetValueForKey(root, key, state->name); \
|
||||
state->name##Changed = false; \
|
||||
}
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
// First load the contents of state.json into a json blob. This will
|
||||
// contain the Shared properties and the unelevated instance's Local
|
||||
// properties.
|
||||
const auto sharedData = _readSharedContents().value_or(std::string{});
|
||||
if (!sharedData.empty())
|
||||
{
|
||||
if (!reader->parse(sharedData.data(), sharedData.data() + sharedData.size(), &root, &errs))
|
||||
{
|
||||
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
|
||||
}
|
||||
}
|
||||
// Layer our shared properties on top of the blob from state.json,
|
||||
// and write it back out.
|
||||
_writeSharedContents(Json::writeString(wbuilder, _toJsonWithBlob(root, FileSource::Shared)));
|
||||
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
const auto content = Json::writeString(wbuilder, root);
|
||||
WriteUTF8FileLocked(file, content);
|
||||
// Finally, write our Local properties back to elevated-state.json
|
||||
_writeLocalContents(Json::writeString(wbuilder, ToJson(FileSource::Local)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're unelevated, this is easy. Just write everything back out.
|
||||
_writeLocalContents(Json::writeString(wbuilder, ToJson(FileSource::Local | FileSource::Shared)));
|
||||
}
|
||||
}
|
||||
CATCH_LOG()
|
||||
|
||||
// Returns the application-global ApplicationState object.
|
||||
Microsoft::Terminal::Settings::Model::ApplicationState ApplicationState::SharedInstance()
|
||||
{
|
||||
std::filesystem::path root{ GetBaseSettingsPath() };
|
||||
static auto state = winrt::make_self<ApplicationState>(root);
|
||||
return *state;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Loads data from the given json blob. Will only read the data that's in
|
||||
// the specified parseSource - so if we're reading the Local state file,
|
||||
// we won't destroy previously parsed Shared data.
|
||||
// - READ: there's no layering for app state.
|
||||
void ApplicationState::FromJson(const Json::Value& root, FileSource parseSource) const noexcept
|
||||
{
|
||||
auto state = _state.lock();
|
||||
// GetValueForKey() comes in two variants:
|
||||
// * take a std::optional<T> reference
|
||||
// * return std::optional<T> by value
|
||||
// At the time of writing the former version skips missing fields in the json,
|
||||
// but we want to explicitly clear state fields that were removed from state.json.
|
||||
//
|
||||
// GH#11222: We only load properties that are of the same type (Local or
|
||||
// Shared) which we requested. If we didn't want to load this type of
|
||||
// property, just skip it.
|
||||
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \
|
||||
if (WI_IsFlagSet(parseSource, source)) \
|
||||
state->name = JsonUtils::GetValueForKey<std::optional<type>>(root, key);
|
||||
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
}
|
||||
|
||||
Json::Value ApplicationState::ToJson(FileSource parseSource) const noexcept
|
||||
{
|
||||
Json::Value root{ Json::objectValue };
|
||||
return _toJsonWithBlob(root, parseSource);
|
||||
}
|
||||
|
||||
Json::Value ApplicationState::_toJsonWithBlob(Json::Value& root, FileSource parseSource) const noexcept
|
||||
{
|
||||
{
|
||||
auto state = _state.lock_shared();
|
||||
|
||||
// GH#11222: We only write properties that are of the same type (Local
|
||||
// or Shared) which we requested. If we didn't want to serialize this
|
||||
// type of property, just skip it.
|
||||
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \
|
||||
if (WI_IsFlagSet(parseSource, source)) \
|
||||
JsonUtils::SetValueForKey(root, key, state->name);
|
||||
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
// Generate all getter/setters
|
||||
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \
|
||||
type ApplicationState::name() const noexcept \
|
||||
{ \
|
||||
const auto state = _state.lock_shared(); \
|
||||
const auto& value = state->name; \
|
||||
return value ? *value : type{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
void ApplicationState::name(const type& value) noexcept \
|
||||
{ \
|
||||
{ \
|
||||
auto state = _state.lock(); \
|
||||
state->name.emplace(value); \
|
||||
} \
|
||||
\
|
||||
_throttler(); \
|
||||
}
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
|
||||
// Method Description:
|
||||
// - Read the contents of our "shared" state - state that should be shared
|
||||
// for elevated and unelevated instances. This is things like the list of
|
||||
// generated profiles, the command palette commandlines.
|
||||
std::optional<std::string> ApplicationState::_readSharedContents() const
|
||||
{
|
||||
return ReadUTF8FileIfExists(_sharedPath);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Read the contents of our "local" state - state that should be kept in
|
||||
// separate files for elevated and unelevated instances. This is things
|
||||
// like the persisted window state, and the approved commandlines (though,
|
||||
// those don't matter when unelevated).
|
||||
// - When elevated, this will DELETE `elevated-state.json` if it has bad
|
||||
// permissions, so we don't potentially read malicious data.
|
||||
std::optional<std::string> ApplicationState::_readLocalContents() const
|
||||
{
|
||||
return ::Microsoft::Console::Utils::IsElevated() ?
|
||||
ReadUTF8FileIfExists(_elevatedPath, true) :
|
||||
ReadUTF8FileIfExists(_sharedPath, false);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Write the contents of our "shared" state - state that should be shared
|
||||
// for elevated and unelevated instances. This will atomically write to
|
||||
// `state.json`
|
||||
void ApplicationState::_writeSharedContents(const std::string_view content) const
|
||||
{
|
||||
WriteUTF8FileAtomic(_sharedPath, content);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Write the contents of our "local" state - state that should be kept in
|
||||
// separate files for elevated and unelevated instances. When elevated,
|
||||
// this will write to `elevated-state.json`, and when unelevated, this
|
||||
// will atomically write to `user-state.json`
|
||||
void ApplicationState::_writeLocalContents(const std::string_view content) const
|
||||
{
|
||||
if (::Microsoft::Console::Utils::IsElevated())
|
||||
{
|
||||
// DON'T use WriteUTF8FileAtomic, which will write to a temporary file
|
||||
// then rename that file to the final filename. That actually lets us
|
||||
// overwrite the elevate file's contents even when unelevated, because
|
||||
// we're effectively deleting the original file, then renaming a
|
||||
// different file in it's place.
|
||||
//
|
||||
// We're not worried about someone else doing that though, if they do
|
||||
// that with the wrong permissions, then we'll just ignore the file and
|
||||
// start over.
|
||||
WriteUTF8File(_elevatedPath, content, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteUTF8FileAtomic(_sharedPath, content);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,21 +16,30 @@ Abstract:
|
||||
#include "WindowLayout.g.h"
|
||||
|
||||
#include <inc/cppwinrt_utils.h>
|
||||
#include <til/mutex.h>
|
||||
#include <til/throttled_func.h>
|
||||
#include "FileUtils.h"
|
||||
#include <JsonUtils.h>
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
// If a property is Shared, then it'll be stored in `state.json`, and used
|
||||
// in both elevated and unelevated instances of the Terminal. If a property
|
||||
// is marked Local, then it will have separate values for elevated and
|
||||
// unelevated instances.
|
||||
enum FileSource : int
|
||||
{
|
||||
Shared = 0x1,
|
||||
Local = 0x2
|
||||
};
|
||||
DEFINE_ENUM_FLAG_OPERATORS(FileSource);
|
||||
|
||||
// This macro generates all getters and setters for ApplicationState.
|
||||
// It provides X with the following arguments:
|
||||
// (type, function name, JSON key, ...variadic construction arguments)
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
#define MTSM_APPLICATION_STATE_FIELDS(X) \
|
||||
X(std::unordered_set<winrt::guid>, GeneratedProfiles, "generatedProfiles") \
|
||||
X(Windows::Foundation::Collections::IVector<Model::WindowLayout>, PersistedWindowLayouts, "persistedWindowLayouts") \
|
||||
X(Windows::Foundation::Collections::IVector<hstring>, RecentCommands, "recentCommands") \
|
||||
X(Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage>, DismissedMessages, "dismissedMessages")
|
||||
// (source, type, function name, JSON key, ...variadic construction arguments)
|
||||
#define MTSM_APPLICATION_STATE_FIELDS(X) \
|
||||
X(FileSource::Shared, std::unordered_set<winrt::guid>, GeneratedProfiles, "generatedProfiles") \
|
||||
X(FileSource::Local, Windows::Foundation::Collections::IVector<Model::WindowLayout>, PersistedWindowLayouts, "persistedWindowLayouts") \
|
||||
X(FileSource::Shared, Windows::Foundation::Collections::IVector<hstring>, RecentCommands, "recentCommands") \
|
||||
X(FileSource::Shared, Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage>, DismissedMessages, "dismissedMessages") \
|
||||
X(FileSource::Local, Windows::Foundation::Collections::IVector<hstring>, AllowedCommandlines, "allowedCommandlines")
|
||||
|
||||
struct WindowLayout : WindowLayoutT<WindowLayout>
|
||||
{
|
||||
@@ -44,22 +53,24 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
friend ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait<Model::WindowLayout>;
|
||||
};
|
||||
|
||||
struct ApplicationState : ApplicationStateT<ApplicationState>
|
||||
struct ApplicationState : public ApplicationStateT<ApplicationState>
|
||||
{
|
||||
static Microsoft::Terminal::Settings::Model::ApplicationState SharedInstance();
|
||||
|
||||
ApplicationState(std::filesystem::path path) noexcept;
|
||||
ApplicationState(const std::filesystem::path& stateRoot) noexcept;
|
||||
~ApplicationState();
|
||||
|
||||
// Methods
|
||||
void Reload() const noexcept;
|
||||
void FromJson(const Json::Value& root, FileSource parseSource) const noexcept;
|
||||
Json::Value ToJson(FileSource parseSource) const noexcept;
|
||||
|
||||
// General getters/setters
|
||||
winrt::hstring FilePath() const noexcept;
|
||||
bool IsStatePath(const winrt::hstring& filename);
|
||||
|
||||
// State getters/setters
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \
|
||||
type name() const noexcept; \
|
||||
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \
|
||||
type name() const noexcept; \
|
||||
void name(const type& value) noexcept;
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
@@ -67,21 +78,25 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
private:
|
||||
struct state_t
|
||||
{
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \
|
||||
std::optional<type> name{ __VA_ARGS__ }; \
|
||||
bool name##Changed = false;
|
||||
|
||||
#define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) std::optional<type> name{ __VA_ARGS__ };
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
};
|
||||
til::shared_mutex<state_t> _state;
|
||||
std::filesystem::path _sharedPath;
|
||||
std::filesystem::path _userPath;
|
||||
std::filesystem::path _elevatedPath;
|
||||
til::throttled_func_trailing<> _throttler;
|
||||
|
||||
Json::Value _getRoot(const winrt::Microsoft::Terminal::Settings::Model::locked_hfile& file) const noexcept;
|
||||
void _write() noexcept;
|
||||
void _write() const noexcept;
|
||||
void _read() const noexcept;
|
||||
|
||||
std::filesystem::path _path;
|
||||
til::shared_mutex<state_t> _state;
|
||||
til::throttled_func_trailing<> _throttler;
|
||||
Json::Value _toJsonWithBlob(Json::Value& root, FileSource parseSource) const noexcept;
|
||||
|
||||
std::optional<std::string> _readSharedContents() const;
|
||||
void _writeSharedContents(const std::string_view content) const;
|
||||
std::optional<std::string> _readLocalContents() const;
|
||||
void _writeLocalContents(const std::string_view content) const;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -28,12 +28,15 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
|
||||
void Reload();
|
||||
|
||||
String FilePath { get; };
|
||||
Boolean IsStatePath(String filename);
|
||||
|
||||
Windows.Foundation.Collections.IVector<WindowLayout> PersistedWindowLayouts { get; set; };
|
||||
|
||||
Windows.Foundation.Collections.IVector<String> RecentCommands { get; set; };
|
||||
|
||||
Windows.Foundation.Collections.IVector<InfoBarMessage> DismissedMessages { get; set; };
|
||||
|
||||
Windows.Foundation.Collections.IVector<String> AllowedCommandlines { get; set; };
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <shlobj.h>
|
||||
#include <WtExeUtils.h>
|
||||
|
||||
#include <aclapi.h>
|
||||
|
||||
static constexpr std::string_view Utf8Bom{ u8"\uFEFF" };
|
||||
static constexpr std::wstring_view UnpackagedSettingsFolderName{ L"Microsoft\\Windows Terminal\\" };
|
||||
|
||||
@@ -39,87 +41,89 @@ namespace winrt::Microsoft::Terminal::Settings::Model
|
||||
return baseSettingsPath;
|
||||
}
|
||||
|
||||
locked_hfile OpenFileReadSharedLocked(const std::filesystem::path& path)
|
||||
static bool _hasExpectedPermissions(const std::filesystem::path& path)
|
||||
{
|
||||
wil::unique_hfile file{ CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr) };
|
||||
THROW_LAST_ERROR_IF(!file);
|
||||
// just lock the entire file
|
||||
OVERLAPPED sOverlapped;
|
||||
sOverlapped.Offset = 0;
|
||||
sOverlapped.OffsetHigh = 0;
|
||||
// Shared lock
|
||||
THROW_LAST_ERROR_IF(!LockFileEx(file.get(),
|
||||
0, // lock shared, wait to return until lock is obtained
|
||||
0, // reserved, does nothing
|
||||
INT_MAX, // lock INT_MAX bytes
|
||||
0, // higher-order bytes, if our state file is greater than 2GB I guess this will be a problem
|
||||
&sOverlapped));
|
||||
return { std::move(file), sOverlapped };
|
||||
}
|
||||
// If we want to only open the file if it's elevated, check the
|
||||
// permissions on this file. We want to make sure that:
|
||||
// * Everyone has permission to read
|
||||
// * admins can do anything
|
||||
// * no one else can do anything.
|
||||
PACL pAcl{ nullptr }; // This doesn't need to be cleanup up apparently
|
||||
|
||||
locked_hfile OpenFileRWExclusiveLocked(const std::filesystem::path& path)
|
||||
{
|
||||
wil::unique_hfile file{ CreateFileW(path.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) };
|
||||
THROW_LAST_ERROR_IF(!file);
|
||||
// just lock the entire file
|
||||
OVERLAPPED sOverlapped;
|
||||
sOverlapped.Offset = 0;
|
||||
sOverlapped.OffsetHigh = 0;
|
||||
// Shared lock
|
||||
THROW_LAST_ERROR_IF(!LockFileEx(file.get(),
|
||||
LOCKFILE_EXCLUSIVE_LOCK, // lock exclusive, wait to return until lock is obtained
|
||||
0, // reserved, does nothing
|
||||
INT_MAX, // lock INT_MAX bytes
|
||||
0, // higher-order bytes, if our state file is greater than 2GB I guess this will be a problem
|
||||
&sOverlapped));
|
||||
return { std::move(file), sOverlapped };
|
||||
}
|
||||
auto status = GetNamedSecurityInfo(path.c_str(),
|
||||
SE_FILE_OBJECT,
|
||||
DACL_SECURITY_INFORMATION,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&pAcl,
|
||||
nullptr,
|
||||
nullptr);
|
||||
THROW_IF_WIN32_ERROR(status);
|
||||
|
||||
std::string ReadUTF8FileLocked(const locked_hfile& file)
|
||||
{
|
||||
const auto fileSize = GetFileSize(file.get(), nullptr);
|
||||
THROW_LAST_ERROR_IF(fileSize == INVALID_FILE_SIZE);
|
||||
PEXPLICIT_ACCESS pEA{ nullptr };
|
||||
DWORD count = 0;
|
||||
status = GetExplicitEntriesFromAcl(pAcl, &count, &pEA);
|
||||
THROW_IF_WIN32_ERROR(status);
|
||||
|
||||
// By making our buffer just slightly larger we can detect if
|
||||
// the file size changed and we've failed to read the full file.
|
||||
std::string buffer(static_cast<size_t>(fileSize) + 1, '\0');
|
||||
DWORD bytesRead = 0;
|
||||
THROW_IF_WIN32_BOOL_FALSE(ReadFile(file.get(), buffer.data(), gsl::narrow<DWORD>(buffer.size()), &bytesRead, nullptr));
|
||||
auto explicitAccessCleanup = wil::scope_exit([&]() { ::LocalFree(pEA); });
|
||||
|
||||
// As mentioned before our buffer was allocated oversized.
|
||||
buffer.resize(bytesRead);
|
||||
|
||||
if (til::starts_with(buffer, Utf8Bom))
|
||||
if (count != 2)
|
||||
{
|
||||
// Yeah this memmove()s the entire content.
|
||||
// But I don't really want to deal with UTF8 BOMs any more than necessary,
|
||||
// as basically not a single editor writes a BOM for UTF8.
|
||||
buffer.erase(0, Utf8Bom.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
// Now, get the Everyone and Admins SIDS so we can make sure they're
|
||||
// the ones in this file.
|
||||
|
||||
wil::unique_sid everyoneSid;
|
||||
wil::unique_sid adminGroupSid;
|
||||
SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;
|
||||
SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
|
||||
|
||||
// Create a SID for the BUILTIN\Administrators group.
|
||||
THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminGroupSid));
|
||||
|
||||
// Create a well-known SID for the Everyone group.
|
||||
THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &everyoneSid));
|
||||
|
||||
bool hadExpectedPermissions = true;
|
||||
|
||||
// Check that the permissions are what we'd expect them to be if only
|
||||
// admins can write to the file. This is basically a mirror of what we
|
||||
// set up in `WriteUTF8File`.
|
||||
|
||||
// For grfAccessPermissions, GENERIC_ALL turns into STANDARD_RIGHTS_ALL,
|
||||
// and GENERIC_READ -> READ_CONTROL
|
||||
hadExpectedPermissions &= WI_AreAllFlagsSet(pEA[0].grfAccessPermissions, STANDARD_RIGHTS_ALL);
|
||||
hadExpectedPermissions &= pEA[0].grfInheritance == NO_INHERITANCE;
|
||||
hadExpectedPermissions &= pEA[0].Trustee.TrusteeForm == TRUSTEE_IS_SID;
|
||||
// SIDs are void*'s that happen to convert to a wchar_t
|
||||
hadExpectedPermissions &= *(pEA[0].Trustee.ptstrName) == *(LPWSTR)(adminGroupSid.get());
|
||||
|
||||
// Now check the other EXPLICIT_ACCESS
|
||||
hadExpectedPermissions &= WI_IsFlagSet(pEA[1].grfAccessPermissions, READ_CONTROL);
|
||||
hadExpectedPermissions &= pEA[1].grfInheritance == NO_INHERITANCE;
|
||||
hadExpectedPermissions &= pEA[1].Trustee.TrusteeForm == TRUSTEE_IS_SID;
|
||||
hadExpectedPermissions &= *(pEA[1].Trustee.ptstrName) == *(LPWSTR)(everyoneSid.get());
|
||||
|
||||
return hadExpectedPermissions;
|
||||
}
|
||||
|
||||
void WriteUTF8FileLocked(const locked_hfile& file, const std::string_view& content)
|
||||
{
|
||||
// truncate the file because we want to overwrite it
|
||||
SetFilePointer(file.get(), 0, nullptr, FILE_BEGIN);
|
||||
THROW_IF_WIN32_BOOL_FALSE(SetEndOfFile(file.get()));
|
||||
|
||||
const auto fileSize = gsl::narrow<DWORD>(content.size());
|
||||
DWORD bytesWritten = 0;
|
||||
THROW_IF_WIN32_BOOL_FALSE(WriteFile(file.get(), content.data(), fileSize, &bytesWritten, nullptr));
|
||||
|
||||
if (bytesWritten != fileSize)
|
||||
{
|
||||
THROW_WIN32_MSG(ERROR_WRITE_FAULT, "failed to write whole file");
|
||||
}
|
||||
}
|
||||
|
||||
// Tries to read a file somewhat atomically without locking it.
|
||||
// Strips the UTF8 BOM if it exists.
|
||||
std::string ReadUTF8File(const std::filesystem::path& path)
|
||||
std::string ReadUTF8File(const std::filesystem::path& path, const bool elevatedOnly)
|
||||
{
|
||||
if (elevatedOnly)
|
||||
{
|
||||
const bool hadExpectedPermissions{ _hasExpectedPermissions(path) };
|
||||
if (!hadExpectedPermissions)
|
||||
{
|
||||
// delete the file. It's been compromised.
|
||||
LOG_LAST_ERROR_IF(!DeleteFile(path.c_str()));
|
||||
// Exit early, because obviously there's nothing to read from the deleted file.
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// From some casual observations we can determine that:
|
||||
// * ReadFile() always returns the requested amount of data (unless the file is smaller)
|
||||
// * It's unlikely that the file was changed between GetFileSize() and ReadFile()
|
||||
@@ -166,11 +170,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model
|
||||
}
|
||||
|
||||
// Same as ReadUTF8File, but returns an empty optional, if the file couldn't be opened.
|
||||
std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path)
|
||||
std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path, const bool elevatedOnly)
|
||||
{
|
||||
try
|
||||
{
|
||||
return { ReadUTF8File(path) };
|
||||
return { ReadUTF8File(path, elevatedOnly) };
|
||||
}
|
||||
catch (const wil::ResultException& exception)
|
||||
{
|
||||
@@ -183,9 +187,75 @@ namespace winrt::Microsoft::Terminal::Settings::Model
|
||||
}
|
||||
}
|
||||
|
||||
void WriteUTF8File(const std::filesystem::path& path, const std::string_view& content)
|
||||
void WriteUTF8File(const std::filesystem::path& path,
|
||||
const std::string_view& content,
|
||||
const bool elevatedOnly)
|
||||
{
|
||||
wil::unique_hfile file{ CreateFileW(path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) };
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
if (elevatedOnly)
|
||||
{
|
||||
// This is very vaguely taken from
|
||||
// https://docs.microsoft.com/en-us/windows/win32/secauthz/creating-a-security-descriptor-for-a-new-object-in-c--
|
||||
// With using https://docs.microsoft.com/en-us/windows/win32/secauthz/well-known-sids
|
||||
// to find out that
|
||||
// * SECURITY_NT_AUTHORITY+SECURITY_LOCAL_SYSTEM_RID == NT AUTHORITY\SYSTEM
|
||||
// * SECURITY_NT_AUTHORITY+SECURITY_BUILTIN_DOMAIN_RID+DOMAIN_ALIAS_RID_ADMINS == BUILTIN\Administrators
|
||||
// * SECURITY_WORLD_SID_AUTHORITY+SECURITY_WORLD_RID == Everyone
|
||||
//
|
||||
// Raymond Chen recommended that I make this file only writable by
|
||||
// SYSTEM, but if I did that, then even we can't write the file
|
||||
// while elevated, which isn't what we want.
|
||||
|
||||
wil::unique_sid everyoneSid;
|
||||
wil::unique_sid adminGroupSid;
|
||||
SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;
|
||||
SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
|
||||
|
||||
// Create a SID for the BUILTIN\Administrators group.
|
||||
THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminGroupSid));
|
||||
|
||||
// Create a well-known SID for the Everyone group.
|
||||
THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &everyoneSid));
|
||||
|
||||
EXPLICIT_ACCESS ea[2]{};
|
||||
|
||||
// Grant Admins all permissions on this file
|
||||
ea[0].grfAccessPermissions = GENERIC_ALL;
|
||||
ea[0].grfAccessMode = SET_ACCESS;
|
||||
ea[0].grfInheritance = NO_INHERITANCE;
|
||||
ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
|
||||
ea[0].Trustee.ptstrName = (LPWSTR)(adminGroupSid.get());
|
||||
|
||||
// Grant Everyone the permission or read this file
|
||||
ea[1].grfAccessPermissions = GENERIC_READ;
|
||||
ea[1].grfAccessMode = SET_ACCESS;
|
||||
ea[1].grfInheritance = NO_INHERITANCE;
|
||||
ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
ea[1].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
|
||||
ea[1].Trustee.ptstrName = (LPWSTR)(everyoneSid.get());
|
||||
|
||||
ACL acl;
|
||||
PACL pAcl = &acl;
|
||||
THROW_IF_WIN32_ERROR(SetEntriesInAcl(2, ea, nullptr, &pAcl));
|
||||
|
||||
SECURITY_DESCRIPTOR sd;
|
||||
THROW_IF_WIN32_BOOL_FALSE(InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION));
|
||||
THROW_IF_WIN32_BOOL_FALSE(SetSecurityDescriptorDacl(&sd, true, pAcl, false));
|
||||
|
||||
// Initialize a security attributes structure.
|
||||
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
sa.lpSecurityDescriptor = &sd;
|
||||
sa.bInheritHandle = false;
|
||||
}
|
||||
|
||||
wil::unique_hfile file{ CreateFileW(path.c_str(),
|
||||
GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
elevatedOnly ? &sa : nullptr,
|
||||
CREATE_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
nullptr) };
|
||||
THROW_LAST_ERROR_IF(!file);
|
||||
|
||||
const auto fileSize = gsl::narrow<DWORD>(content.size());
|
||||
@@ -198,7 +268,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model
|
||||
}
|
||||
}
|
||||
|
||||
void WriteUTF8FileAtomic(const std::filesystem::path& path, const std::string_view& content)
|
||||
void WriteUTF8FileAtomic(const std::filesystem::path& path,
|
||||
const std::string_view& content)
|
||||
{
|
||||
// GH#10787: rename() will replace symbolic links themselves and not the path they point at.
|
||||
// It's thus important that we first resolve them before generating temporary path.
|
||||
|
||||
@@ -1,41 +1,11 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model
|
||||
{
|
||||
// I couldn't find a wil helper for this so I made it myself
|
||||
class locked_hfile
|
||||
{
|
||||
public:
|
||||
wil::unique_hfile file;
|
||||
OVERLAPPED lockedRegion;
|
||||
|
||||
~locked_hfile()
|
||||
{
|
||||
if (file)
|
||||
{
|
||||
// Need to unlock the file before it is closed
|
||||
UnlockFileEx(file.get(), 0, INT_MAX, 0, &lockedRegion);
|
||||
}
|
||||
}
|
||||
|
||||
HANDLE get() const noexcept
|
||||
{
|
||||
return file.get();
|
||||
}
|
||||
};
|
||||
|
||||
std::filesystem::path GetBaseSettingsPath();
|
||||
|
||||
locked_hfile OpenFileReadSharedLocked(const std::filesystem::path& path);
|
||||
locked_hfile OpenFileRWExclusiveLocked(const std::filesystem::path& path);
|
||||
std::string ReadUTF8FileLocked(const locked_hfile& file);
|
||||
void WriteUTF8FileLocked(const locked_hfile& file, const std::string_view& content);
|
||||
|
||||
std::string ReadUTF8File(const std::filesystem::path& path);
|
||||
std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path);
|
||||
void WriteUTF8File(const std::filesystem::path& path, const std::string_view& content);
|
||||
std::string ReadUTF8File(const std::filesystem::path& path, const bool elevatedOnly = false);
|
||||
std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path, const bool elevatedOnly = false);
|
||||
void WriteUTF8File(const std::filesystem::path& path, const std::string_view& content, const bool elevatedOnly = false);
|
||||
void WriteUTF8FileAtomic(const std::filesystem::path& path, const std::string_view& content);
|
||||
}
|
||||
|
||||
@@ -382,7 +382,6 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
||||
struct ConversionTrait<std::unordered_map<std::string, T>>
|
||||
{
|
||||
std::unordered_map<std::string, T> FromJson(const Json::Value& json) const
|
||||
|
||||
@@ -37,7 +37,12 @@ std::wstring_view WslDistroGenerator::GetNamespace() const noexcept
|
||||
static winrt::com_ptr<implementation::Profile> makeProfile(const std::wstring& distName)
|
||||
{
|
||||
const auto WSLDistro{ CreateDynamicProfile(distName) };
|
||||
WSLDistro->Commandline(winrt::hstring{ L"wsl.exe -d " + distName });
|
||||
// GH#11096 - make sure the WSL path starts explicitly with
|
||||
// C:\Windows\System32. Don't want someone path hijacking wsl.exe.
|
||||
wil::unique_cotaskmem_string systemPath;
|
||||
THROW_IF_FAILED(wil::GetSystemDirectoryW(systemPath));
|
||||
std::wstring command(systemPath.get());
|
||||
WSLDistro->Commandline(winrt::hstring{ command + L"\\wsl.exe -d " + distName });
|
||||
WSLDistro->DefaultAppearance().ColorSchemeName(L"Campbell");
|
||||
WSLDistro->StartingDirectory(winrt::hstring{ DEFAULT_STARTING_DIRECTORY });
|
||||
WSLDistro->Icon(L"ms-appx:///ProfileIcons/{9acb9455-ca41-5af7-950f-6bca1bc9722f}.png");
|
||||
|
||||
@@ -53,3 +53,6 @@ TRACELOGGING_DECLARE_PROVIDER(g_hSettingsModelProvider);
|
||||
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#include "til.h"
|
||||
|
||||
#include <til/mutex.h>
|
||||
#include <til/throttled_func.h>
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
{
|
||||
"guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
|
||||
"name": "Windows PowerShell",
|
||||
"commandline": "powershell.exe",
|
||||
"commandline": "%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
|
||||
"hidden": false
|
||||
},
|
||||
{
|
||||
// "name" is filled in by CascadiaSettings as a localized string.
|
||||
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
|
||||
"commandline": "cmd.exe",
|
||||
"commandline": "%SystemRoot%\\System32\\cmd.exe",
|
||||
"hidden": false
|
||||
}
|
||||
]
|
||||
|
||||
@@ -819,8 +819,30 @@ winrt::Windows::Foundation::IAsyncAction AppHost::_SaveWindowLayouts()
|
||||
|
||||
if (_logic.ShouldUsePersistedLayout())
|
||||
{
|
||||
const auto layoutJsons = _windowManager.GetAllWindowLayouts();
|
||||
_logic.SaveWindowLayoutJsons(layoutJsons);
|
||||
try
|
||||
{
|
||||
TraceLoggingWrite(g_hWindowsTerminalProvider,
|
||||
"AppHost_SaveWindowLayouts_Collect",
|
||||
TraceLoggingDescription("Logged when collecting window state"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
const auto layoutJsons = _windowManager.GetAllWindowLayouts();
|
||||
TraceLoggingWrite(g_hWindowsTerminalProvider,
|
||||
"AppHost_SaveWindowLayouts_Save",
|
||||
TraceLoggingDescription("Logged when writing window state"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
_logic.SaveWindowLayoutJsons(layoutJsons);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
TraceLoggingWrite(g_hWindowsTerminalProvider,
|
||||
"AppHost_SaveWindowLayouts_Failed",
|
||||
TraceLoggingDescription("An error occured when collecting or writing window state"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
}
|
||||
|
||||
co_return;
|
||||
@@ -841,6 +863,12 @@ winrt::fire_and_forget AppHost::_SaveWindowLayoutsRepeat()
|
||||
// per 10 seconds, if a save is requested by another source simultaneously.
|
||||
if (_getWindowLayoutThrottler.has_value())
|
||||
{
|
||||
TraceLoggingWrite(g_hWindowsTerminalProvider,
|
||||
"AppHost_requestGetLayout",
|
||||
TraceLoggingDescription("Logged when triggering a throttled write of the window state"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
_getWindowLayoutThrottler.value()();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,4 +94,5 @@ namespace Microsoft::Console::Utils
|
||||
|
||||
GUID CreateV5Uuid(const GUID& namespaceGuid, const gsl::span<const gsl::byte> name);
|
||||
|
||||
bool IsElevated();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "inc/utils.hpp"
|
||||
#include "inc/colorTable.hpp"
|
||||
|
||||
#include <wil/token_helpers.h >
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
|
||||
// Routine Description:
|
||||
@@ -559,3 +561,33 @@ GUID Utils::CreateV5Uuid(const GUID& namespaceGuid, const gsl::span<const gsl::b
|
||||
::memcpy_s(&newGuid, sizeof(GUID), buffer.data(), sizeof(GUID));
|
||||
return EndianSwap(newGuid);
|
||||
}
|
||||
|
||||
bool Utils::IsElevated()
|
||||
{
|
||||
static bool isElevated = []() {
|
||||
try
|
||||
{
|
||||
wil::unique_handle processToken{ GetCurrentProcessToken() };
|
||||
const auto elevationType = wil::get_token_information<TOKEN_ELEVATION_TYPE>(processToken.get());
|
||||
const auto elevationState = wil::get_token_information<TOKEN_ELEVATION>(processToken.get());
|
||||
if (elevationType == TokenElevationTypeDefault && elevationState.TokenIsElevated)
|
||||
{
|
||||
// In this case, the user has UAC entirely disabled. This is sort of
|
||||
// weird, we treat this like the user isn't an admin at all. There's no
|
||||
// separation of powers, so the things we normally want to gate on
|
||||
// "having special powers" doesn't apply.
|
||||
//
|
||||
// See GH#7754, GH#11096
|
||||
return false;
|
||||
}
|
||||
|
||||
return wil::test_token_membership(nullptr, SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
return false;
|
||||
}
|
||||
}();
|
||||
return isElevated;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user