diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index e56b4d46c55..4479fcd4875 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "XamlStyler.Console": { - "version": "3.2206.4", + "version": "3.2311.2", "commands": [ "xstyler" ] diff --git a/.github/actions/spelling/advice.md b/.github/actions/spelling/advice.md index d82df49ee22..536c601aec2 100644 --- a/.github/actions/spelling/advice.md +++ b/.github/actions/spelling/advice.md @@ -6,8 +6,6 @@ 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. 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: * ... **misspelled**, then please *correct* them instead of using the command. @@ -36,7 +34,9 @@ https://www.regexplanet.com/advanced/perl/) yours before committing to verify it * well-formed pattern. - If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it, + 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]( diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt index 7bff8f0cfa0..73b296e6b86 100644 --- a/.github/actions/spelling/allow/allow.txt +++ b/.github/actions/spelling/allow/allow.txt @@ -1,3 +1,4 @@ +aci admins allcolors Apc @@ -8,6 +9,7 @@ breadcrumbs bsd calt ccmp +ccon changelog clickable clig @@ -15,6 +17,7 @@ CMMI copyable Counterintuitively CtrlDToClose +CUI cybersecurity dalet Dcs @@ -53,6 +56,7 @@ hyperlinks iconify img inlined +issuetitle It'd kje libfuzzer @@ -77,11 +81,13 @@ noreply ogonek ok'd overlined +perlw pipeline postmodern Powerline powerline ptys +pwshw qof qps rclt @@ -90,8 +96,11 @@ reserialization reserialize reserializes rlig +rubyw runtimes +servicebus shcha +similaritytolerance slnt Sos ssh @@ -108,6 +117,7 @@ toolset truthiness tshe ubuntu +UEFI uiatextrange UIs und @@ -117,7 +127,9 @@ vsdevcmd walkthrough walkthroughs We'd +westus wildcards +workarounds XBox YBox yeru diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 4ce5fe88d6d..9f5b92bc337 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -4,6 +4,7 @@ acl aclapi alignas alignof +allocconsolewithoptions APPLYTOSUBMENUS appxrecipe bitfield @@ -156,6 +157,7 @@ OUTLINETEXTMETRICW overridable PACL PAGESCROLL +PALLOC PATINVERT PEXPLICIT PICKFOLDERS @@ -210,6 +212,7 @@ tlg TME tmp tmpdir +tokeninfo tolower toupper TRACKMOUSEEVENT diff --git a/.github/actions/spelling/allow/microsoft.txt b/.github/actions/spelling/allow/microsoft.txt index 1ed1c6da897..d14b0a75e3a 100644 --- a/.github/actions/spelling/allow/microsoft.txt +++ b/.github/actions/spelling/allow/microsoft.txt @@ -20,6 +20,7 @@ cpptools cppvsdbg CPRs cryptbase +cscript DACL DACLs defaultlib @@ -88,8 +89,10 @@ Virtualization visualstudio vscode VSTHRD +WINBASEAPI winsdkver wlk +wscript wslpath wtl wtt diff --git a/.github/actions/spelling/allow/names.txt b/.github/actions/spelling/allow/names.txt index 0409931d020..11a1f95486b 100644 --- a/.github/actions/spelling/allow/names.txt +++ b/.github/actions/spelling/allow/names.txt @@ -6,6 +6,7 @@ bhoj Bhojwani Bluloco carlos +craigloewen dhowett Diviness dsafa diff --git a/.github/actions/spelling/candidate.patterns b/.github/actions/spelling/candidate.patterns index 400f103ea28..80f31f92e4f 100644 --- a/.github/actions/spelling/candidate.patterns +++ b/.github/actions/spelling/candidate.patterns @@ -1,23 +1,37 @@ # 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.*$ +# marker to ignore all code on line +^.*\bno-spell-check(?:-line|)(?:\s.*|)$ + +# https://cspell.org/configuration/document-settings/ +# cspell inline +^.*\b[Cc][Ss][Pp][Ee][Ll]{2}:\s*[Dd][Ii][Ss][Aa][Bb][Ll][Ee]-[Ll][Ii][Nn][Ee]\b # patch hunk comments ^\@\@ -\d+(?:,\d+|) \+\d+(?:,\d+|) \@\@ .* # git index header -index [0-9a-z]{7,40}\.\.[0-9a-z]{7,40} +index (?:[0-9a-z]{7,40},|)[0-9a-z]{7,40}\.\.[0-9a-z]{7,40} + +# file permissions +['"`\s][-bcdLlpsw](?:[-r][-w][-Ssx]){2}[-r][-w][-SsTtx]\+?['"`\s] + +# css url wrappings +\burl\([^)]+\) # cid urls (['"])cid:.*?\g{-1} # data url in parens -\(data:[^)]*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})[^)]*\) +#\(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:(?:[^ `'"].*?|)(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1} # data url data:[-a-zA-Z=;:/0-9+]*,\S* +# https/http/file urls +(?:\b(?:https?|ftp|file)://)[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|] + # mailto urls mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,} @@ -35,6 +49,9 @@ magnet:[?=:\w]+ # asciinema \basciinema\.org/a/[0-9a-zA-Z]+ +# asciinema v2 +^\[\d+\.\d+, "[io]", ".*"\]$ + # apple \bdeveloper\.apple\.com/[-\w?=/]+ # Apple music @@ -89,7 +106,7 @@ vpc-\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]+)* +\bgroups\.google\.com(?:/[a-z]+/(?:#!|)[^/\s"]+)* # Google Maps \bmaps\.google\.com/maps\?[\w&;=]* # Google themes @@ -117,6 +134,8 @@ themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+. (?:\[`?[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 SHA refs +\[([0-9a-f]+)\]\(https://(?:www\.|)github.com/[-\w]+/[-\w]+/commit/\g{-1}[0-9a-f]* # GitHub wiki \bgithub\.com/(?:[^/]+/){2}wiki/(?:(?:[^/]+/|)_history|[^/]+(?:/_compare|)/[0-9a-f.]{40,})\b # githubusercontent @@ -128,9 +147,9 @@ themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+. # git.io \bgit\.io/[0-9a-zA-Z]+ # GitHub JSON -"node_id": "[-a-zA-Z=;:/0-9+]*" +"node_id": "[-a-zA-Z=;:/0-9+_]*" # Contributor -\[[^\]]+\]\(https://github\.com/[^/\s"]+\) +\[[^\]]+\]\(https://github\.com/[^/\s"]+/?\) # GHSA GHSA(?:-[0-9a-z]{4}){3} @@ -143,8 +162,8 @@ GHSA(?:-[0-9a-z]{4}){3} # GitLab commits \bgitlab\.[^/\s"]*/(?:[^/\s"]+/){2}commits?/[0-9a-f]+\b -# binanace -accounts.binance.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]* +# binance +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]+ @@ -280,9 +299,9 @@ slack://[a-zA-Z0-9?&=]+ \bdropbox\.com/sh?/[^/\s"]+/[-0-9A-Za-z_.%?=&;]+ # ipfs protocol -ipfs://[0-9a-z]* +ipfs://[0-9a-zA-Z]{3,} # ipfs url -/ipfs/[0-9a-z]* +/ipfs/[0-9a-zA-Z]{3,} # w3 \bw3\.org/[-0-9a-zA-Z/#.]+ @@ -359,22 +378,33 @@ ipfs://[0-9a-z]* # tinyurl \btinyurl\.com/\w+ +# codepen +\bcodepen\.io/[\w/]+ + +# registry.npmjs.org +\bregistry\.npmjs\.org/(?:@[^/"']+/|)[^/"']+/-/[-\w@.]+ + # getopts \bgetopts\s+(?:"[^"]+"|'[^']+') # ANSI color codes -(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m +(?:\\(?:u00|x)1[Bb]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+|)m # URL escaped characters -\%[0-9A-F][A-F] +\%[0-9A-F][A-F](?=[A-Za-z]) +# lower URL escaped characters +\%[0-9a-f][a-f](?=[a-z]{2,}) # IPv6 -\b(?:[0-9a-fA-F]{0,4}:){3,7}[0-9a-fA-F]{0,4}\b +#\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} +(\\?['"]|")[0-9a-f]{40,}\g{-1} # hex runs \b[0-9a-fA-F]{16,}\b # hex in url queries @@ -389,18 +419,21 @@ sha\d+:[0-9]*[a-f]{3,}[0-9a-f]* # Well known gpg keys .well-known/openpgpkey/[\w./]+ +# pki +-----BEGIN.*-----END + # 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 +(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b # integrity -integrity="sha\d+-[-a-zA-Z=;:/0-9+]{40,}" +integrity=(['"])(?:\s*sha\d+-[-a-zA-Z=;:/0-9+]{40,})+\g{-1} # https://www.gnu.org/software/groff/manual/groff.html # man troff content \\f[BCIPR] -# ' -\\\(aq +# '/" +\\\([ad]q # .desktop mime types ^MimeTypes?=.*$ @@ -409,21 +442,33 @@ integrity="sha\d+-[-a-zA-Z=;:/0-9+]{40,}" # Localized .desktop content Name\[[^\]]+\]=.* -# IServiceProvider -\bI(?=(?:[A-Z][a-z]{2,})+\b) +# IServiceProvider / isAThing +\b(?:I|isA)(?=(?:[A-Z][a-z]{2,})+\b) # crypt -"\$2[ayb]\$.{56}" +(['"])\$2[ayb]\$.{56}\g{-1} # scrypt / argon \$(?:scrypt|argon\d+[di]*)\$\S+ +# go.sum +\bh1:\S+ + +# scala modules +("[^"]+"\s*%%?\s*){2,3}"[^"]+" + # Input to GitHub JSON -content: "[-a-zA-Z=;:/0-9+]*=" +content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1} -# Python stringprefix / binaryprefix +# This does not cover multiline strings, if your repository has them, +# you'll want to remove the `(?=.*?")` suffix. +# The `(?=.*?")` suffix should limit the false positives rate +# printf +#%(?:(?:(?:hh?|ll?|[jzt])?[diuoxn]|l?[cs]|L?[fega]|p)(?=[a-z]{2,})|(?:X|L?[FEGA]|p)(?=[a-zA-Z]{2,}))(?=[_a-zA-Z]+\b)(?!%)(?=.*?['"]) + +# Python string prefix / binary prefix # 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 -(?|m([|!/@#,;']).*?\g{-1}) + +# perl qr regex +(?|\(.*?\)|([|!/@#,;']).*?\g{-1}) # Go regular expressions regexp?\.MustCompile\(`[^`]*`\) +# regex choice +\(\?:[^)]+\|[^)]+\) + +# proto +^\s*(\w+)\s\g{-1} = + # sed regular expressions sed 's/(?:[^/]*?[a-zA-Z]{3,}[^/]*?/){2} +# node packages +(["'])\@[^/'" ]+/[^/'" ]+\g{-1} + # go install go install(?:\s+[a-z]+\.[-@\w/.]+)+ +# jetbrains schema https://youtrack.jetbrains.com/issue/RSRP-489571 +urn:shemas-jetbrains-com + # 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+ @@ -460,19 +524,47 @@ go install(?:\s+[a-z]+\.[-@\w/.]+)+ -[0-9a-f]{10}-\w{5}\s # posthog secrets -posthog\.init\((['"])phc_[^"',]+\g{-1}, +([`'"])phc_[^"',]+\g{-1} # xcode # xcodeproject scenes -(?:Controller|ID|id)="\w{3}-\w{2}-\w{3}" +(?:Controller|destination|ID|id)="\w{3}-\w{2}-\w{3}" # xcode api botches customObjectInstantitationMethod +# configure flags +.* \| --\w{2,}.*?(?=\w+\s\w+) + # font awesome classes \.fa-[-a-z0-9]+ +# bearer auth +(['"])Bear[e][r] .*?\g{-1} + +# basic auth +(['"])Basic [-a-zA-Z=;:/0-9+]{3,}\g{-1} + +# base64 encoded content +#([`'"])[-a-zA-Z=;:/0-9+]+=\g{-1} +# base64 encoded content in xml/sgml +>[-a-zA-Z=;:/0-9+]+== 0.0.22) +\\\w{2,}\{ + +# eslint +"varsIgnorePattern": ".+" + +# Windows short paths +[/\\][^/\\]{5,6}~\d{1,2}[/\\] + +# in check-spelling@v0.0.22+, printf markers aren't automatically consumed +# printf markers +#(?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 (Unix, Java/Scala) +# Use if you have things like `-Pdocker` and want to treat them as `docker` +#(?:^|[\t ,>"'`=(])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) + +# Compiler flags (Windows / PowerShell) +# This is a subset of the more general compiler flags pattern. +# It avoids matching `-Path` to prevent it from being treated as `ath` +#(?:^|[\t ,"'`=(])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[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 diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt index 441d1f295ca..57d475dc876 100644 --- a/.github/actions/spelling/excludes.txt +++ b/.github/actions/spelling/excludes.txt @@ -1,21 +1,24 @@ # 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$ +(?:^|/)Pipfile$ +(?:^|/)pyproject.toml +(?:^|/)requirements(?:-dev|-doc|-test|)\.txt$ (?:^|/)sources(?:|\.dep)$ (?:^|/)vendor/ \.a$ \.ai$ +\.all-contributorsrc$ \.avi$ \.bmp$ \.bz2$ \.cer$ \.class$ +\.coveragerc$ \.crl$ \.crt$ \.csr$ @@ -27,11 +30,15 @@ \.eps$ \.exe$ \.gif$ +\.git-blame-ignore-revs$ \.gitattributes$ +\.gitignore$ +\.gitkeep$ \.graffle$ \.gz$ \.icns$ \.ico$ +\.ipynb$ \.jar$ \.jks$ \.jpeg$ @@ -41,61 +48,62 @@ \.lock$ \.map$ \.min\.. +\.mo$ \.mod$ \.mp3$ \.mp4$ \.o$ \.ocf$ \.otf$ +\.p12$ +\.parquet$ \.pbxproj$ \.pdf$ \.pem$ +\.pfx$ \.png$ \.psd$ \.pyc$ +\.pylintrc$ +\.qm$ \.runsettings$ \.s$ \.sig$ \.so$ \.svg$ \.svgz$ -\.svgz?$ +\.sys$ \.tar$ \.tgz$ \.tiff?$ \.ttf$ +\.vcxproj\.filters$ \.vsdx$ \.wav$ \.webm$ \.webp$ \.woff -\.woff2?$ \.xcf$ -\.xls \.xlsx?$ \.xpm$ -\.yml$ +\.xz$ \.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$ +^\Qbuild/config/release.gdnbaselines\E$ ^\Qsrc/host/ft_host/chafa.txt\E$ -^\Qsrc/tools/closetest/CloseTest.vcxproj.filters\E$ -^\XamlStyler.json$ +^\Qsrc/host/ft_uia/run.bat\E$ +^\Qsrc/host/runft.bat\E$ +^\Qsrc/tools/lnkd/lnkd.bat\E$ +^\Qsrc/tools/pixels/pixels.bat\E$ ^build/config/ ^consolegit2gitfilters\.json$ ^dep/ -^doc/reference/master-sequence-list.csv$ +^doc/reference/master-sequence-list\.csv$ ^doc/reference/UTF8-torture-test\.txt$ +^doc/reference/windows-terminal-logo\.ans$ ^oss/ -^src/host/ft_uia/run\.bat$ -^src/host/runft\.bat$ -^src/host/runut\.bat$ +^samples/PixelShaders/Screenshots/ ^src/interactivity/onecore/BgfxEngine\. ^src/renderer/atlas/ ^src/renderer/wddmcon/WddmConRenderer\. @@ -107,14 +115,13 @@ ^src/terminal/parser/ut_parser/Base64Test.cpp$ ^src/terminal/parser/ut_parser/run\.bat$ ^src/tools/benchcat +^src/tools/integrity/dirs$ ^src/tools/integrity/packageuwp/ConsoleUWP\.appxSources$ -^src/tools/lnkd/lnkd\.bat$ -^src/tools/pixels/pixels\.bat$ -^src/tools/RenderingTests/main.cpp$ +^src/tools/RenderingTests/main\.cpp$ ^src/tools/texttests/fira\.txt$ -^src/tools/U8U16Test/(?:fr|ru|zh)\.txt$ -^src/types/ColorFix.cpp -^src/types/ut_types/UtilsTests.cpp$ -^tools/ReleaseEngineering/ServicingPipeline.ps1$ +^src/tools/U8U16Test/(?!en)..\. +^src/types/ColorFix\.cpp$ +^src/types/ut_types/UtilsTests\.cpp$ +^tools/ReleaseEngineering/ServicingPipeline\.ps1$ +^XamlStyler\.json$ ignore$ -SUMS$ diff --git a/.github/actions/spelling/expect/04cdb9b77d6827c0202f51acd4205b017015bfff.txt b/.github/actions/spelling/expect/04cdb9b77d6827c0202f51acd4205b017015bfff.txt new file mode 100644 index 00000000000..f117f5081da --- /dev/null +++ b/.github/actions/spelling/expect/04cdb9b77d6827c0202f51acd4205b017015bfff.txt @@ -0,0 +1,5 @@ +EOB +swrapped +wordi +wordiswrapped +wrappe diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 3ea1b7e958f..b85247338c0 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1,18 +1,16 @@ aabbcc -aarch ABANDONFONT abbcc ABCDEFGHIJKLMNOPQRSTUVWXY ABCF abgr -abi ABORTIFHUNG +ACCESSTOKEN acidev ACIOSS ACover actctx ACTCTXW -activatable ADDALIAS ADDREF ADDSTRING @@ -32,7 +30,7 @@ ALTERNATENAME ALTF ALTNUMPAD ALWAYSTIP -amd +aml ansicpg ANSISYS ANSISYSRC @@ -50,25 +48,23 @@ APPBARDATA appcontainer appium appletname -applets applicationmodel APPLMODAL appmodel APPWINDOW +APPXMANIFESTVERSION APrep apsect APSTUDIO archeologists -argb +Argb ARRAYSIZE ARROWKEYS asan ASBSET -ASDF asdfghjkl ASetting ASingle -asmx ASYNCWINDOWPOS atch ATest @@ -85,9 +81,8 @@ AUTORADIOBUTTON autoscrolling Autowrap AVerify -AVX awch -azzle +azurecr backgrounded Backgrounder backgrounding @@ -107,15 +102,16 @@ benchcat bgfx bgidx Bgk -BGR bgra BHID bigobj +binlog binplace binplaced +binskim bitcoin bitcrazed -bitmask +bitmasks BITOPERATION BKCOLOR BKGND @@ -126,10 +122,7 @@ BODGY BOLDFONT BOOLIFY bools -Bopomofo Borland -BOTTOMLEFT -BOTTOMRIGHT boutput boxheader BPBF @@ -139,7 +132,6 @@ branchconfig brandings Browsable Bspace -bstr BTNFACE bufferout buffersize @@ -162,11 +154,9 @@ cbiex CBN CBoolean cbt -cbuffer CCCBB cch CCHAR -cci CCmd ccolor CCom @@ -184,18 +174,16 @@ cfte CFuzz cgscrn chafa -changelist +changelists chaof charinfo CHARSETINFO chcbpat chh -chk chshdng CHT Cic -cielab -Cielab +CLASSSTRING CLE cleartype CLICKACTIVE @@ -205,7 +193,7 @@ CLIPCHILDREN CLIPSIBLINGS closetest cloudconsole -cls +cloudvault CLSCTX clsids CLUSTERMAP @@ -214,23 +202,19 @@ cmder CMDEXT cmh CMOUSEBUTTONS -cmt +Cmts cmw -cmyk CNL cnn -cnt -CNTRL Codeflow -codepage +codenav +codepages codepath -codepoints coinit colorizing COLORMATRIX COLORREFs colorschemes -colorspaces colorspec colortable colortbl @@ -239,7 +223,6 @@ colortool COLR combaseapi comctl -COMDAT commandline commctrl commdlg @@ -256,7 +239,6 @@ conddkrefs condrv conechokey conemu -configurability conhost conime conimeinfo @@ -313,16 +295,14 @@ CPPCORECHECK cppcorecheckrules cpprestsdk cppwinrt -CProc cpx CREATESCREENBUFFER CREATESTRUCT CREATESTRUCTW -cred +createvpack crisman CRLFs crloew -Crt CRTLIBS csbi csbiex @@ -340,6 +320,7 @@ ctlseqs CTRLEVENT CTRLFREQUENCY CTRLKEYSHORTCUTS +Ctrls CTRLVOLUME Ctxt CUF @@ -378,7 +359,6 @@ DATABLOCK DBatch dbcs DBCSFONT -dbg DBGALL DBGCHARS DBGFONTS @@ -423,6 +403,7 @@ DECECM DECEKBD DECERA DECFI +DECFNK DECFRA DECIC DECID @@ -465,6 +446,7 @@ DECSLPP DECSLRM DECSMKR DECSR +DECST DECSTBM DECSTGLT DECSTR @@ -503,17 +485,17 @@ directio DIRECTX DISABLEDELAYEDEXPANSION DISABLENOSCROLL +DISPATCHNOTIFY DISPLAYATTRIBUTE DISPLAYATTRIBUTEPROPERTY DISPLAYCHANGE -distro +distros dlg DLGC DLLGETVERSIONPROC dllinit dllmain DLLVERSIONINFO -DLOAD DLOOK DONTCARE doskey @@ -563,24 +545,20 @@ ECH echokey ecount ECpp -ect Edgium EDITKEYS EDITTEXT EDITUPDATE -edputil Efast efghijklmn EHsc EINS EJO ELEMENTNOTAVAILABLE -elems -emacs EMPTYBOX enabledelayedexpansion +ENDCAP endptr -endregion ENTIREBUFFER entrypoints ENU @@ -588,10 +566,12 @@ ENUMLOGFONT ENUMLOGFONTEX enumranges EOK -eplace EPres EQU ERASEBKGND +ESFCIB +esrp +ESV ETW EUDC EVENTID @@ -605,7 +585,6 @@ EXETYPE exeuwp exewin exitwin -expectedinput EXPUNGECOMMANDHISTORY EXSTYLE EXTENDEDEDITKEY @@ -646,7 +625,7 @@ FIter FIXEDCONVERTED FIXEDFILEINFO Flg -flyout +flyouts fmodern fmtarg fmtid @@ -656,27 +635,20 @@ fontdlg FONTENUMDATA FONTENUMPROC FONTFACE -FONTFAMILY FONTHEIGHT fontinfo FONTOK -FONTSIZE FONTSTRING -fonttbl FONTTYPE -FONTWEIGHT FONTWIDTH FONTWINDOW -fooo FORCEOFFFEEDBACK FORCEONFEEDBACK -framebuffer FRAMECHANGED fre frontends fsanitize Fscreen -FSCTL FSINFOCLASS fte Ftm @@ -690,12 +662,12 @@ fuzzwrapper fwdecl fwe fwlink -GAUSSIAN gci gcx gdi gdip gdirenderer +gdnbaselines Geddy geopol GETALIAS @@ -705,7 +677,6 @@ GETALIASEXES GETALIASEXESLENGTH GETAUTOHIDEBAREX GETCARETWIDTH -getch GETCLIENTAREAANIMATION GETCOMMANDHISTORY GETCOMMANDHISTORYLENGTH @@ -721,7 +692,6 @@ GETDISPLAYSIZE GETDLGCODE GETDPISCALEDSIZE GETFONTINFO -GETFONTSIZE GETHARDWARESTATE GETHUNGAPPTIMEOUT GETICON @@ -736,7 +706,6 @@ GETMOUSEVANISH GETNUMBEROFFONTS GETNUMBEROFINPUTEVENTS GETOBJECT -GETPOS GETSELECTIONINFO getset GETTEXTLEN @@ -752,13 +721,14 @@ gfx GGI GHIJK GHIJKL +gitcheckin gitfilters +gitlab gitmodules gle GLOBALFOCUS GLYPHENTRY GMEM -GNUC Goldmine gonce goutput @@ -766,11 +736,9 @@ GREENSCROLL Grehan Greyscale gridline -groupbox gset gsl GTP -GTR guc guidatom GValue @@ -821,20 +789,18 @@ hmod hmodule hmon homoglyph -HORZ hostable hostlib +Hostx HPA hpcon HPCON -hpj +hpen HPR HProvider HREDRAW hresult -hrottled hscroll -hsl hstr hstring HTBOTTOMLEFT @@ -844,7 +810,6 @@ HTCLIENT HTLEFT HTMAXBUTTON HTMINBUTTON -HTMLTo HTRIGHT HTTOP HTTOPLEFT @@ -855,9 +820,7 @@ hwheel hwnd HWNDPARENT iccex -icket ICONERROR -Iconified ICONINFORMATION IConsole ICONSTOP @@ -870,7 +833,6 @@ idllib IDOK IDR idth -idx IDXGI IEnd IEnum @@ -882,14 +844,12 @@ IHosted iid IIo ime -Imm IMPEXP inbox inclusivity INCONTEXT INFOEX inheritcursor -inheritdoc inheritfrom INITCOMMONCONTROLSEX INITDIALOG @@ -897,7 +857,6 @@ initguid INITMENU inkscape INLINEPREFIX -inlines inproc Inputkeyinfo INPUTPROCESSORPROFILE @@ -911,7 +870,7 @@ Interner intsafe INVALIDARG INVALIDATERECT -ioctl +Ioctl ipch ipp IProperty @@ -930,9 +889,9 @@ ivalid IWIC IXP jconcpp +JLO JOBOBJECT JOBOBJECTINFOCLASS -jpe JPN jsoncpp Jsons @@ -950,13 +909,13 @@ kernelbase kernelbasestaging KEYBDINPUT keychord -keydown +keydowns KEYFIRST KEYLAST Keymapping keyscan keystate -keyup +keyups khome KILLACTIVE KILLFOCUS @@ -990,6 +949,7 @@ LEFTALIGN libpopcnt libsancov libtickit +licate LIMITTEXT LINEDOWN LINESELECTION @@ -1002,7 +962,6 @@ listptrsize lld llx LMENU -lnk lnkd lnkfile LNM @@ -1011,6 +970,7 @@ LOBYTE localappdata locsrc Loewen +LOGBRUSH LOGFONT LOGFONTA LOGFONTW @@ -1018,7 +978,6 @@ logissue losslessly loword lparam -LPCCH lpch LPCPLINFO LPCREATESTRUCT @@ -1059,12 +1018,12 @@ lpwstr LRESULT lsb lsconfig -lss lstatus lstrcmp lstrcmpi LTEXT LTLTLTLTL +ltsc LUID luma lval @@ -1109,6 +1068,7 @@ MENUITEMINFO MENUSELECT messageext metaproj +microsoftpublicsymbols midl mii MIIM @@ -1123,7 +1083,6 @@ Mip MMBB mmcc MMCPL -MMIX mmsystem MNC MNOPQ @@ -1137,12 +1096,12 @@ MONITORINFOF MOUSEACTIVATE MOUSEFIRST MOUSEHWHEEL -MOUSEMOVE MOVESTART msb msctf msctls msdata +MSDL msft MSGCMDLINEF MSGF @@ -1156,21 +1115,19 @@ MSIL msix msrc MSVCRTD -msys MTSM -munged munges +Munged murmurhash muxes myapplet +mybranch mydir MYMAX Mypair Myval NAMELENGTH -nameof namestream -natvis NCCALCSIZE NCCREATE NCLBUTTONDOWN @@ -1183,6 +1140,8 @@ NCRBUTTONUP NCXBUTTONDOWN NCXBUTTONUP NEL +nerf +nerror netcoreapp netstandard NEWCPLINFO @@ -1217,12 +1176,10 @@ NOMOVE NONALERT nonbreaking nonclient -NONCONST NONINFRINGEMENT NONPREROTATED nonspace NOOWNERZORDER -Nop NOPAINT noprofile NOREDRAW @@ -1244,7 +1201,6 @@ NOTRACK NOTSUPPORTED nouicompat nounihan -NOUPDATE NOYIELD NOZORDER nrcs @@ -1268,28 +1224,25 @@ ntuser NTVDM ntverp nugetversions -nullability nullness nullonfailure nullopts numlock -numpad NUMSCROLL +NUnit nupkg NVIDIA OACR objbase ocolor -odl oemcp OEMFONT OEMFORMAT OEMs offboarded -oklab -Oklab OLEAUT OLECHAR +onebranch onecore ONECOREBASE ONECORESDKTOOLS @@ -1300,7 +1253,6 @@ onecoreuuid ONECOREWINDOWS onehalf oneseq -OOM openbash opencode opencon @@ -1309,24 +1261,23 @@ openconsoleproxy openps openvt ORIGINALFILENAME +orking osc OSDEPENDSROOT OSG OSGENG -osign oss -otepad -ouicompat -OUnter outdir OUTOFCONTEXT Outptr outstr OVERLAPPEDWINDOW OWNDC +owneralias OWNERDRAWFIXED packagename packageuwp +PACKAGEVERSIONNUMBER PACKCOORD PACKVERSION pagedown @@ -1337,9 +1288,9 @@ PALPC pankaj parentable parms -passthrough PATCOPY pathcch +Pathto PATTERNID pcat pcb @@ -1358,7 +1309,6 @@ PCONSOLEENDTASK PCONSOLESETFOREGROUND PCONSOLEWINDOWOWNER pcoord -pcs pcshell PCSHORT PCSR @@ -1367,8 +1317,9 @@ PCWCH PCWCHAR PCWSTR pda -Pdbs +pdbs pdbstr +PDPs pdtobj pdw pdx @@ -1385,10 +1336,9 @@ PFONT PFONTENUMDATA PFS pgd -pgdn +pgomgr PGONu pguid -pgup phhook phwnd pidl @@ -1401,7 +1351,6 @@ pipestr pixelheight PIXELSLIST PJOBOBJECT -pkey platforming playsound ploc @@ -1414,6 +1363,7 @@ pntm POBJECT Podcast POINTSLIST +policheck POLYTEXTW poppack POPUPATTR @@ -1442,8 +1392,6 @@ prealigned prect prefast preflighting -prefs -prepopulated presorted PREVENTPINNING PREVIEWLABEL @@ -1455,6 +1403,7 @@ prioritization processenv processhost PROCESSINFOCLASS +PRODEXT PROPERTYID PROPERTYKEY PROPERTYVAL @@ -1486,7 +1435,6 @@ psr PSTR psz ptch -ptrs ptsz PTYIn PUCHAR @@ -1497,7 +1445,6 @@ pwstr pwsz pythonw Qaabbcc -qos QUERYOPEN QUESTIONMARK quickedit @@ -1519,9 +1466,7 @@ RBUTTONDBLCLK RBUTTONDOWN RBUTTONUP rcch -RCDATA rcelms -rcl rclsid RCOA RCOCA @@ -1536,7 +1481,7 @@ READCONSOLEOUTPUTSTRING READMODE reallocs reamapping -rects +rectread redef redefinable Redir @@ -1555,7 +1500,7 @@ remoting renamer renderengine rendersize -reparent +reparented reparenting replatformed Replymessage @@ -1567,36 +1512,31 @@ Resequence RESETCONTENT resheader resmimetype +resultmacros resw resx rfa rfid rftp -RGBCOLOR rgbi rgbs rgci rgfae rgfte -rgi rgn rgp rgpwsz rgrc -rgs -rgui rgw RIGHTALIGN RIGHTBUTTON riid -Rike -RIPMSG RIS roadmap robomac rosetta -roundtrips RRF +rrr RRRGGGBB rsas rtcore @@ -1604,7 +1544,6 @@ RTEXT RTFTo RTLREADING Rtn -RTTI ruleset runas RUNDLL @@ -1622,16 +1561,14 @@ RVERTICAL rvpa RWIN rxvt -safearray safemath -sapi sba SBCS SBCSDBCS sbi sbiex -sbold -scancode +sbom +scancodes scanline schemename SCL @@ -1643,7 +1580,6 @@ screeninfo screenshots scriptload scrollback -scrollbars SCROLLFORWARD SCROLLINFO scrolllock @@ -1652,7 +1588,6 @@ SCROLLSCALE SCROLLSCREENBUFFER scursor sddl -sdeleted SDKDDK securityappcontainer segfault @@ -1664,7 +1599,6 @@ Selfhosters SERVERDLL SETACTIVE SETBUDDYINT -SETCOLOR setcp SETCURSEL SETCURSOR @@ -1672,7 +1606,6 @@ SETCURSORINFO SETCURSORPOSITION SETDISPLAYMODE SETFOCUS -SETFONT SETFOREGROUND SETHARDWARESTATE SETHOTKEY @@ -1690,6 +1623,7 @@ SETSCREENBUFFERSIZE SETSEL SETTEXTATTRIBUTE SETTINGCHANGE +setvariable Setwindow SETWINDOWINFO SFGAO @@ -1707,7 +1641,6 @@ shellscalingapi SHFILEINFO SHGFI SHIFTJIS -Shl shlguid shlobj shlwapi @@ -1723,6 +1656,7 @@ SHOWWINDOW sidebyside SIF SIGDN +Signtool SINGLEFLAG SINGLETHREADED siup @@ -1742,12 +1676,10 @@ slpit SManifest SMARTQUOTE SMTO -SND SOLIDBOX Solutiondir somefile sourced -spammy SRCCODEPAGE SRCCOPY SRCINVERT @@ -1755,8 +1687,6 @@ srcsrv SRCSRVTRG srctool srect -srgb -Srgb srv srvinit srvpipe @@ -1770,7 +1700,6 @@ STARTUPINFOW STARTWPARMS STARTWPARMSA STARTWPARMSW -Statusline stb stdafx STDAPI @@ -1780,26 +1709,21 @@ STDEXT STDMETHODCALLTYPE STDMETHODIMP STGM -stl -Stri Stringable STRINGTABLE -strrev strsafe STUBHEAD STUVWX stylecop SUA subcompartment -subfolders -subkey +subkeys SUBLANG subresource subsystemconsole subsystemwindows swapchain swapchainpanel -swappable SWMR SWP SYMED @@ -1825,8 +1749,6 @@ targetentrypoint TARGETLIBS TARGETNAME targetver -taskbar -tbar TBase tbc tbi @@ -1835,8 +1757,8 @@ TBM tchar TCHFORMAT TCI -tcommandline tcommands +tdbuild Tdd TDelegated TDP @@ -1853,7 +1775,7 @@ terminfo TEs testcon testd -testenv +testenvs testlab testlist testmd @@ -1862,7 +1784,6 @@ TESTNULL testpass testpasses TEXCOORD -texel TExpected textattribute TEXTATTRIBUTEID @@ -1875,14 +1796,12 @@ TEXTMETRICW textmode texttests TFCAT -tfoo TFunction -tga THUMBPOSITION THUMBTRACK TIcon tilunittests -titlebar +titlebars TITLEISLINKNAME TJson TLambda @@ -1899,18 +1818,13 @@ toolbars TOOLINFO TOOLWINDOW TOPDOWNDIB -TOPLEFT -TOPRIGHT TOpt tosign touchpad Tpp Tpqrst -tprivapi -tput tracelog tracelogging -traceloggingprovider traceviewpp trackbar TRACKCOMPOSITION @@ -1924,8 +1838,8 @@ TRIANGLESTRIP Tribool TRIMZEROHEADINGS trx +tsa tsattrs -tsf tsgr tsm TStr @@ -1934,15 +1848,12 @@ TSub TTBITMAP TTFONT TTFONTLIST -tthe -tthis TTM TTo tvpp +tvtseq Txtev typechecked -typelib -typeparam TYUI UAC uap @@ -1953,10 +1864,8 @@ ucd uch UChars udk -UDM uer UError -uget uia UIACCESS uiacore @@ -1964,9 +1873,12 @@ uiautomationcore uielem UIELEMENTENABLEDONLY UINTs +ul ulcch -umul -umulh +uld +uldb +uldash +ulwave Unadvise unattend UNCPRIORITY @@ -1975,7 +1887,6 @@ unhighlighting unhosted UNICODETEXT UNICRT -uninitialize Unintense Uniscribe unittesting @@ -1986,7 +1897,6 @@ UNORM unparseable unregistering untextured -untimes UPDATEDISPLAY UPDOWN UPKEY @@ -2003,7 +1913,6 @@ USEFILLATTRIBUTE USEGLYPHCHARS USEHICON USEPOSITION -userbase USERDATA userdpiapi Userp @@ -2015,8 +1924,6 @@ USESTDHANDLES usp USRDLL utext -UText -UTEXT utr UVWXY UVWXYZ @@ -2026,12 +1933,9 @@ uxtheme Vanara vararg vclib -vcpkg vcprintf vcxitems -vec vectorize -vectorized VERCTRL VERTBAR VFT @@ -2045,16 +1949,19 @@ vkey VKKEYSCAN VMs VPA +vpack +vpackdirectory +VPACKMANIFESTDIRECTORY VPR -VProc -VRaw VREDRAW vsc vsconfig vscprintf VSCROLL vsdevshell +vse vsinfo +vsinstalldir vso vspath VSTAMP @@ -2089,7 +1996,6 @@ WCIA WCIW WCSHELPER wcsicmp -wcsnicmp wcsrev wddm wddmcon @@ -2144,16 +2050,17 @@ windowsshell windowsterminal windowsx windowtheme -WINDOWTITLE winevent wingdi winget +wingetcreate WINIDE winioctl winmd winmeta winmgr winmm +WINMSAPP winnt Winperf WInplace @@ -2179,13 +2086,13 @@ WNegative WNull wnwb workarea -workaround WOutside WOWARM WOWx wparam WPartial wpf +wpfdotnet WPR WPrep WPresent @@ -2243,6 +2150,7 @@ xdy XEncoding xes xff +XFG XFile XFORM xin @@ -2270,9 +2178,7 @@ yact YCast YCENTER YCount -YDPI YLimit -YOffset YSubstantial YVIRTUALSCREEN YWalk diff --git a/.github/actions/spelling/expect/web.txt b/.github/actions/spelling/expect/web.txt index 8f5b59ac730..39e8cc78d55 100644 --- a/.github/actions/spelling/expect/web.txt +++ b/.github/actions/spelling/expect/web.txt @@ -1,4 +1,3 @@ -WCAG winui appshellintegration mdtauk diff --git a/.github/actions/spelling/line_forbidden.patterns b/.github/actions/spelling/line_forbidden.patterns index 31ad2ddcd26..41cf9272ed2 100644 --- a/.github/actions/spelling/line_forbidden.patterns +++ b/.github/actions/spelling/line_forbidden.patterns @@ -1,4 +1,6 @@ -# reject `m_data` as there's a certain OS which has evil defines that break things if it's used elsewhere +# reject `m_data` as VxWorks defined it and that breaks things if it's used elsewhere +# see [fprime](https://github.com/nasa/fprime/commit/d589f0a25c59ea9a800d851ea84c2f5df02fb529) +# and [Qt](https://github.com/qtproject/qt-solutions/blame/fb7bc42bfcc578ff3fa3b9ca21a41e96eb37c1c7/qtscriptclassic/src/qscriptbuffer_p.h#L46) # \bm_data\b # If you have a framework that uses `it()` for testing and `fit()` for debugging a specific test, @@ -6,40 +8,72 @@ # to use this: #\bfit\( +# s.b. anymore +\bany more[,.] + # s.b. GitHub -\bGithub\b +(?)[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}(?:[}"]|"'`=(])-(?:D(?=[A-Z])|[WX]|f(?=[ms]))(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) + +# hit-count: 60 file-count: 35 # version suffix 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: 2 file-count: 2 +# This does not cover multiline strings, if your repository has them, +# you'll want to remove the `(?=.*?")` suffix. +# The `(?=.*?")` suffix should limit the false positives rate +# printf +%(?:s)(?!ize)(?=[a-z]{2,}) -# hit-count: 10 file-count: 7 +# hit-count: 16 file-count: 10 # uuid: \b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b +# hit-count: 13 file-count: 4 +# Non-English +[a-zA-Z]*[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*|[a-zA-Z]{3,}[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]|[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3,} + +# hit-count: 7 file-count: 5 +# 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}|[iu]\d+)\b + +# hit-count: 7 file-count: 1 +# regex choice +\(\?:[^)]+\|[^)]+\) + # hit-count: 4 file-count: 4 -# mailto urls -mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,} +# tar arguments +\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+ # hit-count: 4 file-count: 1 # ANSI color codes -(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m +(?:\\(?:u00|x)1[Bb]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+|)m + +# hit-count: 4 file-count: 1 +# 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|[,.])* + +# hit-count: 3 file-count: 3 +# mailto urls +mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,} # hit-count: 2 file-count: 1 -# latex -\\(?:n(?:ew|ormal|osub)|r(?:enew)|t(?:able(?:of|)|he|itle))(?=[a-z]+) +# Python string prefix / binary prefix +# 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 +(?= 0.0.22) +\\\w{2,}\{ # 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 +# 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 + +# Questionably acceptable forms of `in to` +# Personally, I prefer `log into`, but people object +# https://www.tprteaching.com/log-into-log-in-to-login/ +\b(?:[Ll]og|[Ss]ign) in to\b + +# to opt in +\bto opt in\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 +[-bcdlpsw](?:[-r][-w][-Ssx]){3}\s+\d+\s+\S+\s+\S+\s+\d+\s+ +# mount +\bmount\s+-t\s+(\w+)\s+\g{-1}\b +# C types and repeated CSS values +\s(auto|center|div|Guid|inherit|long|LONG|none|normal|solid|that|thin|transparent|very)(?: \g{-1})+\s +# C struct +\bstruct\s+(\w+)\s+\g{-1}\b +# go templates +\s(\w+)\s+\g{-1}\s+\`(?:graphql|inject|json|yaml): +# doxygen / javadoc / .net +(?:[\\@](?:brief|groupname|t?param|return|retval)|(?:public|private|\[Parameter(?:\(.+\)|)\])(?:\s+static|\s+override|\s+readonly)*)(?:\s+\{\w+\}|)\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*$ diff --git a/.github/actions/spelling/reject.txt b/.github/actions/spelling/reject.txt index 301719de47e..2ac1670d534 100644 --- a/.github/actions/spelling/reject.txt +++ b/.github/actions/spelling/reject.txt @@ -1,6 +1,7 @@ ^attache$ ^attacher$ ^attachers$ +^bellow$ benefitting occurences? ^dependan.* diff --git a/.github/workflows/addToProject.yml b/.github/workflows/addToProject.yml index 3fbb2a7b2cb..4baa537dd19 100644 --- a/.github/workflows/addToProject.yml +++ b/.github/workflows/addToProject.yml @@ -7,13 +7,13 @@ on: - labeled - unlabeled -permissions: {} +permissions: {} jobs: add-to-project: name: Add issue to project runs-on: ubuntu-latest steps: - - uses: actions/add-to-project@v0.3.0 + - uses: actions/add-to-project@v0.5.0 with: project-url: https://github.com/orgs/microsoft/projects/159 github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} diff --git a/.github/workflows/similarIssues.yml b/.github/workflows/similarIssues.yml new file mode 100644 index 00000000000..fa93ad8597d --- /dev/null +++ b/.github/workflows/similarIssues.yml @@ -0,0 +1,32 @@ +name: GitGudSimilarIssues comments + +on: + issues: + types: [opened] + +jobs: + getSimilarIssues: + runs-on: ubuntu-latest + outputs: + message: ${{ steps.getBody.outputs.message }} + steps: + - id: getBody + uses: craigloewen-msft/GitGudSimilarIssues@main + with: + issuetitle: ${{ github.event.issue.title }} + repo: ${{ github.repository }} + similaritytolerance: "0.75" + add-comment: + needs: getSimilarIssues + runs-on: ubuntu-latest + permissions: + issues: write + if: needs.getSimilarIssues.outputs.message != '' + steps: + - name: Add comment + run: gh issue comment "$NUMBER" --repo "$REPO" --body "$BODY" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NUMBER: ${{ github.event.issue.number }} + REPO: ${{ github.repository }} + BODY: ${{ needs.getSimilarIssues.outputs.message }} diff --git a/.github/workflows/spelling2.yml b/.github/workflows/spelling2.yml index 446b24343ed..2778a7e9e5d 100644 --- a/.github/workflows/spelling2.yml +++ b/.github/workflows/spelling2.yml @@ -5,7 +5,7 @@ name: Spell checking # 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) +# (in odd cases, it might actually run just to collapse a comment, 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 @@ -34,6 +34,29 @@ name: Spell checking # # For background, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Update-with-deploy-key +# Sarif reporting +# +# Access to Sarif reports is generally restricted (by GitHub) to members of the repository. +# +# Requires enabling `security-events: write` +# and configuring the action with `use_sarif: 1` +# +# For information on the feature, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Sarif-output + +# Minimal workflow structure: +# +# on: +# push: +# ... +# pull_request_target: +# ... +# jobs: +# # you only want the spelling job, all others should be omitted +# spelling: +# # remove `security-events: write` and `use_sarif: 1` +# # remove `experimental_apply_changes_via_bot: 1` +# ... otherwise adjust the `with:` as you wish + on: push: branches: @@ -43,8 +66,6 @@ on: pull_request_target: branches: - "**" - tags-ignore: - - "**" types: - 'opened' - 'reopened' @@ -60,10 +81,11 @@ jobs: contents: read pull-requests: read actions: read + security-events: write outputs: followup: ${{ steps.spelling.outputs.followup }} runs-on: ubuntu-latest - if: "contains(github.event_name, 'pull_request') || github.event_name == 'push'" + 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 @@ -71,35 +93,50 @@ jobs: steps: - name: check-spelling id: spelling - uses: check-spelling/check-spelling@v0.0.21 + uses: check-spelling/check-spelling@v0.0.22 with: - suppress_push_for_open_pull_request: 1 + suppress_push_for_open_pull_request: ${{ github.actor != 'dependabot[bot]' && 1 }} checkout: true check_file_names: 1 - spell_check_this: check-spelling/spell-check-this@prerelease + spell_check_this: microsoft/terminal@main post_comment: 0 use_magic_file: 1 - extra_dictionary_limit: 10 + report-timing: 1 + warnings: bad-regex,binary-file,deprecated-feature,ignored-expect-variant,large-file,limited-references,no-newline-at-eof,noisy-file,non-alpha-in-dictionary,token-is-substring,unexpected-line-ending,whitespace-in-dictionary,minified-file,unsupported-configuration,no-files-to-check + experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }} + use_sarif: ${{ (!github.event.pull_request || (github.event.pull_request.head.repo.full_name == github.repository)) && 1 }} + extra_dictionary_limit: 20 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:software-terms/dict/softwareTerms.txt cspell:cpp/src/stdlib-cpp.txt - cspell:fullstack/fullstack.txt + cspell:lorem-ipsum/dictionary.txt + cspell:cpp/src/stdlib-c.txt + cspell:php/dict/php.txt cspell:filetypes/filetypes.txt - cspell:html/html.txt - cspell:cpp/src/compiler-msvc.txt + cspell:java/src/java.txt cspell:python/src/common/extra.txt - cspell:powershell/powershell.txt + cspell:node/dict/node.txt + cspell:java/src/java-terms.txt cspell:aws/aws.txt + cspell:typescript/dict/typescript.txt + cspell:dotnet/dict/dotnet.txt + cspell:golang/dict/go.txt + cspell:fullstack/dict/fullstack.txt + cspell:cpp/src/compiler-msvc.txt + cspell:python/src/python/python-lib.txt + cspell:mnemonics/src/mnemonics.txt + cspell:cpp/src/stdlib-cmath.txt + cspell:css/dict/css.txt cspell:cpp/src/lang-keywords.txt - cspell:npm/npm.txt - cspell:dotnet/dotnet.txt + cspell:django/dict/django.txt cspell:python/src/python/python.txt - cspell:css/css.txt - cspell:cpp/src/stdlib-cmath.txt - check_extra_dictionaries: '' + cspell:html/dict/html.txt + cspell:cpp/src/ecosystem.txt + cspell:cpp/src/compiler-clang-attributes.txt + cspell:npm/dict/npm.txt + cspell:r/src/r.txt + cspell:powershell/dict/powershell.txt + cspell:csharp/csharp.txt comment-push: name: Report (Push) @@ -111,10 +148,10 @@ jobs: if: (success() || failure()) && needs.spelling.outputs.followup && github.event_name == 'push' steps: - name: comment - uses: check-spelling/check-spelling@v0.0.21 + uses: check-spelling/check-spelling@v0.0.22 with: checkout: true - spell_check_this: check-spelling/spell-check-this@prerelease + spell_check_this: microsoft/terminal@main task: ${{ needs.spelling.outputs.followup }} comment-pr: @@ -123,12 +160,38 @@ jobs: runs-on: ubuntu-latest needs: spelling permissions: + contents: read 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 + uses: check-spelling/check-spelling@v0.0.22 with: checkout: true - spell_check_this: check-spelling/spell-check-this@prerelease + spell_check_this: microsoft/terminal@main task: ${{ needs.spelling.outputs.followup }} + experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }} + + update: + name: Update PR + permissions: + contents: write + pull-requests: write + actions: read + runs-on: ubuntu-latest + if: ${{ + github.repository_owner != 'microsoft' && + github.event_name == 'issue_comment' && + github.event.issue.pull_request && + contains(github.event.comment.body, '@check-spelling-bot apply') + }} + concurrency: + group: spelling-update-${{ github.event.issue.number }} + cancel-in-progress: false + steps: + - name: apply spelling updates + uses: check-spelling/check-spelling@v0.0.22 + with: + experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }} + checkout: true + ssh_key: "${{ secrets.CHECK_SPELLING }}" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c7f3b1a710f..18ebdd96011 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ The point of doing all this work in public is to ensure that we are holding ours The team triages new issues several times a week. During triage, the team uses labels to categorize, manage, and drive the project workflow. -We employ [a bot engine](https://github.com/microsoft/terminal/blob/main/doc/bot.md) to help us automate common processes within our workflow. +We employ [a bot engine](./doc/bot.md) to help us automate common processes within our workflow. We drive the bot by tagging issues with specific labels which cause the bot engine to close issues, merge branches, etc. This bot engine helps us keep the repo clean by automating the process of notifying appropriate parties if/when information/follow-up is needed, and closing stale issues/PRs after reminders have remained unanswered for several days. diff --git a/README.md b/README.md index 88968fa7d22..462731aa594 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ This repository contains the source code for: * [Windows Terminal Preview](https://aka.ms/terminal-preview) * The Windows console host (`conhost.exe`) * Components shared between the two projects -* [ColorTool](https://github.com/microsoft/terminal/tree/main/src/tools/ColorTool) -* [Sample projects](https://github.com/microsoft/terminal/tree/main/samples) +* [ColorTool](./src/tools/ColorTool) +* [Sample projects](./samples) that show how to consume the Windows Console APIs Related repositories include: @@ -21,7 +21,7 @@ Related repositories include: ## Installing and running Windows Terminal -> **Note**\ +> [!NOTE] > Windows Terminal requires Windows 10 2004 (build 19041) or later ### Microsoft Store [Recommended] @@ -53,7 +53,7 @@ fails for any reason, you can try the following command at a PowerShell prompt: Add-AppxPackage Microsoft.WindowsTerminal_.msixbundle ``` -> **Note**\ +> [!NOTE] > If you install Terminal manually: > > * You may need to install the [VC++ v14 Desktop Framework Package](https://docs.microsoft.com/troubleshoot/cpp/c-runtime-packages-desktop-bridge#how-to-install-and-update-desktop-framework-packages). @@ -72,7 +72,7 @@ package: winget install --id Microsoft.WindowsTerminal -e ``` -> **Note**\ +> [!NOTE] > Dependency support is available in WinGet version [1.6.2631 or later](https://github.com/microsoft/winget-cli/releases). To install the Terminal stable release 1.18 or later, please make sure you have the updated version of the WinGet client. #### Via Chocolatey (unofficial) @@ -262,7 +262,7 @@ Cause: You're launching the incorrect solution in Visual Studio. Solution: Make sure you're building & deploying the `CascadiaPackage` project in Visual Studio. -> **Note**\ +> [!NOTE] > `OpenConsole.exe` is just a locally-built `conhost.exe`, the classic > Windows Console that hosts Windows' command-line infrastructure. OpenConsole > is used by Windows Terminal to connect to and communicate with command-line @@ -286,7 +286,7 @@ enhance Windows Terminal\! ***BEFORE you start work on a feature/fix***, please read & follow our [Contributor's -Guide](https://github.com/microsoft/terminal/blob/main/CONTRIBUTING.md) to +Guide](./CONTRIBUTING.md) to help avoid any wasted or duplicate effort. ## Communicating with the Team @@ -387,10 +387,10 @@ Please review these brief docs below about our coding practices. This is a work in progress as we learn what we'll need to provide people in order to be effective contributors to our project. -* [Coding Style](https://github.com/microsoft/terminal/blob/main/doc/STYLE.md) -* [Code Organization](https://github.com/microsoft/terminal/blob/main/doc/ORGANIZATION.md) -* [Exceptions in our legacy codebase](https://github.com/microsoft/terminal/blob/main/doc/EXCEPTIONS.md) -* [Helpful smart pointers and macros for interfacing with Windows in WIL](https://github.com/microsoft/terminal/blob/main/doc/WIL.md) +* [Coding Style](./doc/STYLE.md) +* [Code Organization](./doc/ORGANIZATION.md) +* [Exceptions in our legacy codebase](./doc/EXCEPTIONS.md) +* [Helpful smart pointers and macros for interfacing with Windows in WIL](./doc/WIL.md) --- diff --git a/SUPPORT.md b/SUPPORT.md index ba2389bf86f..275d58a9aed 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -14,4 +14,4 @@ Support for Windows Terminal is limited to the resources listed above. [gh-bug]: https://github.com/microsoft/terminal/issues/new?assignees=&labels=Issue-Bug&template=bug_report.md&title= [gh-feature]: https://github.com/microsoft/terminal/issues/new?assignees=&labels=Issue-Feature&template=Feature_Request.md&title= [docs]: https://docs.microsoft.com/windows/terminal -[contributor]: https://github.com/microsoft/terminal/blob/main/CONTRIBUTING.md +[contributor]: ./CONTRIBUTING.md diff --git a/build/pipelines/ci.yml b/build/pipelines/ci.yml index 4ad72566a15..ab3490bc58b 100644 --- a/build/pipelines/ci.yml +++ b/build/pipelines/ci.yml @@ -102,7 +102,7 @@ stages: - ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: - stage: CodeIndexer - displayName: Github CodeNav Indexer + displayName: GitHub CodeNav Indexer dependsOn: [] jobs: - template: ./templates-v2/job-index-github-codenav.yml diff --git a/build/pipelines/templates-v2/job-index-github-codenav.yml b/build/pipelines/templates-v2/job-index-github-codenav.yml index e2edf55e651..b59b0a436ef 100644 --- a/build/pipelines/templates-v2/job-index-github-codenav.yml +++ b/build/pipelines/templates-v2/job-index-github-codenav.yml @@ -1,6 +1,6 @@ jobs: - job: CodeNavIndexer - displayName: Run Github CodeNav Indexer + displayName: Run GitHub CodeNav Indexer pool: { vmImage: windows-2022 } steps: diff --git a/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml b/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml index 271e7d3e121..159eb6d080d 100644 --- a/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml +++ b/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml @@ -247,7 +247,7 @@ extends: - stage: Publish displayName: Publish - dependsOn: [Build, Package] + dependsOn: [Build] jobs: - template: ./build/pipelines/templates-v2/job-publish-symbols.yml@self parameters: diff --git a/custom.props b/custom.props index 328d7042eb2..6a0586fd8b2 100644 --- a/custom.props +++ b/custom.props @@ -3,9 +3,9 @@ true - 2023 + 2024 1 - 20 + 21 Windows Terminal diff --git a/dep/nuget/packages.config b/dep/nuget/packages.config index e401b157d67..e6fd7ec3a68 100644 --- a/dep/nuget/packages.config +++ b/dep/nuget/packages.config @@ -9,7 +9,7 @@ - + diff --git a/doc/AddASetting.md b/doc/AddASetting.md index 2d4c414d910..59128125d71 100644 --- a/doc/AddASetting.md +++ b/doc/AddASetting.md @@ -5,10 +5,10 @@ `.../console/published/wincon.w` in the OS repo when you submit the PR. The branch won't build without it. * For now, you can update winconp.h with your consumable changes. - * Define registry name (ex `CONSOLE_REGISTRY_CURSORCOLOR`) - * Add the setting to `CONSOLE_STATE_INFO` + * Define registry name (ex: `CONSOLE_REGISTRY_CURSORCOLOR`) + * Add the setting to `CONSOLE_STATE_INFO`. * Define the property key ID and the property key itself. - - Yes, the large majority of the `DEFINE_PROPERTYKEY` defs are the same, it's only the last byte of the guid that changes + - Yes, the large majority of the `DEFINE_PROPERTYKEY` defs are the same, it's only the last byte of the guid that changes. 2. Add matching fields to Settings.hpp - Add getters, setters, the whole drill. @@ -17,9 +17,9 @@ - We need to add it to *reading and writing* the registry from the propsheet, and *reading* the link from the propsheet. Yes, that's weird, but the propsheet is smart enough to re-use ShortcutSerialization::s_SetLinkValues, but not smart enough to do the same with RegistrySerialization. - `src/propsheet/registry.cpp` - `propsheet/registry.cpp@InitRegistryValues` should initialize the default value for the property. - - `propsheet/registry.cpp@GetRegistryValues` should make sure to read the property from the registry + - `propsheet/registry.cpp@GetRegistryValues` should make sure to read the property from the registry. -4. Add the field to the propslib registry map +4. Add the field to the propslib registry map. 5. Add the value to `ShortcutSerialization.cpp` - Read the value in `ShortcutSerialization::s_PopulateV2Properties` @@ -30,11 +30,11 @@ Now, your new setting should be stored just like all the other properties. 7. Update the feature test properties to get add the setting as well - `ft_uia/Common/NativeMethods.cs@WinConP`: - - `Wtypes.PROPERTYKEY PKEY_Console_` - - `NT_CONSOLE_PROPS` + - `Wtypes.PROPERTYKEY PKEY_Console_`. + - `NT_CONSOLE_PROPS`. 8. Add the default value for the setting to `win32k-settings.man` - If the setting shouldn't default to 0 or `nullptr`, then you'll need to set the default value of the setting in `win32k-settings.man`. -9. Update `Settings::InitFromStateInfo` and `Settings::CreateConsoleStateInfo` to get/set the value in a CONSOLE_STATE_INFO appropriately +9. Update `Settings::InitFromStateInfo` and `Settings::CreateConsoleStateInfo` to get/set the value in a CONSOLE_STATE_INFO appropriately. diff --git a/doc/COOKED_READ_DATA.md b/doc/COOKED_READ_DATA.md new file mode 100644 index 00000000000..f4b463f7aa6 --- /dev/null +++ b/doc/COOKED_READ_DATA.md @@ -0,0 +1,96 @@ +# COOKED_READ_DATA, aka conhost's readline implementation + +## Test instructions + +All of the following ✅ marks must be fulfilled during manual testing: +* ASCII input +* Chinese input (中文維基百科) ❔ + * Resizing the window properly wraps/unwraps wide glyphs ❌ + Broken due to `TextBuffer::Reflow` bugs +* Surrogate pair input (🙂) ❔ + * Resizing the window properly wraps/unwraps surrogate pairs ❌ + Broken due to `TextBuffer::Reflow` bugs +* In cmd.exe + * Create 2 file: "a😊b.txt" and "a😟b.txt" + * Press tab: Autocomplete to "a😊b.txt" ✅ + * Navigate the cursor right past the "a" + * Press tab twice: Autocomplete to "a😟b.txt" ✅ +* Execute `printf(" "); gets(buffer);` in C (or equivalent) + * Press Tab, A, Ctrl+V, Tab, A ✅ + * The prompt is " A^V A" ✅ + * Cursor navigation works ✅ + * Backspacing/Deleting random parts of it works ✅ + * It never deletes the initial 4 spaces ✅ +* Backspace deletes preceding glyphs ✅ +* Ctrl+Backspace deletes preceding words ✅ +* Escape clears input ✅ +* Home navigates to start ✅ +* Ctrl+Home deletes text between cursor and start ✅ +* End navigates to end ✅ +* Ctrl+End deletes text between cursor and end ✅ +* Left navigates over previous code points ✅ +* Ctrl+Left navigates to previous word-starts ✅ +* Right and F1 navigate over next code points ✅ + * Pressing right at the end of input copies characters + from the previous command ✅ +* Ctrl+Right navigates to next word-ends ✅ +* Insert toggles overwrite mode ✅ +* Delete deletes next code point ✅ +* Up and F5 cycle through history ✅ + * Doesn't crash with no history ✅ + * Stops at first entry ✅ +* Down cycles through history ✅ + * Doesn't crash with no history ✅ + * Stops at last entry ✅ +* PageUp retrieves the oldest command ✅ +* PageDown retrieves the newest command ✅ +* F2 starts "copy to char" prompt ✅ + * Escape dismisses prompt ✅ + * Typing a character copies text from the previous command up + until that character into the current buffer (acts identical + to F3, but with automatic character search) ✅ +* F3 copies the previous command into the current buffer, + starting at the current cursor position, + for as many characters as possible ✅ + * Doesn't erase trailing text if the current buffer + is longer than the previous command ✅ + * Puts the cursor at the end of the copied text ✅ +* F4 starts "copy from char" prompt ✅ + * Escape dismisses prompt ✅ + * Erases text between the current cursor position and the + first instance of a given char (but not including it) ✅ +* F6 inserts Ctrl+Z ✅ +* F7 without modifiers starts "command list" prompt ✅ + * Escape dismisses prompt ✅ + * Minimum size of 40x10 characters ✅ + * Width expands to fit the widest history command ✅ + * Height expands up to 20 rows with longer histories ✅ + * F9 starts "command number" prompt ✅ + * Left/Right paste replace the buffer with the given command ✅ + * And put cursor at the end of the buffer ✅ + * Up/Down navigate selection through history ✅ + * Stops at start/end with <10 entries ✅ + * Stops at start/end with >20 entries ✅ + * Wide text rendering during pagination with >20 entries ✅ + * Shift+Up/Down moves history items around ✅ + * Home navigates to first entry ✅ + * End navigates to last entry ✅ + * PageUp navigates by 20 items at a time or to first ✅ + * PageDown navigates by 20 items at a time or to last ✅ +* Alt+F7 clears command history ✅ +* F8 cycles through commands that start with the same text as + the current buffer up until the current cursor position ✅ + * Doesn't crash with no history ✅ +* F9 starts "command number" prompt ✅ + * Escape dismisses prompt ✅ + * Ignores non-ASCII-decimal characters ✅ + * Allows entering between 1 and 5 digits ✅ + * Pressing Enter fetches the given command from the history ✅ +* Alt+F10 clears doskey aliases ✅ +* In cmd.exe, with an empty prompt in an empty directory: + Pressing tab produces an audible bing and prints no text ✅ +* When Narrator is enabled, in cmd.exe: + * Typing individual characters announces only + exactly each character that is being typed ✅ + * Backspacing at the end of a prompt announces + only exactly each deleted character ✅ diff --git a/doc/Niksa.md b/doc/Niksa.md index 5efcdda0abd..ec6af1f1463 100644 --- a/doc/Niksa.md +++ b/doc/Niksa.md @@ -51,7 +51,7 @@ Will this UI enhancement come to other apps on Windows? Almost certainly not. Th Will we try to keep it from regressing? Yes! Right now it's sort of a manual process. We identify that something is getting slow and then we go haul out [WPR](https://docs.microsoft.com/en-us/windows-hardware/test/wpt/windows-performance-recorder) and start taking traces. We stare down the hot paths and try to reason out what is going on and then improve them. For instance, in the last cycle or two, we focused on heap allocations as a major area where we could improve our end-to-end performance, changing a ton of our code to use stack-constructed iterator-like facades over the underlying request buffer instead of translating and allocating it into a new heap space for each level of processing. -As an aside, @bitcrazed wants us to automate performance tests in some conhost specific way, but I haven't quite figured out a controlled environment to do this in yet. The Windows Engineering System runs performance tests each night that give us a coarse grained way of knowing if we messed something up for the whole operating system, and they technically offer a fine grained way for us to insert our own performance tests... but I just haven't got around to that yet. If you have an idea for a way for us to do this in an automated fashion, I'm all ears. +As an aside, @bitcrazed wants us to automate performance tests in some conhost specific way, but I haven't quite figured out a controlled environment to do this in yet. The Windows Engineering System runs performance tests each night that give us a coarse-grained way of knowing if we messed something up for the whole operating system, and they technically offer a fine-grained way for us to insert our own performance tests... but I just haven't got around to that yet. If you have an idea for a way for us to do this in an automated fashion, I'm all ears. If there's anything else you'd like to know, let me know. I could go on all day. I deleted like 15 tangents from this reply before posting it.... diff --git a/doc/ORGANIZATION.md b/doc/ORGANIZATION.md index 07a6e41ee7e..5f93639d099 100644 --- a/doc/ORGANIZATION.md +++ b/doc/ORGANIZATION.md @@ -125,8 +125,6 @@ * Private calls into the Windows Window Manager to perform privileged actions related to the console process (working to eliminate) or for High DPI stuff (also working to eliminate) * `Userprivapi.cpp` * `Windowdpiapi.cpp` -* New UTF8 state machine in progress to improve Bash (and other apps) support for UTF-8 in console - * `Utf8ToWideCharParser.cpp` * Window resizing/layout/management/window messaging loops and all that other stuff that has us interact with Windows to create a visual display surface and control the user interaction entry point * `Window.cpp` * `Windowproc.cpp` diff --git a/doc/roadmap-2022.md b/doc/roadmap-2022.md index 5c4d5bf7e70..923ed00bf82 100644 --- a/doc/roadmap-2022.md +++ b/doc/roadmap-2022.md @@ -115,7 +115,7 @@ Incoming issues/asks/etc. are triaged several times a week, labeled appropriatel [Up Next]: https://github.com/microsoft/terminal/milestone/37 [Backlog]: https://github.com/microsoft/terminal/milestone/45 -[Terminal v2 Roadmap]: https://github.com/microsoft/terminal/tree/main/doc/terminal-v2-roadmap.md +[Terminal v2 Roadmap]: ./terminal-v2-roadmap.md [Windows Terminal Preview 1.2 Release]: https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-2-release/ [Windows Terminal Preview 1.3 Release]: https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-3-release/ @@ -131,4 +131,4 @@ Incoming issues/asks/etc. are triaged several times a week, labeled appropriatel [Windows Terminal Preview 1.13 Release]: https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-13-release/ [Windows Terminal Preview 1.14 Release]: https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-14-release/ -[Terminal 2023 Roadmap]: https://github.com/microsoft/terminal/tree/main/doc/roadmap-2023.md +[Terminal 2023 Roadmap]: ./roadmap-2023.md diff --git a/doc/roadmap-2023.md b/doc/roadmap-2023.md index c27faa340e0..33616a59890 100644 --- a/doc/roadmap-2023.md +++ b/doc/roadmap-2023.md @@ -54,9 +54,9 @@ _informative, not normative_ For a more fluid take on what each of the team's personal goals are, head on over to [Core team North Stars]. This has a list of more long-term goals that each team member is working towards, but not things that are necessarily committed work. -[^1]: A conclusive list of these features can be found at https://github.com/microsoft/terminal/blob/main/src/features.xml. Note that this is a raw XML doc used to light up specific parts of the codebase, and not something authored for human consumption. +[^1]: A conclusive list of these features can be found at [../src/features.xml](../src/features.xml). Note that this is a raw XML doc used to light up specific parts of the codebase, and not something authored for human consumption. -[2022 Roadmap]: https://github.com/microsoft/terminal/tree/main/doc/roadmap-2022.md +[2022 Roadmap]: ./roadmap-2022.md [Terminal 1.17]: https://github.com/microsoft/terminal/releases/tag/v1.17.1023 [Terminal 1.18]: https://github.com/microsoft/terminal/releases/tag/v1.18.1462.0 diff --git a/doc/specs/#1571 - New Tab Menu Customization/#1571 - New Tab Menu Customization.md b/doc/specs/#1571 - New Tab Menu Customization/#1571 - New Tab Menu Customization.md index 3d2b6532cd3..45bd669f848 100644 --- a/doc/specs/#1571 - New Tab Menu Customization/#1571 - New Tab Menu Customization.md +++ b/doc/specs/#1571 - New Tab Menu Customization/#1571 - New Tab Menu Customization.md @@ -435,7 +435,7 @@ ultimately deemed it to be out of scope for the initial spec review. [#2046]: https://github.com/microsoft/terminal/issues/2046 -[Command Palette, Addendum 1]: https://github.com/microsoft/terminal/blob/main/doc/specs/%232046%20-%20Unified%20keybindings%20and%20commands%2C%20and%20synthesized%20action%20names.md +[Command Palette, Addendum 1]: ../%232046%20-%20Unified%20keybindings%20and%20commands%2C%20and%20synthesized%20action%20names.md [#3337]: https://github.com/microsoft/terminal/issues/3337 [#6899]: https://github.com/microsoft/terminal/issues/6899 diff --git a/doc/specs/#1595 - Suggestions UI/Suggestions-UI.md b/doc/specs/#1595 - Suggestions UI/Suggestions-UI.md index 84f307c0ce6..b787e7b7bf5 100644 --- a/doc/specs/#1595 - Suggestions UI/Suggestions-UI.md +++ b/doc/specs/#1595 - Suggestions UI/Suggestions-UI.md @@ -734,7 +734,7 @@ shape of extensions will be is very much still to be determined. [#14939]: https://github.com/microsoft/terminal/issues/7285 [#keep]: https://github.com/zadjii/keep -[VsCode Tasks]: https://github.com/microsoft/terminal/blob/main/.vscode/tasks.json +[VsCode Tasks]: ../../../.vscode/tasks.json [Tasks]: https://github.com/microsoft/terminal/issues/12862 diff --git a/doc/specs/#2046 - Unified keybindings and commands, and synthesized action names.md b/doc/specs/#2046 - Unified keybindings and commands, and synthesized action names.md index de7832d7ed3..dffba83d6b6 100644 --- a/doc/specs/#2046 - Unified keybindings and commands, and synthesized action names.md +++ b/doc/specs/#2046 - Unified keybindings and commands, and synthesized action names.md @@ -605,4 +605,4 @@ as well as 3 schemes: "Scheme 1", "Scheme 2", and "Scheme 3". -[Command Palette Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%232046%20-%20Command%20Palette.md +[Command Palette Spec]: ./%232046%20-%20Command%20Palette.md diff --git a/doc/specs/#5000 - Process Model 2.0/#1032 - Elevation Quality of Life Improvements.md b/doc/specs/#5000 - Process Model 2.0/#1032 - Elevation Quality of Life Improvements.md index acac26f4d8a..4535ff304a2 100644 --- a/doc/specs/#5000 - Process Model 2.0/#1032 - Elevation Quality of Life Improvements.md +++ b/doc/specs/#5000 - Process Model 2.0/#1032 - Elevation Quality of Life Improvements.md @@ -612,8 +612,8 @@ You could have a profile that layers on an existing profile, with elevated-speci [#8514]: https://github.com/microsoft/terminal/issues/8514 [#10276]: https://github.com/microsoft/terminal/issues/10276 -[Process Model 2.0 Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%235000%20-%20Process%20Model%202.0.md -[Configuration object for profiles]: https://github.com/microsoft/terminal/blob/main/doc/specs/Configuration%20object%20for%20profiles.md -[Session Management Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%234472%20-%20Windows%20Terminal%20Session%20Management.md +[Process Model 2.0 Spec]: ../%235000%20-%20Process%20Model%202.0.md +[Configuration object for profiles]: ../%233062%20-%20Appearance configuration object for profiles.md +[Session Management Spec]: ./%234472%20-%20Windows%20Terminal%20Session%20Management.md [The Old New Thing: How can I launch an unelevated process from my elevated process, redux]: https://devblogs.microsoft.com/oldnewthing/20190425-00/?p=102443 [Workspace Trust]: https://code.visualstudio.com/docs/editor/workspace-trust diff --git a/doc/specs/#5000 - Process Model 2.0/#4472 - Windows Terminal Session Management.md b/doc/specs/#5000 - Process Model 2.0/#4472 - Windows Terminal Session Management.md index 97409d56be5..538da23725f 100644 --- a/doc/specs/#5000 - Process Model 2.0/#4472 - Windows Terminal Session Management.md +++ b/doc/specs/#5000 - Process Model 2.0/#4472 - Windows Terminal Session Management.md @@ -559,4 +559,4 @@ runtime. [Tab Tear-out in the community toolkit]: https://github.com/windows-toolkit/Sample-TabView-TearOff [Quake mode scenarios]: https://github.com/microsoft/terminal/issues/653#issuecomment-661370107 [`ISwapChainPanelNative2::SetSwapChainHandle`]: https://docs.microsoft.com/en-us/windows/win32/api/windows.ui.xaml.media.dxinterop/nf-windows-ui-xaml-media-dxinterop-iswapchainpanelnative2-setswapchainhandle -[Process Model 2.0 Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%235000%20-%20Process%20Model%202.0/%235000%20-%20Process%20Model%202.0.md +[Process Model 2.0 Spec]: ./doc/specs/%235000%20-%20Process%20Model%202.0/%235000%20-%20Process%20Model%202.0.md diff --git a/doc/specs/#653 - Quake Mode/#653 - Quake Mode.md b/doc/specs/#653 - Quake Mode/#653 - Quake Mode.md index 6b684e6386c..9bf32e33d06 100644 --- a/doc/specs/#653 - Quake Mode/#653 - Quake Mode.md +++ b/doc/specs/#653 - Quake Mode/#653 - Quake Mode.md @@ -730,7 +730,7 @@ user to differentiate between the two behaviors. [#5727]: https://github.com/microsoft/terminal/issues/5727 [#9992]: https://github.com/microsoft/terminal/issues/9992 -[Process Model 2.0 Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%235000%20-%20Process%20Model%202.0/%235000%20-%20Process%20Model%202.0.md +[Process Model 2.0 Spec]: ../%235000%20-%20Process%20Model%202.0/%235000%20-%20Process%20Model%202.0.md [Quake 3 sample]: https://youtu.be/ZmR6HQbuHPA?t=27 [`RegisterHotKey`]: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerhotkey [`dev/migrie/f/653-QUAKE-MODE`]: https://github.com/microsoft/terminal/tree/dev/migrie/f/653-QUAKE-MODE diff --git a/doc/specs/#6899 - Action IDs/#6899 - Action IDs.md b/doc/specs/#6899 - Action IDs/#6899 - Action IDs.md index 50faf9d1efb..c8c76b0d6af 100644 --- a/doc/specs/#6899 - Action IDs/#6899 - Action IDs.md +++ b/doc/specs/#6899 - Action IDs/#6899 - Action IDs.md @@ -215,8 +215,8 @@ actions manually. the tab context menu or the control context menu. -[Command Palette Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%232046%20-%20Command%20Palette.md -[New Tab Menu Customization Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%231571%20-%20New%20Tab%20Menu%20Customization.md +[Command Palette Spec]: ./doc/specs/%232046%20-%20Command%20Palette.md +[New Tab Menu Customization Spec]: ./doc/specs/%231571%20-%20New%20Tab%20Menu%20Customization.md [#1571]: https://github.com/microsoft/terminal/issues/1571 [#1912]: https://github.com/microsoft/terminal/issues/1912 diff --git a/doc/specs/#7335 - Console Allocation Policy.md b/doc/specs/#7335 - Console Allocation Policy.md new file mode 100644 index 00000000000..c094a8edfc3 --- /dev/null +++ b/doc/specs/#7335 - Console Allocation Policy.md @@ -0,0 +1,396 @@ +--- +author: Dustin Howett @DHowett +created on: 2020-08-16 +last updated: 2023-12-12 +issue id: "#7335" +--- + +# Console Allocation Policy + +## Abstract + +Due to the design of the console subsystem on Windows as it has existed since Windows 95, every application that is +stamped with the `IMAGE_SUBSYSTEM_WINDOWS_CUI` subsystem in its PE header will be allocated a console by kernel32. + +Any application that is stamped `IMAGE_SUBSYSTEM_WINDOWS_GUI` will not automatically be allocated a console. + +This has worked fine for many years: when you double-click a console application in your GUI shell, it is allocated a +console. When you run a GUI application from your console shell, it is **not** allocated a console. The shell will +**not** wait for it to exit before returning you to a prompt. + +There is a large class of applications that do not fit neatly into this mold. Take Python, Ruby, Perl, Lua, or even +VBScript: These languages are not relegated to running in a console session; they can be used to write fully-fledged GUI +applications like any other language. + +Because their interpreters are console subsystem applications, however, any user double-clicking a shortcut to a Python +or Perl application will be presented with a console window that the language runtime may choose to garbage collect, or +may choose not to. + +If the runtime chooses to hide the window, there will still be a brief period during which that window is visible. It is +inescapable. + +Likewise, any user running that GUI application from a console shell will see their shell hang until the application +terminates. + +All of these scripting languages worked around this by shipping two binaries each, identical in every way expect in +their subsystem fields. python/pythonw, perl/perlw, ruby/rubyw, wscript/cscript. + +PowerShell[^1] is waiting to deal with this problem because they don't necessarily want to ship a `pwshw.exe` for all +of their GUI-only authors. Every additional `*w` version of an application is an additional maintenance burden and +source of cognitive overhead[^2] for users. + +On the other side, you have mostly-GUI applications that want to print output to a console **if there is one +connected**. + +These applications are still primarily GUI-driven, but they might support arguments like `/?` or `--help`. They only +need a console when they need to print out some text. Sometimes they'll allocate their own console (which opens a new +window) to display in, and sometimes they'll reattach to the originating console. VSCode does the latter, and so when +you run `code` from CMD, and then `exit` CMD, your console window sticks around because VSCode is still attached to it. +It will never print anything, and your only option is to close it. + +There's another risk in reattaching, too. Given that the shell decides whether to wait based on the subsystem +field, GUI subsystem applications that reattach to their owning consoles *just to print some text* end up stomping on +the output of any shell that doesn't wait for them: + +``` +C:\> application --help + +application - the interesting application +C:\> Usage: application [OPTIONS] ... +``` + +> _(the prompt is interleaved with the output)_ + +## Solution Design + +I propose that we introduce a fusion manifest field, **consoleAllocationPolicy**, with the following values: + +* _absent_ +* `detached` + +This field allows an application to disable the automatic allocation of a console, regardless of the [process creation flags] +passed to [`CreateProcess`] and its subsystem value. + +It would look (roughly) like this: + +```xml + + + + + detached + + + +``` + +The effects of this field will only apply to binaries in the `IMAGE_SUBSYSTEM_WINDOWS_CUI` subsystem, as it pertains to +the particulars of their console allocation. + +**All console inheritance will proceed as normal.** Since this field takes effect only in the absence of console +inheritance, CUI applications will still be able to run inside an existing console session. + +| policy | behavior | +| - | - | +| _absent_ | _default behavior_ | +| `detached` | The new process is not attached to a console session (similar to `DETACHED_PROCESS`) unless one was inherited. | + +An application that specifies the `detached` allocation policy will _not_ present a console window when launched by +Explorer, Task Scheduler, etc. + +### Interaction with existing APIs + +[`CreateProcess`] supports a number of [process creation flags] that dictate how a spawned application will behave with +regards to console allocation: + +* `DETACHED_PROCESS`: No console inheritance, no console host spawned for the new process. +* `CREATE_NEW_CONSOLE`: No console inheritance, new console host **is** spawned for the new process. +* `CREATE_NO_WINDOW`: No console inheritance, new console host **is** spawned for the new process. + * this is the same as `CREATE_NEW_CONSOLE`, except that the first connection packet specifies that the window should + be invisible + +Due to the design of [`CreateProcess`] and `ShellExecute`, this specification recommends that an allocation policy of +`detached` _override_ the inclusion of `CREATE_NEW_CONSOLE` in the `dwFlags` parameter to [`CreateProcess`]. + +> **Note** +> `ShellExecute` passes `CREATE_NEW_CONSOLE` _by default_ on all invocations. This impacts our ability to resolve the +> conflicts between these two APIs--`detached` policy and `CREATE_NEW_CONSOLE`--without auditing every call site in +> every Windows application that calls `ShellExecute` on a console application. Doing so is infeasible. + +### Application impact + +An application that opts into the `detached` console allocation policy will **not** be allocated a console unless one is +inherited. This presents an issue for applications like PowerShell that do want a console window when they are launched +directly. + +Applications in this category can call `AllocConsole()` early in their startup to get fine-grained control over when a +console is presented. + +The call to `AllocConsole()` will fail safely if the application has already inherited a console handle. It will succeed +if the application does not currently have a console handle. + +> **Note** +> **Backwards Compatibility**: The behavior of `AllocConsole()` is not changing in response to this specification; +> therefore, applications that intend to run on older versions of Windows that do not support console allocation +> policies, which call `AllocConsole()`, will continue to behave normally. + +### New APIs + +Because a console-subsystem application may still want fine-grained control over when and how its console window is +spawned, we propose the inclusion of a new API, `AllocConsoleWithOptions(PALLOC_CONSOLE_OPTIONS)`. + +#### `AllocConsoleWithOptions` + +```c++ +// Console Allocation Modes +typedef enum ALLOC_CONSOLE_MODE { + ALLOC_CONSOLE_MODE_DEFAULT = 0, + ALLOC_CONSOLE_MODE_NEW_WINDOW = 1, + ALLOC_CONSOLE_MODE_NO_WINDOW = 2 +} ALLOC_CONSOLE_MODE; + +typedef enum ALLOC_CONSOLE_RESULT { + ALLOC_CONSOLE_RESULT_NO_CONSOLE = 0, + ALLOC_CONSOLE_RESULT_NEW_CONSOLE = 1, + ALLOC_CONSOLE_RESULT_EXISTING_CONSOLE = 2 +} ALLOC_CONSOLE_RESULT, *PALLOC_CONSOLE_RESULT; + +typedef +struct ALLOC_CONSOLE_OPTIONS +{ + ALLOC_CONSOLE_MODE mode; + BOOL useShowWindow; + WORD showWindow; +} ALLOC_CONSOLE_OPTIONS, *PALLOC_CONSOLE_OPTIONS; + +WINBASEAPI +HRESULT +WINAPI +AllocConsoleWithOptions(_In_opt_ PALLOC_CONSOLE_OPTIONS allocOptions, _Out_opt_ PALLOC_CONSOLE_RESULT result); +``` + +**AllocConsoleWithOptions** affords an application control over how and when it begins a console session. + +> [!NOTE] +> Unlike `AllocConsole`, `AllocConsoleWithOptions` without a mode (`ALLOC_CONSOLE_MODE_DEFAULT`) will only allocate a console if one was +> requested during `CreateProcess`. +> +> To override this behavior, pass one of `ALLOC_CONSOLE_MODE_NEW_WINDOW` (which is equivalent to being spawned with +> `CREATE_NEW_WINDOW`) or `ALLOC_CONSOLE_MODE_NO_WINDOW` (which is equivalent to being spawned with `CREATE_NO_CONSOLE`.) + +##### Parameters + +**allocOptions**: A pointer to a `ALLOC_CONSOLE_OPTIONS`. + +**result**: An optional out pointer, which will be populated with a member of the `ALLOC_CONSOLE_RESULT` enum. + +##### `ALLOC_CONSOLE_OPTIONS` + +###### Members + +**mode**: See the table below for the descriptions of the available modes. + +**useShowWindow**: Specifies whether the value in `showWindow` should be used. + +**showWindow**: If `useShowWindow` is set, specifies the ["show command"] used to display your +console window. + +###### Return Value + +`AllocConsoleWithOptions` will return `S_OK` and populate `result` to indicate whether--and how--a console session was +created. + +`AllocConsoleWithOptions` will return a failing `HRESULT` if the request could not be completed. + +###### Modes + +| Mode | Description | +|:-------------------------------:| ------------------------------------------------------------------------------------------------------------------------------ | +| `ALLOC_CONSOLE_MODE_DEFAULT` | Allocate a console session if (and how) one was requested by the parent process. | +| `ALLOC_CONSOLE_MODE_NEW_WINDOW` | Allocate a console session with a window, even if this process was created with `CREATE_NO_CONSOLE` or `DETACHED_PROCESS`. | +| `ALLOC_CONSOLE_MODE_NO_WINDOW` | Allocate a console session _without_ a window, even if this process was created with `CREATE_NEW_WINDOW` or `DETACHED_PROCESS` | + +###### Notes + +Applications seeking backwards compatibility are encouraged to delay-load `AllocConsoleWithOptions` or check for its presence in +the `api-ms-win-core-console-l1` APISet. + +## Inspiration + +Fusion manifest entries are used to make application-scoped decisions like this all the time, like `longPathAware` and +`heapType`. + +CUI applications that can spawn a UI (or GUI applications that can print to a console) are commonplace on other +platforms because there is no subsystem differentiation. + +## UI/UX Design + +There is no UI for this feature. + +## Capabilities + +### Accessibility + +This should have no impact on accessibility. + +### Security + +One reviewer brought up the potential for a malicious actor to spawn an endless stream of headless daemon processes. + +This proposal in no way changes the facilities available to malicious people for causing harm: they could have simply +used `IMAGE_SUBSYSTEM_WINDOWS_GUI` and not presented a UI--an option that has been available to them for 35 years. + +### Reliability + +This should have no impact on reliability. + +### Compatibility + +An existing application opting into **detached** may constitute a breaking change, but the scope of the breakage is +restricted to that application and is expected to be managed by the application. + +All behavioral changes are opt-in. + +> **EXAMPLE**: If Python updates python.exe to specify an allocation policy of **detached**, graphical python applications +> will become double-click runnable from the graphical shell without spawning a console window. _However_, console-based +> python applications will no longer spawn a console window when double-clicked from the graphical shell. +> +> In addition, if python.exe specifies **detached**, Console APIs will fail until a console is allocated. + +Python could work around this by calling [`AllocConsole`] or [new API `AllocConsoleWithOptions`](#allocconsolewithoptions) +if it can be detected that console I/O is required. + +#### Downlevel + +On downlevel versions of Windows that do not understand (or expect) this manifest field, applications will allocate +consoles as specified by their image subsystem (described in the [abstract](#abstract) above). + +### Performance, Power, and Efficiency + +This should have no impact on performance, power or efficiency. + +## Potential Issues + +### Shell Hang + +I am **not** proposing a change in how shells determine whether to wait for an application before returning to a prompt. +This means that a console subsystem application that intends to primarily present a UI but occasionally print text to a +console (therefore choosing the **detached** allocation policy) will cause the shell to "hang" and wait for it to +exit. + +The decision to pause/wait is made entirely in the calling shell, and the console subsystem cannot influence that +decision. + +Because the vast majority of shells on Windows "hang" by calling `WaitFor...Object` with a HANDLE to the spawned +process, an application that wants to be a "hybrid" CUI/GUI application will be forced to spawn a separate process to +detach from the shell and then terminate its main process. + +This is very similar to the forking model seen in many POSIX-compliant operating systems. + +### Launching interactively from Explorer, Task Scheduler, etc. + +Applications like PowerShell may wish to retain automatic console allocation, and **detached** would be unsuitable for +them. If PowerShell specifies the `detached` console allocation policy, launching `pwsh.exe` from File Explorer it will +no longer spawn a console. This would almost certainly break PowerShell for all users. + +Such applications can use `AllocConsole()` early in their startup. + +At the same time, PowerShell wants `-WindowStyle Hidden` to suppress the console _before it's created_. + +Applications in this category can use `AllocConsoleWithOptions()` to specify additional information about the new console window. + +PowerShell, and any other shell that wishes to maintain interactive launch from the graphical shell, can start in +**detached** mode and then allocate a console as necessary. Therefore: + +* PowerShell will set `detached` +* On startup, it will process its commandline arguments. +* If `-WindowStyle Hidden` is **not** present (the default case), it can: + * `AllocConsole()` or `AllocConsoleWithOptions(NULL)` + * Either of these APIs will present a console window (or not) based on the flags passed through `STARTUPINFO` during + [`CreateProcess`]. +* If `-WindowStyle Hidden` is present, it can: + * `AllocConsoleWithOptions(&alloc)` where `alloc.mode` specifies `ALLOC_CONSOLE_MODE_HIDDEN` + +## Future considerations + +We're introducing a new manifest field today -- what if we want to introduce more? Should we have a `consoleSettings` +manifest block? + +Are there other allocation policies we need to consider? + +## Resources + +### Rejected Solutions + +- A new PE subsystem, `IMAGE_SUBSYSTEM_WINDOWS_HYBRID` + - it would behave like **inheritOnly** + - relies on shells to update and check for this + - checking a subsystem doesn't work right with app execution aliases[^3] + - This is not a new problem, but it digs the hole a little deeper. + - requires standardization outside of Microsoft because the PE format is a dependency of the UEFI specification[^4] + - requires coordination between tooling teams both within and without Microsoft (regarding any tool that operates on + or produces PE files) + +- An exported symbol that shells can check for to determine whether to wait for the attached process to exit + - relies on shells to update and check for this + - cracking an executable to look for symbols is probably the last thing shells want to do + - we could provide an API to determine whether to wait or return? + - fragile, somewhat silly, exporting symbols from EXEs is annoying and uncommon + +An earlier version of this specification offered the **always** allocation policy, with the following behaviors: + +> **STRUCK FROM SPECIFICATION** +> +> * A GUI subsystem application would always get a console window. +> * A command-line shell would not wait for it to exit before returning a prompt. + +It was cut because a GUI application that wants a console window can simply attach to an existing console session or +allocate a new one. We found no compelling use case that would require the forced allocation of a console session +outside of the application's code. + +An earlier version of this specification offered the **inheritOnly** allocation policy, instead of the finer-grained +**hidden** and **detached** policies. We deemed it insufficient for PowerShell's use case because any application +launched by an **inheritOnly** PowerShell would immediately force the uncontrolled allocation of a console window. + +> **STRUCK FROM SPECIFICATION** +> +> The move to **hidden** allows PowerShell to offer a fully-fledged console connection that can be itself inherited by a +> downstream application. + +#### Additional allocation policies + +An earlier revision of this specification suggested two allocation policies: + +> **STRUCK FROM SPECIFICATION** +> +> **hidden** is intended to be used by console applications that want finer-grained control over the visibility of their +> console windows, but that still need a console host to service console APIs. This includes most scripting language +> interpreters. +> +> **detached** is intended to be used by primarily graphical applications that would like to operate against a console _if +> one is present_ but do not mind its absence. This includes any graphical tool with a `--help` or `/?` argument. + +The `hidden` policy was rejected due to an incompatibility with modern console hosting, as `hidden` would require an +application to interact with the console window via `GetConsoleWindow()` and explicitly show it. + +> **STRUCK FROM SPECIFICATION** +> +> ##### ShowWindow and ConPTY +> +> The pseudoconsole creates a hidden window to service `GetConsoleWindow()`, and it can be trivially shown using +> `ShowWindow`. If we recommend that applications `ShowWindow` on startup, we will need to guard the pseudoconsole's +> pseudo-window from being shown. + +[^1]: [Powershell -WindowStyle Hidden still shows a window briefly] +[^2]: [StackOverflow: pythonw.exe or python.exe?] +[^3]: [PowerShell: Windows Store applications incorrectly assumed to be console applications] +[^4]: [UEFI spec 2.6 appendix Q.1] + +[Powershell -WindowStyle Hidden still shows a window briefly]: https://github.com/PowerShell/PowerShell/issues/3028 +[PowerShell: Windows Store applications incorrectly assumed to be console applications]: https://github.com/PowerShell/PowerShell/issues/9970 +[StackOverflow: pythonw.exe or python.exe?]: https://stackoverflow.com/questions/9705982/pythonw-exe-or-python-exe +[UEFI spec 2.6 appendix Q.1]: https://www.uefi.org/sites/default/files/resources/UEFI%20Spec%202_6.pdf +[`AllocConsole`]: https://docs.microsoft.com/windows/console/allocconsole +[`CreateProcess`]: https://docs.microsoft.com/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw +[process creation flags]: https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags +["show command"]: https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-showwindow diff --git a/doc/specs/drafts/#2634 - Broadcast Input/#2634 - Broadcast Input.md b/doc/specs/drafts/#2634 - Broadcast Input/#2634 - Broadcast Input.md index 76cc91f5083..01689b8f430 100644 --- a/doc/specs/drafts/#2634 - Broadcast Input/#2634 - Broadcast Input.md +++ b/doc/specs/drafts/#2634 - Broadcast Input/#2634 - Broadcast Input.md @@ -290,7 +290,7 @@ though. **I recommend we ignore this for now, and leave this as a follow-up**. For reference, refer to the following from iTerm2: ![image](https://user-images.githubusercontent.com/2578976/64075757-fa971980-ccee-11e9-9e44-47aaf3bca76c.png) -We don't have a menu bar like on MacOS, but we do have a tab context menu. We +We don't have a menu bar like on macOS, but we do have a tab context menu. We could add these items as a nested entry under each tab. If we wanted to do this, we should also make sure to dynamically change the icon of the MenuItem to reflect the current broadcast state. diff --git a/doc/specs/drafts/#3327 - Application Theming/#3327 - Application Theming.md b/doc/specs/drafts/#3327 - Application Theming/#3327 - Application Theming.md index e0f6d865541..e956b334913 100644 --- a/doc/specs/drafts/#3327 - Application Theming/#3327 - Application Theming.md +++ b/doc/specs/drafts/#3327 - Application Theming/#3327 - Application Theming.md @@ -373,7 +373,7 @@ changes, or the active pane in a tab changes: `TabRowControl` to match. The `tab.cornerRadius` might be a bit trickier to implement. Currently, there's -not a XAML resource that controls this, nor is this something that's exposed by +no XAML resource that controls this, nor is this something that's exposed by the TabView control. Fortunately, this is something that's exposed to us programmatically. We'll need to manually set that value on each `TabViewItem` as we create new tabs. When we reload settings, we'll need to make sure to come diff --git a/doc/terminal-v2-roadmap.md b/doc/terminal-v2-roadmap.md index a7484bbe7d6..59394da3e5b 100644 --- a/doc/terminal-v2-roadmap.md +++ b/doc/terminal-v2-roadmap.md @@ -142,4 +142,4 @@ Feature Notes: [#4472]: https://github.com/microsoft/terminal/issues/4472 [#8048]: https://github.com/microsoft/terminal/pull/8048 -[Terminal 2022 Roadmap]: https://github.com/microsoft/terminal/tree/main/doc/roadmap-2022.md +[Terminal 2022 Roadmap]: ./roadmap-2022.md diff --git a/oss/libpopcnt/libpopcnt.h b/oss/libpopcnt/libpopcnt.h index ffcd976b032..de24253d9a6 100644 --- a/oss/libpopcnt/libpopcnt.h +++ b/oss/libpopcnt/libpopcnt.h @@ -95,7 +95,7 @@ #define HAVE_AVX512 #endif -#if defined(X86_OR_X64) +#if defined(X86_OR_X64) && !defined(_M_ARM64EC) /* MSVC compatible compilers (Windows) */ #if defined(_MSC_VER) /* clang-cl (LLVM 10 from 2020) requires /arch:AVX2 or diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 4036b991d81..87ab005d3bb 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -404,6 +404,18 @@ til::CoordType ROW::AdjustToGlyphStart(til::CoordType column) const noexcept return _adjustBackward(_clampedColumn(column)); } +// Returns the (exclusive) ending column of the glyph at the given column. +// In other words, if you have 3 wide glyphs +// AA BB CC +// 01 23 45 <-- column +// Examples: +// - `AdjustToGlyphEnd(4)` returns 6. +// - `AdjustToGlyphEnd(3)` returns 4. +til::CoordType ROW::AdjustToGlyphEnd(til::CoordType column) const noexcept +{ + return _adjustForward(_clampedColumnInclusive(column)); +} + // Routine Description: // - clears char data in column in row // Arguments: @@ -939,6 +951,32 @@ uint16_t ROW::size() const noexcept return _columnCount; } +// Routine Description: +// - Retrieves the column that is one after the last non-space character in the row. +til::CoordType ROW::GetLastNonSpaceColumn() const noexcept +{ + const auto text = GetText(); + const auto beg = text.begin(); + const auto end = text.end(); + auto it = end; + + for (; it != beg; --it) + { + // it[-1] is safe as `it` is always greater than `beg` (loop invariant). + if (til::at(it, -1) != L' ') + { + break; + } + } + + // We're supposed to return the measurement in cells and not characters + // and therefore simply calculating `it - beg` would be wrong. + // + // An example: The row is 10 cells wide and `it` points to the second character. + // `it - beg` would return 1, but it's possible it's actually 1 wide glyph and 8 whitespace. + return gsl::narrow_cast(GetReadableColumnCount() - (end - it)); +} + til::CoordType ROW::MeasureLeft() const noexcept { const auto text = GetText(); @@ -957,6 +995,8 @@ til::CoordType ROW::MeasureLeft() const noexcept return gsl::narrow_cast(it - beg); } +// Routine Description: +// - Retrieves the column that is one after the last valid character in the row. til::CoordType ROW::MeasureRight() const noexcept { if (_wrapForced) @@ -969,26 +1009,7 @@ til::CoordType ROW::MeasureRight() const noexcept return width; } - const auto text = GetText(); - const auto beg = text.begin(); - const auto end = text.end(); - auto it = end; - - for (; it != beg; --it) - { - // it[-1] is safe as `it` is always greater than `beg` (loop invariant). - if (til::at(it, -1) != L' ') - { - break; - } - } - - // We're supposed to return the measurement in cells and not characters - // and therefore simply calculating `it - beg` would be wrong. - // - // An example: The row is 10 cells wide and `it` points to the second character. - // `it - beg` would return 1, but it's possible it's actually 1 wide glyph and 8 whitespace. - return gsl::narrow_cast(_columnCount - (end - it)); + return GetLastNonSpaceColumn(); } bool ROW::ContainsText() const noexcept diff --git a/src/buffer/out/Row.hpp b/src/buffer/out/Row.hpp index 29f7fe18b8c..af8088c3ccd 100644 --- a/src/buffer/out/Row.hpp +++ b/src/buffer/out/Row.hpp @@ -137,6 +137,7 @@ class ROW final til::CoordType NavigateToPrevious(til::CoordType column) const noexcept; til::CoordType NavigateToNext(til::CoordType column) const noexcept; til::CoordType AdjustToGlyphStart(til::CoordType column) const noexcept; + til::CoordType AdjustToGlyphEnd(til::CoordType column) const noexcept; void ClearCell(til::CoordType column); OutputCellIterator WriteCells(OutputCellIterator it, til::CoordType columnBegin, std::optional wrap = std::nullopt, std::optional limitRight = std::nullopt); @@ -151,6 +152,7 @@ class ROW final TextAttribute GetAttrByColumn(til::CoordType column) const; std::vector GetHyperlinks() const; uint16_t size() const noexcept; + til::CoordType GetLastNonSpaceColumn() const noexcept; til::CoordType MeasureLeft() const noexcept; til::CoordType MeasureRight() const noexcept; bool ContainsText() const noexcept; diff --git a/src/buffer/out/search.cpp b/src/buffer/out/search.cpp index 9707e8fdeaa..fd8942e0bac 100644 --- a/src/buffer/out/search.cpp +++ b/src/buffer/out/search.cpp @@ -111,6 +111,28 @@ const til::point_span* Search::GetCurrent() const noexcept return nullptr; } +void Search::HighlightResults() const +{ + std::vector toSelect; + const auto& textBuffer = _renderData->GetTextBuffer(); + + for (const auto& r : _results) + { + const auto rbStart = textBuffer.BufferToScreenPosition(r.start); + const auto rbEnd = textBuffer.BufferToScreenPosition(r.end); + + til::inclusive_rect re; + re.top = rbStart.y; + re.bottom = rbEnd.y; + re.left = rbStart.x; + re.right = rbEnd.x; + + toSelect.emplace_back(re); + } + + _renderData->SelectSearchRegions(std::move(toSelect)); +} + // Routine Description: // - Takes the found word and selects it in the screen buffer @@ -127,6 +149,7 @@ bool Search::SelectCurrent() const return true; } + _renderData->ClearSelection(); return false; } diff --git a/src/buffer/out/search.h b/src/buffer/out/search.h index c2d035e3e9c..a338f1272c4 100644 --- a/src/buffer/out/search.h +++ b/src/buffer/out/search.h @@ -33,6 +33,7 @@ class Search final void FindNext() noexcept; const til::point_span* GetCurrent() const noexcept; + void HighlightResults() const; bool SelectCurrent() const; const std::vector& Results() const noexcept; diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 52a5c6e3b72..5c71dd1d398 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -126,6 +126,8 @@ void TextBuffer::_reserve(til::size screenBufferSize, const TextAttribute& defau // The compiler doesn't understand the likelihood of our branches. (PGO does, but that's imperfect.) __declspec(noinline) void TextBuffer::_commit(const std::byte* row) { + assert(row >= _commitWatermark); + const auto rowEnd = row + _bufferRowStride; const auto remaining = gsl::narrow_cast(_bufferEnd - _commitWatermark); const auto minimum = gsl::narrow_cast(rowEnd - _commitWatermark); @@ -146,7 +148,7 @@ void TextBuffer::_decommit() noexcept _commitWatermark = _buffer.get(); } -// Constructs ROWs up to (excluding) the ROW pointed to by `until`. +// Constructs ROWs between [_commitWatermark,until). void TextBuffer::_construct(const std::byte* until) noexcept { for (; _commitWatermark < until; _commitWatermark += _bufferRowStride) @@ -158,8 +160,7 @@ void TextBuffer::_construct(const std::byte* until) noexcept } } -// Destroys all previously constructed ROWs. -// Be careful! This doesn't reset any of the members, in particular the _commitWatermark. +// Destructs ROWs between [_buffer,_commitWatermark). void TextBuffer::_destroy() const noexcept { for (auto it = _buffer.get(); it < _commitWatermark; it += _bufferRowStride) @@ -168,9 +169,8 @@ void TextBuffer::_destroy() const noexcept } } -// This function is "direct" because it trusts the caller to properly wrap the "offset" -// parameter modulo the _height of the buffer, etc. But keep in mind that a offset=0 -// is the GetScratchpadRow() and not the GetRowByOffset(0). That one is offset=1. +// This function is "direct" because it trusts the caller to properly +// wrap the "offset" parameter modulo the _height of the buffer. ROW& TextBuffer::_getRowByOffsetDirect(size_t offset) { const auto row = _buffer.get() + _bufferRowStride * offset; @@ -184,6 +184,7 @@ ROW& TextBuffer::_getRowByOffsetDirect(size_t offset) return *reinterpret_cast(row); } +// See GetRowByOffset(). ROW& TextBuffer::_getRow(til::CoordType y) const { // Rows are stored circularly, so the index you ask for is offset by the start position and mod the total of rows. @@ -197,6 +198,7 @@ ROW& TextBuffer::_getRow(til::CoordType y) const } // We add 1 to the row offset, because row "0" is the one returned by GetScratchpadRow(). + // See GetScratchpadRow() for more explanation. #pragma warning(suppress : 26492) // Don't use const_cast to cast away const or volatile (type.3). return const_cast(this)->_getRowByOffsetDirect(gsl::narrow_cast(offset) + 1); } @@ -238,6 +240,9 @@ ROW& TextBuffer::GetScratchpadRow() // Returns a row filled with whitespace and the given attributes, for you to freely use. ROW& TextBuffer::GetScratchpadRow(const TextAttribute& attributes) { + // The scratchpad row is mapped to the underlying index 0, whereas all regular rows are mapped to + // index 1 and up. We do it this way instead of the other way around (scratchpad row at index _height), + // because that would force us to MEM_COMMIT the entire buffer whenever this function is called. auto& r = _getRowByOffsetDirect(0); r.Reset(attributes); return r; @@ -902,15 +907,14 @@ til::point TextBuffer::GetLastNonSpaceCharacter(const Viewport* viewOptional) co // If the X coordinate turns out to be -1, the row was empty, we need to search backwards for the real end of text. const auto viewportTop = viewport.Top(); - auto fDoBackUp = (coordEndOfText.x < 0 && coordEndOfText.y > viewportTop); // this row is empty, and we're not at the top - while (fDoBackUp) + + // while (this row is empty, and we're not at the top) + while (coordEndOfText.x < 0 && coordEndOfText.y > viewportTop) { coordEndOfText.y--; const auto& backupRow = GetRowByOffset(coordEndOfText.y); // We need to back up to the previous row if this line is empty, AND there are more rows - coordEndOfText.x = backupRow.MeasureRight() - 1; - fDoBackUp = (coordEndOfText.x < 0 && coordEndOfText.y > viewportTop); } // don't allow negative results @@ -1146,6 +1150,39 @@ void TextBuffer::Reset() noexcept _initialAttributes = _currentAttributes; } +void TextBuffer::ClearScrollback(const til::CoordType start, const til::CoordType height) +{ + if (start <= 0) + { + return; + } + + if (height <= 0) + { + _decommit(); + return; + } + + // Our goal is to move the viewport to the absolute start of the underlying memory buffer so that we can + // MEM_DECOMMIT the remaining memory. _firstRow is used to make the TextBuffer behave like a circular buffer. + // The start parameter is relative to the _firstRow. The trick to get the content to the absolute start + // is to simply add _firstRow ourselves and then reset it to 0. This causes ScrollRows() to write into + // the absolute start while reading from relative coordinates. This works because GetRowByOffset() + // operates modulo the buffer height and so the possibly-too-large startAbsolute won't be an issue. + const auto startAbsolute = _firstRow + start; + _firstRow = 0; + ScrollRows(startAbsolute, height, -startAbsolute); + + const auto end = _estimateOffsetOfLastCommittedRow(); + for (auto y = height; y <= end; ++y) + { + GetMutableRowByOffset(y).Reset(_initialAttributes); + } + + ScrollMarks(-start); + ClearMarksInRange(til::point{ 0, height }, til::point{ _width, _height }); +} + // Routine Description: // - This is the legacy screen resize with minimal changes // Arguments: @@ -1329,36 +1366,32 @@ til::point TextBuffer::_GetWordStartForAccessibility(const til::point target, co { auto result = target; const auto bufferSize = GetSize(); - auto stayAtOrigin = false; // ignore left boundary. Continue until readable text found while (_GetDelimiterClassAt(result, wordDelimiters) != DelimiterClass::RegularChar) { - if (!bufferSize.DecrementInBounds(result)) + if (result == bufferSize.Origin()) { - // first char in buffer is a DelimiterChar or ControlChar - // we can't move any further back - stayAtOrigin = true; - break; + //looped around and hit origin (no word between origin and target) + return result; } + bufferSize.DecrementInBounds(result); } // make sure we expand to the left boundary or the beginning of the word while (_GetDelimiterClassAt(result, wordDelimiters) == DelimiterClass::RegularChar) { - if (!bufferSize.DecrementInBounds(result)) + if (result == bufferSize.Origin()) { // first char in buffer is a RegularChar // we can't move any further back - break; + return result; } + bufferSize.DecrementInBounds(result); } - // move off of delimiter and onto word start - if (!stayAtOrigin && _GetDelimiterClassAt(result, wordDelimiters) != DelimiterClass::RegularChar) - { - bufferSize.IncrementInBounds(result); - } + // move off of delimiter + bufferSize.IncrementInBounds(result); return result; } @@ -1376,10 +1409,16 @@ til::point TextBuffer::_GetWordStartForSelection(const til::point target, const const auto bufferSize = GetSize(); const auto initialDelimiter = _GetDelimiterClassAt(result, wordDelimiters); + const bool isControlChar = initialDelimiter == DelimiterClass::ControlChar; // expand left until we hit the left boundary or a different delimiter class - while (result.x > bufferSize.Left() && (_GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter)) + while (result != bufferSize.Origin() && _GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter) { + //prevent selection wrapping on whitespace selection + if (isControlChar && result.x == bufferSize.Left()) + { + break; + } bufferSize.DecrementInBounds(result); } @@ -1457,25 +1496,21 @@ til::point TextBuffer::_GetWordEndForAccessibility(const til::point target, cons } else { - auto iter{ GetCellDataAt(result, bufferSize) }; - while (iter && iter.Pos() != limit && _GetDelimiterClassAt(iter.Pos(), wordDelimiters) == DelimiterClass::RegularChar) + while (result != limit && result != bufferSize.BottomRightInclusive() && _GetDelimiterClassAt(result, wordDelimiters) == DelimiterClass::RegularChar) { // Iterate through readable text - ++iter; + bufferSize.IncrementInBounds(result); } - while (iter && iter.Pos() != limit && _GetDelimiterClassAt(iter.Pos(), wordDelimiters) != DelimiterClass::RegularChar) + while (result != limit && result != bufferSize.BottomRightInclusive() && _GetDelimiterClassAt(result, wordDelimiters) != DelimiterClass::RegularChar) { // expand to the beginning of the NEXT word - ++iter; + bufferSize.IncrementInBounds(result); } - result = iter.Pos(); - - // Special case: we tried to move one past the end of the buffer, - // but iter prevented that (because that pos doesn't exist). + // Special case: we tried to move one past the end of the buffer // Manually increment onto the EndExclusive point. - if (!iter) + if (result == bufferSize.BottomRightInclusive()) { bufferSize.IncrementInBounds(result, true); } @@ -1495,19 +1530,18 @@ til::point TextBuffer::_GetWordEndForSelection(const til::point target, const st { const auto bufferSize = GetSize(); - // can't expand right - if (target.x == bufferSize.RightInclusive()) - { - return target; - } - auto result = target; const auto initialDelimiter = _GetDelimiterClassAt(result, wordDelimiters); + const bool isControlChar = initialDelimiter == DelimiterClass::ControlChar; - // expand right until we hit the right boundary or a different delimiter class - while (result.x < bufferSize.RightInclusive() && (_GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter)) + // expand right until we hit the right boundary as a ControlChar or a different delimiter class + while (result != bufferSize.BottomRightInclusive() && _GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter) { - bufferSize.IncrementInBounds(result); + if (isControlChar && result.x == bufferSize.RightInclusive()) + { + break; + } + bufferSize.IncrementInBoundsCircular(result); } if (_GetDelimiterClassAt(result, wordDelimiters) != initialDelimiter) @@ -1919,135 +1953,6 @@ void TextBuffer::_ExpandTextRow(til::inclusive_rect& textRow) const } } -// Routine Description: -// - Retrieves the text data from the selected region and presents it in a clipboard-ready format (given little post-processing). -// Arguments: -// - includeCRLF - inject CRLF pairs to the end of each line -// - trimTrailingWhitespace - remove the trailing whitespace at the end of each line -// - textRects - the rectangular regions from which the data will be extracted from the buffer (i.e.: selection rects) -// - GetAttributeColors - function used to map TextAttribute to RGB COLORREFs. If null, only extract the text. -// - formatWrappedRows - if set we will apply formatting (CRLF inclusion and whitespace trimming) on wrapped rows -// Return Value: -// - The text, background color, and foreground color data of the selected region of the text buffer. -const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF, - const bool trimTrailingWhitespace, - const std::vector& selectionRects, - std::function(const TextAttribute&)> GetAttributeColors, - const bool formatWrappedRows) const -{ - TextAndColor data; - const auto copyTextColor = GetAttributeColors != nullptr; - - // preallocate our vectors to reduce reallocs - const auto rows = selectionRects.size(); - data.text.reserve(rows); - if (copyTextColor) - { - data.FgAttr.reserve(rows); - data.BkAttr.reserve(rows); - } - - // for each row in the selection - for (size_t i = 0; i < rows; i++) - { - const auto iRow = selectionRects.at(i).top; - - const auto highlight = Viewport::FromInclusive(selectionRects.at(i)); - - // retrieve the data from the screen buffer - auto it = GetCellDataAt(highlight.Origin(), highlight); - - // allocate a string buffer - std::wstring selectionText; - std::vector selectionFgAttr; - std::vector selectionBkAttr; - - // preallocate to avoid reallocs - selectionText.reserve(gsl::narrow(highlight.Width()) + 2); // + 2 for \r\n if we munged it - if (copyTextColor) - { - selectionFgAttr.reserve(gsl::narrow(highlight.Width()) + 2); - selectionBkAttr.reserve(gsl::narrow(highlight.Width()) + 2); - } - - // copy char data into the string buffer, skipping trailing bytes - while (it) - { - const auto& cell = *it; - - if (cell.DbcsAttr() != DbcsAttribute::Trailing) - { - const auto chars = cell.Chars(); - selectionText.append(chars); - - if (copyTextColor) - { - const auto cellData = cell.TextAttr(); - const auto [CellFgAttr, CellBkAttr] = GetAttributeColors(cellData); - for (size_t j = 0; j < chars.size(); ++j) - { - selectionFgAttr.push_back(CellFgAttr); - selectionBkAttr.push_back(CellBkAttr); - } - } - } - - ++it; - } - - // We apply formatting to rows if the row was NOT wrapped or formatting of wrapped rows is allowed - const auto shouldFormatRow = formatWrappedRows || !GetRowByOffset(iRow).WasWrapForced(); - - if (trimTrailingWhitespace) - { - if (shouldFormatRow) - { - // remove the spaces at the end (aka trim the trailing whitespace) - while (!selectionText.empty() && selectionText.back() == UNICODE_SPACE) - { - selectionText.pop_back(); - if (copyTextColor) - { - selectionFgAttr.pop_back(); - selectionBkAttr.pop_back(); - } - } - } - } - - // apply CR/LF to the end of the final string, unless we're the last line. - // a.k.a if we're earlier than the bottom, then apply CR/LF. - if (includeCRLF && i < selectionRects.size() - 1) - { - if (shouldFormatRow) - { - // then we can assume a CR/LF is proper - selectionText.push_back(UNICODE_CARRIAGERETURN); - selectionText.push_back(UNICODE_LINEFEED); - - if (copyTextColor) - { - // can't see CR/LF so just use black FG & BK - const auto Blackness = RGB(0x00, 0x00, 0x00); - selectionFgAttr.push_back(Blackness); - selectionFgAttr.push_back(Blackness); - selectionBkAttr.push_back(Blackness); - selectionBkAttr.push_back(Blackness); - } - } - } - - data.text.emplace_back(std::move(selectionText)); - if (copyTextColor) - { - data.FgAttr.emplace_back(std::move(selectionFgAttr)); - data.BkAttr.emplace_back(std::move(selectionBkAttr)); - } - } - - return data; -} - size_t TextBuffer::SpanLength(const til::point coordStart, const til::point coordEnd) const { const auto bufferSize = GetSize(); @@ -2086,186 +1991,292 @@ std::wstring TextBuffer::GetPlainText(const til::point& start, const til::point& } // Routine Description: -// - Generates a CF_HTML compliant structure based on the passed in text and color data +// - Given a copy request and a row, retrieves the row bounds [begin, end) and +// a boolean indicating whether a line break should be added to this row. // Arguments: -// - rows - the text and color data we will format & encapsulate -// - backgroundColor - default background color for characters, also used in padding +// - req - the copy request +// - iRow - the row index +// - row - the row +// Return Value: +// - The row bounds and a boolean for line break +std::tuple TextBuffer::_RowCopyHelper(const TextBuffer::CopyRequest& req, const til::CoordType iRow, const ROW& row) const +{ + til::CoordType rowBeg = 0; + til::CoordType rowEnd = 0; + if (req.blockSelection) + { + const auto lineRendition = row.GetLineRendition(); + const auto minX = req.bufferCoordinates ? req.minX : ScreenToBufferLine(til::point{ req.minX, iRow }, lineRendition).x; + const auto maxX = req.bufferCoordinates ? req.maxX : ScreenToBufferLine(til::point{ req.maxX, iRow }, lineRendition).x; + + rowBeg = minX; + rowEnd = maxX + 1; // +1 to get an exclusive end + } + else + { + const auto lineRendition = row.GetLineRendition(); + const auto beg = req.bufferCoordinates ? req.beg : ScreenToBufferLine(req.beg, lineRendition); + const auto end = req.bufferCoordinates ? req.end : ScreenToBufferLine(req.end, lineRendition); + + rowBeg = iRow != beg.y ? 0 : beg.x; + rowEnd = iRow != end.y ? row.GetReadableColumnCount() : end.x + 1; // +1 to get an exclusive end + } + + // Our selection mechanism doesn't stick to glyph boundaries at the moment. + // We need to adjust begin and end points manually to avoid partially + // selected glyphs. + rowBeg = row.AdjustToGlyphStart(rowBeg); + rowEnd = row.AdjustToGlyphEnd(rowEnd); + + // When `formatWrappedRows` is set, apply formatting on all rows (wrapped + // and non-wrapped), but when it's false, format non-wrapped rows only. + const auto shouldFormatRow = req.formatWrappedRows || !row.WasWrapForced(); + + // trim trailing whitespace + if (shouldFormatRow && req.trimTrailingWhitespace) + { + rowEnd = std::min(rowEnd, row.GetLastNonSpaceColumn()); + } + + // line breaks + const auto addLineBreak = shouldFormatRow && req.includeLineBreak; + + return { rowBeg, rowEnd, addLineBreak }; +} + +// Routine Description: +// - Retrieves the text data from the buffer and presents it in a clipboard-ready format. +// Arguments: +// - req - the copy request having the bounds of the selected region and other related configuration flags. +// Return Value: +// - The text data from the selected region of the text buffer. Empty if the copy request is invalid. +std::wstring TextBuffer::GetPlainText(const CopyRequest& req) const +{ + if (req.beg > req.end) + { + return {}; + } + + std::wstring selectedText; + + for (auto iRow = req.beg.y; iRow <= req.end.y; ++iRow) + { + const auto& row = GetRowByOffset(iRow); + const auto& [rowBeg, rowEnd, addLineBreak] = _RowCopyHelper(req, iRow, row); + + // save selected text + selectedText += row.GetText(rowBeg, rowEnd); + + if (addLineBreak && iRow != req.end.y) + { + selectedText += L"\r\n"; + } + } + + return selectedText; +} + +// Routine Description: +// - Generates a CF_HTML compliant structure from the selected region of the buffer +// Arguments: +// - req - the copy request having the bounds of the selected region and other related configuration flags. // - fontHeightPoints - the unscaled font height // - fontFaceName - the name of the font used +// - backgroundColor - default background color for characters, also used in padding +// - isIntenseBold - true if being intense is treated as being bold +// - GetAttributeColors - function to get the colors of the text attributes as they're rendered // Return Value: -// - string containing the generated HTML -std::string TextBuffer::GenHTML(const TextAndColor& rows, +// - string containing the generated HTML. Empty if the copy request is invalid. +std::string TextBuffer::GenHTML(const CopyRequest& req, const int fontHeightPoints, const std::wstring_view fontFaceName, - const COLORREF backgroundColor) + const COLORREF backgroundColor, + const bool isIntenseBold, + std::function(const TextAttribute&)> GetAttributeColors) const noexcept { + // GH#5347 - Don't provide a title for the generated HTML, as many + // web applications will paste the title first, followed by the HTML + // content, which is unexpected. + + if (req.beg > req.end) + { + return {}; + } + try { - std::ostringstream htmlBuilder; + std::string htmlBuilder; - // First we have to add some standard - // HTML boiler plate required for CF_HTML - // as part of the HTML Clipboard format - const std::string htmlHeader = - ""; - htmlBuilder << htmlHeader; + // First we have to add some standard HTML boiler plate required for + // CF_HTML as part of the HTML Clipboard format + constexpr std::string_view htmlHeader = ""; + htmlBuilder += htmlHeader; - htmlBuilder << ""; + htmlBuilder += ""; // apply global style in div element { - htmlBuilder << "
"; + htmlBuilder += "\">"; } - // copy text and info color from buffer - auto hasWrittenAnyText = false; - std::optional fgColor = std::nullopt; - std::optional bkColor = std::nullopt; - for (size_t row = 0; row < rows.text.size(); row++) + for (auto iRow = req.beg.y; iRow <= req.end.y; ++iRow) { - size_t startOffset = 0; - - if (row != 0) + const auto& row = GetRowByOffset(iRow); + const auto [rowBeg, rowEnd, addLineBreak] = _RowCopyHelper(req, iRow, row); + const auto rowBegU16 = gsl::narrow_cast(rowBeg); + const auto rowEndU16 = gsl::narrow_cast(rowEnd); + const auto runs = row.Attributes().slice(rowBegU16, rowEndU16).runs(); + + auto x = rowBegU16; + for (const auto& [attr, length] : runs) { - htmlBuilder << "
"; - } - - for (size_t col = 0; col < rows.text.at(row).length(); col++) - { - const auto writeAccumulatedChars = [&](bool includeCurrent) { - if (col >= startOffset) - { - const auto unescapedText = ConvertToA(CP_UTF8, std::wstring_view(rows.text.at(row)).substr(startOffset, col - startOffset + includeCurrent)); - for (const auto c : unescapedText) - { - switch (c) - { - case '<': - htmlBuilder << "<"; - break; - case '>': - htmlBuilder << ">"; - break; - case '&': - htmlBuilder << "&"; - break; - default: - htmlBuilder << c; - } - } - - startOffset = col; - } - }; - - if (rows.text.at(row).at(col) == '\r' || rows.text.at(row).at(col) == '\n') + const auto nextX = gsl::narrow_cast(x + length); + const auto [fg, bg, ul] = GetAttributeColors(attr); + const auto fgHex = Utils::ColorToHexString(fg); + const auto bgHex = Utils::ColorToHexString(bg); + const auto ulHex = Utils::ColorToHexString(ul); + const auto ulStyle = attr.GetUnderlineStyle(); + const auto isUnderlined = ulStyle != UnderlineStyle::NoUnderline; + const auto isCrossedOut = attr.IsCrossedOut(); + const auto isOverlined = attr.IsOverlined(); + + htmlBuilder += "' instead. - writeAccumulatedChars(false); - break; + htmlBuilder += "font-weight:bold;"; } - auto colorChanged = false; - if (!fgColor.has_value() || rows.FgAttr.at(row).at(col) != fgColor.value()) + if (attr.IsItalic()) { - fgColor = rows.FgAttr.at(row).at(col); - colorChanged = true; + htmlBuilder += "font-style:italic;"; } - if (!bkColor.has_value() || rows.BkAttr.at(row).at(col) != bkColor.value()) + if (isCrossedOut || isOverlined) { - bkColor = rows.BkAttr.at(row).at(col); - colorChanged = true; + fmt::format_to(std::back_inserter(htmlBuilder), + FMT_COMPILE("text-decoration:{} {} {};"), + isCrossedOut ? "line-through" : "", + isOverlined ? "overline" : "", + fgHex); } - if (colorChanged) + if (isUnderlined) { - writeAccumulatedChars(false); + // Since underline, overline and strikethrough use the same css property, + // we cannot apply different colors to them at the same time. However, we + // can achieve the desired result by creating a nested and applying + // underline style and color to it. + htmlBuilder += "\">"; + case UnderlineStyle::NoUnderline: + break; + case UnderlineStyle::DoublyUnderlined: + fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("text-decoration:underline double {};"), ulHex); + break; + case UnderlineStyle::CurlyUnderlined: + fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("text-decoration:underline wavy {};"), ulHex); + break; + case UnderlineStyle::DottedUnderlined: + fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("text-decoration:underline dotted {};"), ulHex); + break; + case UnderlineStyle::DashedUnderlined: + fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("text-decoration:underline dashed {};"), ulHex); + break; + case UnderlineStyle::SinglyUnderlined: + default: + fmt::format_to(std::back_inserter(htmlBuilder), FMT_COMPILE("text-decoration:underline {};"), ulHex); + break; } - - htmlBuilder << ""; } - hasWrittenAnyText = true; + htmlBuilder += "\">"; + + // text + std::string unescapedText; + THROW_IF_FAILED(til::u16u8(row.GetText(x, nextX), unescapedText)); + for (const auto c : unescapedText) + { + switch (c) + { + case '<': + htmlBuilder += "<"; + break; + case '>': + htmlBuilder += ">"; + break; + case '&': + htmlBuilder += "&"; + break; + default: + htmlBuilder += c; + } + } - // if this is the last character in the row, flush the whole row - if (col == rows.text.at(row).length() - 1) + if (isUnderlined) { - writeAccumulatedChars(true); + // close the nested span we created for underline + htmlBuilder += ""; } + + htmlBuilder += ""; + + // advance to next run of text + x = nextX; } - } - if (hasWrittenAnyText) - { - // last opened span wasn't closed in loop above, so close it now - htmlBuilder << ""; + // never add line break to the last row. + if (addLineBreak && iRow < req.end.y) + { + htmlBuilder += "
"; + } } - htmlBuilder << "
"; + htmlBuilder += ""; - htmlBuilder << ""; + htmlBuilder += ""; constexpr std::string_view HtmlFooter = ""; - htmlBuilder << HtmlFooter; + htmlBuilder += HtmlFooter; // once filled with values, there will be exactly 157 bytes in the clipboard header constexpr size_t ClipboardHeaderSize = 157; // these values are byte offsets from start of clipboard const auto htmlStartPos = ClipboardHeaderSize; - const auto htmlEndPos = ClipboardHeaderSize + gsl::narrow(htmlBuilder.tellp()); + const auto htmlEndPos = ClipboardHeaderSize + gsl::narrow(htmlBuilder.length()); const auto fragStartPos = ClipboardHeaderSize + gsl::narrow(htmlHeader.length()); const auto fragEndPos = htmlEndPos - HtmlFooter.length(); // header required by HTML 0.9 format - std::ostringstream clipHeaderBuilder; - clipHeaderBuilder << "Version:0.9\r\n"; - clipHeaderBuilder << std::setfill('0'); - clipHeaderBuilder << "StartHTML:" << std::setw(10) << htmlStartPos << "\r\n"; - clipHeaderBuilder << "EndHTML:" << std::setw(10) << htmlEndPos << "\r\n"; - clipHeaderBuilder << "StartFragment:" << std::setw(10) << fragStartPos << "\r\n"; - clipHeaderBuilder << "EndFragment:" << std::setw(10) << fragEndPos << "\r\n"; - clipHeaderBuilder << "StartSelection:" << std::setw(10) << fragStartPos << "\r\n"; - clipHeaderBuilder << "EndSelection:" << std::setw(10) << fragEndPos << "\r\n"; - - return clipHeaderBuilder.str() + htmlBuilder.str(); + std::string clipHeaderBuilder; + clipHeaderBuilder += "Version:0.9\r\n"; + fmt::format_to(std::back_inserter(clipHeaderBuilder), FMT_COMPILE("StartHTML:{:0>10}\r\n"), htmlStartPos); + fmt::format_to(std::back_inserter(clipHeaderBuilder), FMT_COMPILE("EndHTML:{:0>10}\r\n"), htmlEndPos); + fmt::format_to(std::back_inserter(clipHeaderBuilder), FMT_COMPILE("StartFragment:{:0>10}\r\n"), fragStartPos); + fmt::format_to(std::back_inserter(clipHeaderBuilder), FMT_COMPILE("EndFragment:{:0>10}\r\n"), fragEndPos); + fmt::format_to(std::back_inserter(clipHeaderBuilder), FMT_COMPILE("StartSelection:{:0>10}\r\n"), fragStartPos); + fmt::format_to(std::back_inserter(clipHeaderBuilder), FMT_COMPILE("EndSelection:{:0>10}\r\n"), fragEndPos); + + return clipHeaderBuilder + htmlBuilder; } catch (...) { @@ -2275,25 +2286,36 @@ std::string TextBuffer::GenHTML(const TextAndColor& rows, } // Routine Description: -// - Generates an RTF document based on the passed in text and color data +// - Generates an RTF document from the selected region of the buffer // RTF 1.5 Spec: https://www.biblioscape.com/rtf15_spec.htm // RTF 1.9.1 Spec: https://msopenspecs.azureedge.net/files/Archive_References/[MSFT-RTF].pdf // Arguments: -// - rows - the text and color data we will format & encapsulate -// - backgroundColor - default background color for characters, also used in padding +// - req - the copy request having the bounds of the selected region and other related configuration flags. // - fontHeightPoints - the unscaled font height // - fontFaceName - the name of the font used -// - htmlTitle - value used in title tag of html header. Used to name the application +// - backgroundColor - default background color for characters, also used in padding +// - isIntenseBold - true if being intense is treated as being bold +// - GetAttributeColors - function to get the colors of the text attributes as they're rendered // Return Value: -// - string containing the generated RTF -std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoints, const std::wstring_view fontFaceName, const COLORREF backgroundColor) +// - string containing the generated RTF. Empty if the copy request is invalid. +std::string TextBuffer::GenRTF(const CopyRequest& req, + const int fontHeightPoints, + const std::wstring_view fontFaceName, + const COLORREF backgroundColor, + const bool isIntenseBold, + std::function(const TextAttribute&)> GetAttributeColors) const noexcept { + if (req.beg > req.end) + { + return {}; + } + try { - std::ostringstream rtfBuilder; + std::string rtfBuilder; // start rtf - rtfBuilder << "{"; + rtfBuilder += "{"; // Standard RTF header. // This is similar to the header generated by WordPad. @@ -2309,10 +2331,11 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi // Some features are blocked by default to maintain compatibility // with older programs (Eg. Word 97-2003). `nouicompat` disables this // behavior, and unblocks these features. See: Spec 1.9.1, Pg. 51. - rtfBuilder << "\\rtf1\\ansi\\ansicpg1252\\deff0\\nouicompat"; + rtfBuilder += "\\rtf1\\ansi\\ansicpg1252\\deff0\\nouicompat"; // font table - rtfBuilder << "{\\fonttbl{\\f0\\fmodern\\fcharset0 " << ConvertToA(CP_UTF8, fontFaceName) << ";}}"; + // Brace escape: add an extra brace (of same kind) after a brace to escape it within the format string. + fmt::format_to(std::back_inserter(rtfBuilder), FMT_COMPILE("{{\\fonttbl{{\\f0\\fmodern\\fcharset0 {};}}}}"), til::u16u8(fontFaceName)); // map to keep track of colors: // keys are colors represented by COLORREF @@ -2320,8 +2343,8 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi std::unordered_map colorMap; // RTF color table - std::ostringstream colorTableBuilder; - colorTableBuilder << "{\\colortbl ;"; + std::string colorTableBuilder; + colorTableBuilder += "{\\colortbl ;"; const auto getColorTableIndex = [&](const COLORREF color) -> size_t { // Exclude the 0 index for the default color, and start with 1. @@ -2329,103 +2352,127 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi const auto [it, inserted] = colorMap.emplace(color, colorMap.size() + 1); if (inserted) { - colorTableBuilder << "\\red" << static_cast(GetRValue(color)) - << "\\green" << static_cast(GetGValue(color)) - << "\\blue" << static_cast(GetBValue(color)) - << ";"; + const auto red = static_cast(GetRValue(color)); + const auto green = static_cast(GetGValue(color)); + const auto blue = static_cast(GetBValue(color)); + fmt::format_to(std::back_inserter(colorTableBuilder), FMT_COMPILE("\\red{}\\green{}\\blue{};"), red, green, blue); } return it->second; }; // content - std::ostringstream contentBuilder; - contentBuilder << "\\viewkind4\\uc4"; + std::string contentBuilder; + + // \viewkindN: View mode of the document to be used. N=4 specifies that the document is in Normal view. (maybe unnecessary?) + // \ucN: Number of unicode fallback characters after each codepoint. (global) + contentBuilder += "\\viewkind4\\uc1"; // paragraph styles - // \fs specifies font size in half-points i.e. \fs20 results in a font size - // of 10 pts. That's why, font size is multiplied by 2 here. - contentBuilder << "\\pard\\slmult1\\f0\\fs" << std::to_string(2 * fontHeightPoints) - // Set the background color for the page. But, the - // standard way (\cbN) to do this isn't supported in Word. - // However, the following control words sequence works - // in Word (and other RTF editors also) for applying the - // text background color. See: Spec 1.9.1, Pg. 23. - << "\\chshdng0\\chcbpat" << getColorTableIndex(backgroundColor) - << " "; - - std::optional fgColor = std::nullopt; - std::optional bkColor = std::nullopt; - for (size_t row = 0; row < rows.text.size(); ++row) + // \pard: paragraph description + // \slmultN: line-spacing multiple + // \fN: font to be used for the paragraph, where N is the font index in the font table + contentBuilder += "\\pard\\slmult1\\f0"; + + // \fsN: specifies font size in half-points. E.g. \fs20 results in a font + // size of 10 pts. That's why, font size is multiplied by 2 here. + fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\fs{}"), std::to_string(2 * fontHeightPoints)); + + // Set the background color for the page. But the standard way (\cbN) to do + // this isn't supported in Word. However, the following control words sequence + // works in Word (and other RTF editors also) for applying the text background + // color. See: Spec 1.9.1, Pg. 23. + fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\chshdng0\\chcbpat{}"), getColorTableIndex(backgroundColor)); + + for (auto iRow = req.beg.y; iRow <= req.end.y; ++iRow) { - size_t startOffset = 0; - - if (row != 0) + const auto& row = GetRowByOffset(iRow); + const auto [rowBeg, rowEnd, addLineBreak] = _RowCopyHelper(req, iRow, row); + const auto rowBegU16 = gsl::narrow_cast(rowBeg); + const auto rowEndU16 = gsl::narrow_cast(rowEnd); + const auto runs = row.Attributes().slice(rowBegU16, rowEndU16).runs(); + + auto x = rowBegU16; + for (auto& [attr, length] : runs) { - contentBuilder << "\\line "; // new line - } + const auto nextX = gsl::narrow_cast(x + length); + const auto [fg, bg, ul] = GetAttributeColors(attr); + const auto fgIdx = getColorTableIndex(fg); + const auto bgIdx = getColorTableIndex(bg); + const auto ulIdx = getColorTableIndex(ul); + const auto ulStyle = attr.GetUnderlineStyle(); - for (size_t col = 0; col < rows.text.at(row).length(); ++col) - { - const auto writeAccumulatedChars = [&](bool includeCurrent) { - if (col >= startOffset) - { - const auto text = std::wstring_view{ rows.text.at(row) }.substr(startOffset, col - startOffset + includeCurrent); - _AppendRTFText(contentBuilder, text); + // start an RTF group that can be closed later to restore the + // default attribute. + contentBuilder += "{"; - startOffset = col; - } - }; + fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\cf{}"), fgIdx); + fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\chshdng0\\chcbpat{}"), bgIdx); - if (rows.text.at(row).at(col) == '\r' || rows.text.at(row).at(col) == '\n') + if (isIntenseBold && attr.IsIntense()) { - // do not include \r nor \n as they don't have color attributes. - // For line break use \line instead. - writeAccumulatedChars(false); - break; + contentBuilder += "\\b"; } - auto colorChanged = false; - if (!fgColor.has_value() || rows.FgAttr.at(row).at(col) != fgColor.value()) + if (attr.IsItalic()) { - fgColor = rows.FgAttr.at(row).at(col); - colorChanged = true; + contentBuilder += "\\i"; } - if (!bkColor.has_value() || rows.BkAttr.at(row).at(col) != bkColor.value()) + if (attr.IsCrossedOut()) { - bkColor = rows.BkAttr.at(row).at(col); - colorChanged = true; + contentBuilder += "\\strike"; } - if (colorChanged) + switch (ulStyle) { - writeAccumulatedChars(false); - contentBuilder << "\\chshdng0\\chcbpat" << getColorTableIndex(bkColor.value()) - << "\\cf" << getColorTableIndex(fgColor.value()) - << " "; + case UnderlineStyle::NoUnderline: + break; + case UnderlineStyle::DoublyUnderlined: + fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\uldb\\ulc{}"), ulIdx); + break; + case UnderlineStyle::CurlyUnderlined: + fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\ulwave\\ulc{}"), ulIdx); + break; + case UnderlineStyle::DottedUnderlined: + fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\uld\\ulc{}"), ulIdx); + break; + case UnderlineStyle::DashedUnderlined: + fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\uldash\\ulc{}"), ulIdx); + break; + case UnderlineStyle::SinglyUnderlined: + default: + fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\ul\\ulc{}"), ulIdx); + break; } - // if this is the last character in the row, flush the whole row - if (col == rows.text.at(row).length() - 1) - { - writeAccumulatedChars(true); - } + // RTF commands and the text data must be separated by a space. + // Otherwise, if the text begins with a space then that space will + // be interpreted as part of the last command, and will be lost. + contentBuilder += " "; + + const auto unescapedText = row.GetText(x, nextX); // including character at nextX + _AppendRTFText(contentBuilder, unescapedText); + + contentBuilder += "}"; // close RTF group + + // advance to next run of text + x = nextX; } - } - // end colortbl - colorTableBuilder << "}"; + // never add line break to the last row. + if (addLineBreak && iRow < req.end.y) + { + contentBuilder += "\\line"; + } + } // add color table to the final RTF - rtfBuilder << colorTableBuilder.str(); + rtfBuilder += colorTableBuilder + "}"; // add the text content to the final RTF - rtfBuilder << contentBuilder.str(); + rtfBuilder += contentBuilder + "}"; - // end rtf - rtfBuilder << "}"; - - return rtfBuilder.str(); + return rtfBuilder; } catch (...) { @@ -2434,7 +2481,7 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi } } -void TextBuffer::_AppendRTFText(std::ostringstream& contentBuilder, const std::wstring_view& text) +void TextBuffer::_AppendRTFText(std::string& contentBuilder, const std::wstring_view& text) { for (const auto codeUnit : text) { @@ -2445,16 +2492,18 @@ void TextBuffer::_AppendRTFText(std::ostringstream& contentBuilder, const std::w case L'\\': case L'{': case L'}': - contentBuilder << "\\" << gsl::narrow(codeUnit); - break; + contentBuilder += "\\"; + [[fallthrough]]; default: - contentBuilder << gsl::narrow(codeUnit); + contentBuilder += gsl::narrow_cast(codeUnit); } } else { // Windows uses unsigned wchar_t - RTF uses signed ones. - contentBuilder << "\\u" << std::to_string(til::bit_cast(codeUnit)) << "?"; + // '?' is the fallback ascii character. + const auto codeUnitRTFStr = std::to_string(til::bit_cast(codeUnit)); + fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\u{}?"), codeUnitRTFStr); } } } diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index 8ba16f97e75..419e01471e5 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -194,6 +194,7 @@ class TextBuffer final til::point BufferToScreenPosition(const til::point position) const; void Reset() noexcept; + void ClearScrollback(const til::CoordType start, const til::CoordType height); void ResizeTraditional(const til::size newSize); @@ -229,33 +230,94 @@ class TextBuffer final std::wstring GetCustomIdFromId(uint16_t id) const; void CopyHyperlinkMaps(const TextBuffer& OtherBuffer); - class TextAndColor - { - public: - std::vector text; - std::vector> FgAttr; - std::vector> BkAttr; - }; - size_t SpanLength(const til::point coordStart, const til::point coordEnd) const; - const TextAndColor GetText(const bool includeCRLF, - const bool trimTrailingWhitespace, - const std::vector& textRects, - std::function(const TextAttribute&)> GetAttributeColors = nullptr, - const bool formatWrappedRows = false) const; - std::wstring GetPlainText(const til::point& start, const til::point& end) const; - static std::string GenHTML(const TextAndColor& rows, - const int fontHeightPoints, - const std::wstring_view fontFaceName, - const COLORREF backgroundColor); + struct CopyRequest + { + // beg and end coordinates are inclusive + til::point beg; + til::point end; + + til::CoordType minX; + til::CoordType maxX; + bool blockSelection = false; + bool trimTrailingWhitespace = true; + bool includeLineBreak = true; + bool formatWrappedRows = false; + + // whether beg, end coordinates are in buffer coordinates or screen coordinates + bool bufferCoordinates = false; + + CopyRequest() = default; + + constexpr CopyRequest(const TextBuffer& buffer, const til::point& beg, const til::point& end, const bool blockSelection, const bool includeLineBreak, const bool trimTrailingWhitespace, const bool formatWrappedRows, const bool bufferCoordinates = false) noexcept : + beg{ std::max(beg, til::point{ 0, 0 }) }, + end{ std::min(end, til::point{ buffer._width - 1, buffer._height - 1 }) }, + minX{ std::min(this->beg.x, this->end.x) }, + maxX{ std::max(this->beg.x, this->end.x) }, + blockSelection{ blockSelection }, + includeLineBreak{ includeLineBreak }, + trimTrailingWhitespace{ trimTrailingWhitespace }, + formatWrappedRows{ formatWrappedRows }, + bufferCoordinates{ bufferCoordinates } + { + } + + static CopyRequest FromConfig(const TextBuffer& buffer, + const til::point& beg, + const til::point& end, + const bool singleLine, + const bool blockSelection, + const bool trimBlockSelection, + const bool bufferCoordinates = false) noexcept + { + return { + buffer, + beg, + end, + blockSelection, + + /* includeLineBreak */ + // - SingleLine mode collapses all rows into one line, unless we're in + // block selection mode. + // - Block selection should preserve the visual structure by including + // line breaks on all rows (together with `formatWrappedRows`). + // (Selects like a box, pastes like a box) + !singleLine || blockSelection, + + /* trimTrailingWhitespace */ + // Trim trailing whitespace if we're not in single line mode and — either + // we're not in block selection mode or, we're in block selection mode and + // trimming is allowed. + !singleLine && (!blockSelection || trimBlockSelection), + + /* formatWrappedRows */ + // In block selection, we should apply formatting to wrapped rows as well. + // (Otherwise, they're only applied to non-wrapped rows.) + blockSelection, + + bufferCoordinates + }; + } + }; + + std::wstring GetPlainText(const CopyRequest& req) const; + + std::string GenHTML(const CopyRequest& req, + const int fontHeightPoints, + const std::wstring_view fontFaceName, + const COLORREF backgroundColor, + const bool isIntenseBold, + std::function(const TextAttribute&)> GetAttributeColors) const noexcept; - static std::string GenRTF(const TextAndColor& rows, - const int fontHeightPoints, - const std::wstring_view fontFaceName, - const COLORREF backgroundColor); + std::string GenRTF(const CopyRequest& req, + const int fontHeightPoints, + const std::wstring_view fontFaceName, + const COLORREF backgroundColor, + const bool isIntenseBold, + std::function(const TextAttribute&)> GetAttributeColors) const noexcept; struct PositionInformation { @@ -303,8 +365,9 @@ class TextBuffer final til::point _GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const; void _PruneHyperlinks(); void _trimMarksOutsideBuffer(); + std::tuple _RowCopyHelper(const CopyRequest& req, const til::CoordType iRow, const ROW& row) const; - static void _AppendRTFText(std::ostringstream& contentBuilder, const std::wstring_view& text); + static void _AppendRTFText(std::string& contentBuilder, const std::wstring_view& text); Microsoft::Console::Render::Renderer& _renderer; diff --git a/src/cascadia/CascadiaPackage/Package-Can.appxmanifest b/src/cascadia/CascadiaPackage/Package-Can.appxmanifest index 04c7960ce95..164ce0bca43 100644 --- a/src/cascadia/CascadiaPackage/Package-Can.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Can.appxmanifest @@ -8,13 +8,14 @@ xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4" xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" + xmlns:uap17="http://schemas.microsoft.com/appx/manifest/uap/windows10/17" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10" - IgnorableNamespaces="uap mp rescap uap3 desktop6 virtualization"> + IgnorableNamespaces="uap mp rescap uap3 uap17 desktop6 virtualization"> HKEY_CURRENT_USER\Console\%%Startup + defer + @@ -91,7 +94,7 @@ Description="Console host built from microsoft/terminal open source repository" PublicFolder="Public"> - {1F9F2BF5-5BC3-4F17-B0E6-912413F1F451} + {A854D02A-F2FE-44A5-BB24-D03F4CF830D4} @@ -102,7 +105,7 @@ Description="Terminal host built from microsoft/terminal open source repository" PublicFolder="Public"> - {051F34EE-C1FD-4B19-AF75-9BA54648434C} + {1706609C-A4CE-4C0D-B7D2-C19BF66398A5} diff --git a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest index 4a0735bb206..d8ceefaeef6 100644 --- a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest @@ -7,6 +7,7 @@ xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4" + xmlns:uap17="http://schemas.microsoft.com/appx/manifest/uap/windows10/17" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" @@ -14,7 +15,7 @@ xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10" xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" - IgnorableNamespaces="uap mp rescap uap3 desktop6 virtualization"> + IgnorableNamespaces="uap mp rescap uap3 uap17 desktop6 virtualization"> HKEY_CURRENT_USER\Console\%%Startup + defer diff --git a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest index 98fb12b9455..3a7bf26e0f7 100644 --- a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest @@ -9,13 +9,14 @@ xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4" xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" xmlns:uap7="http://schemas.microsoft.com/appx/manifest/uap/windows10/7" + xmlns:uap17="http://schemas.microsoft.com/appx/manifest/uap/windows10/17" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10" - IgnorableNamespaces="uap mp rescap uap3 desktop6 virtualization"> + IgnorableNamespaces="uap mp rescap uap3 uap17 desktop6 virtualization"> HKEY_CURRENT_USER\Console\%%Startup + defer diff --git a/src/cascadia/CascadiaPackage/Package.appxmanifest b/src/cascadia/CascadiaPackage/Package.appxmanifest index c123778d1e9..f04863da27c 100644 --- a/src/cascadia/CascadiaPackage/Package.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package.appxmanifest @@ -9,13 +9,14 @@ xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4" xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" xmlns:uap7="http://schemas.microsoft.com/appx/manifest/uap/windows10/7" + xmlns:uap17="http://schemas.microsoft.com/appx/manifest/uap/windows10/17" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10" - IgnorableNamespaces="uap mp rescap uap3 desktop6 virtualization"> + IgnorableNamespaces="uap mp rescap uap3 uap17 desktop6 virtualization"> HKEY_CURRENT_USER\Console\%%Startup + defer diff --git a/src/cascadia/LocalTests_TerminalApp/pch.h b/src/cascadia/LocalTests_TerminalApp/pch.h index 154fe5d0177..24162c0f936 100644 --- a/src/cascadia/LocalTests_TerminalApp/pch.h +++ b/src/cascadia/LocalTests_TerminalApp/pch.h @@ -70,6 +70,8 @@ Author(s): // Manually include til after we include Windows.Foundation to give it winrt superpowers #include "til.h" +#include + // Common includes for most tests: #include "../../inc/conattrs.hpp" #include "../../types/inc/utils.hpp" diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index cc09e23e1e4..43f08b5adf2 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -1066,10 +1066,7 @@ namespace winrt::TerminalApp::implementation { if (termControl.HasSelection()) { - const auto selections{ termControl.SelectedText(true) }; - - // concatenate the selection into a single line - auto searchText = std::accumulate(selections.begin(), selections.end(), std::wstring()); + std::wstring searchText{ termControl.SelectedText(true) }; // make it compact by replacing consecutive whitespaces with a single space searchText = std::regex_replace(searchText, std::wregex(LR"(\s+)"), L" "); diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index fb39ebf72fd..7ba29cb6eea 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -124,8 +124,7 @@ namespace winrt::TerminalApp::implementation return appLogic->GetSettings(); } - AppLogic::AppLogic() : - _reloadState{ std::chrono::milliseconds(100), []() { ApplicationState::SharedInstance().Reload(); } } + AppLogic::AppLogic() { // For your own sanity, it's better to do setup outside the ctor. // If you do any setup in the ctor that ends up throwing an exception, @@ -327,10 +326,6 @@ namespace winrt::TerminalApp::implementation { _reloadSettings->Run(); } - else if (ApplicationState::SharedInstance().IsStatePath(modifiedBasename)) - { - _reloadState(); - } }); } diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 695632da75a..84c586d79cd 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -91,7 +91,6 @@ namespace winrt::TerminalApp::implementation ::TerminalApp::AppCommandlineArgs _settingsAppArgs; std::shared_ptr> _reloadSettings; - til::throttled_func_trailing<> _reloadState; std::vector _warnings{}; diff --git a/src/cascadia/TerminalApp/ColorHelper.cpp b/src/cascadia/TerminalApp/ColorHelper.cpp index 7ed3f8a260c..c2dd4c2303c 100644 --- a/src/cascadia/TerminalApp/ColorHelper.cpp +++ b/src/cascadia/TerminalApp/ColorHelper.cpp @@ -1,6 +1,4 @@ -#include "pch.h" #include "ColorHelper.h" -#include using namespace winrt::TerminalApp; diff --git a/src/cascadia/TerminalApp/ColorHelper.h b/src/cascadia/TerminalApp/ColorHelper.h index cbe6582c8d1..e4ab3f42b77 100644 --- a/src/cascadia/TerminalApp/ColorHelper.h +++ b/src/cascadia/TerminalApp/ColorHelper.h @@ -1,7 +1,6 @@ #pragma once -#include "pch.h" -#include +#include namespace winrt::TerminalApp { diff --git a/src/cascadia/TerminalApp/DebugTapConnection.cpp b/src/cascadia/TerminalApp/DebugTapConnection.cpp index 0058460f95d..789c1f21395 100644 --- a/src/cascadia/TerminalApp/DebugTapConnection.cpp +++ b/src/cascadia/TerminalApp/DebugTapConnection.cpp @@ -50,6 +50,7 @@ namespace winrt::Microsoft::TerminalApp::implementation void TerminalOutput(const winrt::event_token& token) noexcept { _wrappedConnection.TerminalOutput(token); }; winrt::event_token StateChanged(const TypedEventHandler& handler) { return _wrappedConnection.StateChanged(handler); }; void StateChanged(const winrt::event_token& token) noexcept { _wrappedConnection.StateChanged(token); }; + winrt::guid SessionId() const noexcept { return {}; } ConnectionState State() const noexcept { return _wrappedConnection.State(); } private: @@ -98,6 +99,15 @@ namespace winrt::Microsoft::TerminalApp::implementation _wrappedConnection = nullptr; } + guid DebugTapConnection::SessionId() const noexcept + { + if (const auto c = _wrappedConnection.get()) + { + return c.SessionId(); + } + return {}; + } + ConnectionState DebugTapConnection::State() const noexcept { if (auto strongConnection{ _wrappedConnection.get() }) diff --git a/src/cascadia/TerminalApp/DebugTapConnection.h b/src/cascadia/TerminalApp/DebugTapConnection.h index d202e563e92..34282cb2464 100644 --- a/src/cascadia/TerminalApp/DebugTapConnection.h +++ b/src/cascadia/TerminalApp/DebugTapConnection.h @@ -19,6 +19,8 @@ namespace winrt::Microsoft::TerminalApp::implementation void WriteInput(const hstring& data); void Resize(uint32_t rows, uint32_t columns); void Close(); + + winrt::guid SessionId() const noexcept; winrt::Microsoft::Terminal::TerminalConnection::ConnectionState State() const noexcept; void SetInputTap(const Microsoft::Terminal::TerminalConnection::ITerminalConnection& inputTap); diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index b24eda43144..dc386b431ff 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -246,7 +246,9 @@ IPaneContent.idl - + + NotUsing + Create diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index aea70fe6dad..4198c2d9a49 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1210,7 +1210,7 @@ namespace winrt::TerminalApp::implementation TerminalConnection::ITerminalConnection connection{ nullptr }; auto connectionType = profile.ConnectionType(); - winrt::guid sessionGuid{}; + Windows::Foundation::Collections::ValueSet valueSet; if (connectionType == TerminalConnection::AzureConnection::ConnectionType() && TerminalConnection::AzureConnection::IsAzureConnectionAvailable()) @@ -1226,23 +1226,16 @@ namespace winrt::TerminalApp::implementation connection = TerminalConnection::ConptyConnection{}; } - auto valueSet = TerminalConnection::ConptyConnection::CreateSettings(azBridgePath.native(), - L".", - L"Azure", - false, - L"", - nullptr, - settings.InitialRows(), - settings.InitialCols(), - winrt::guid(), - profile.Guid()); - - if constexpr (Feature_VtPassthroughMode::IsEnabled()) - { - valueSet.Insert(L"passthroughMode", Windows::Foundation::PropertyValue::CreateBoolean(settings.VtPassthrough())); - } - - connection.Initialize(valueSet); + valueSet = TerminalConnection::ConptyConnection::CreateSettings(azBridgePath.native(), + L".", + L"Azure", + false, + L"", + nullptr, + settings.InitialRows(), + settings.InitialCols(), + winrt::guid(), + profile.Guid()); } else @@ -1267,38 +1260,38 @@ namespace winrt::TerminalApp::implementation // process until later, on another thread, after we've already // restored the CWD to its original value. auto newWorkingDirectory{ _evaluatePathForCwd(settings.StartingDirectory()) }; - auto conhostConn = TerminalConnection::ConptyConnection(); - auto valueSet = TerminalConnection::ConptyConnection::CreateSettings(settings.Commandline(), - newWorkingDirectory, - settings.StartingTitle(), - settings.ReloadEnvironmentVariables(), - _WindowProperties.VirtualEnvVars(), - environment, - settings.InitialRows(), - settings.InitialCols(), - winrt::guid(), - profile.Guid()); - - valueSet.Insert(L"passthroughMode", Windows::Foundation::PropertyValue::CreateBoolean(settings.VtPassthrough())); + connection = TerminalConnection::ConptyConnection{}; + valueSet = TerminalConnection::ConptyConnection::CreateSettings(settings.Commandline(), + newWorkingDirectory, + settings.StartingTitle(), + settings.ReloadEnvironmentVariables(), + _WindowProperties.VirtualEnvVars(), + environment, + settings.InitialRows(), + settings.InitialCols(), + winrt::guid(), + profile.Guid()); if (inheritCursor) { valueSet.Insert(L"inheritCursor", Windows::Foundation::PropertyValue::CreateBoolean(true)); } + } - conhostConn.Initialize(valueSet); - - sessionGuid = conhostConn.Guid(); - connection = conhostConn; + if constexpr (Feature_VtPassthroughMode::IsEnabled()) + { + valueSet.Insert(L"passthroughMode", Windows::Foundation::PropertyValue::CreateBoolean(settings.VtPassthrough())); } + connection.Initialize(valueSet); + TraceLoggingWrite( g_hTerminalAppProvider, "ConnectionCreated", TraceLoggingDescription("Event emitted upon the creation of a connection"), TraceLoggingGuid(connectionType, "ConnectionTypeGuid", "The type of the connection"), TraceLoggingGuid(profile.Guid(), "ProfileGuid", "The profile's GUID"), - TraceLoggingGuid(sessionGuid, "SessionGuid", "The WT_SESSION's GUID"), + TraceLoggingGuid(connection.SessionId(), "SessionGuid", "The WT_SESSION's GUID"), TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); @@ -2607,12 +2600,9 @@ namespace winrt::TerminalApp::implementation auto dataPack = DataPackage(); dataPack.RequestedOperation(DataPackageOperation::Copy); - // The EventArgs.Formats() is an override for the global setting "copyFormatting" - // iff it is set - auto useGlobal = copiedData.Formats() == nullptr; - auto copyFormats = useGlobal ? - _settings.GlobalSettings().CopyFormatting() : - copiedData.Formats().Value(); + const auto copyFormats = copiedData.Formats() != nullptr ? + copiedData.Formats().Value() : + static_cast(0); // copy text to dataPack dataPack.SetText(copiedData.Text()); @@ -2645,6 +2635,75 @@ namespace winrt::TerminalApp::implementation CATCH_LOG(); } + static wil::unique_close_clipboard_call _openClipboard(HWND hwnd) + { + bool success = false; + + // OpenClipboard may fail to acquire the internal lock --> retry. + for (DWORD sleep = 10;; sleep *= 2) + { + if (OpenClipboard(hwnd)) + { + success = true; + break; + } + // 10 iterations + if (sleep > 10000) + { + break; + } + Sleep(sleep); + } + + return wil::unique_close_clipboard_call{ success }; + } + + static winrt::hstring _extractClipboard() + { + // This handles most cases of pasting text as the OS converts most formats to CF_UNICODETEXT automatically. + if (const auto handle = GetClipboardData(CF_UNICODETEXT)) + { + const wil::unique_hglobal_locked lock{ handle }; + const auto str = static_cast(lock.get()); + if (!str) + { + return {}; + } + + const auto maxLen = GlobalSize(handle) / sizeof(wchar_t); + const auto len = wcsnlen(str, maxLen); + return winrt::hstring{ str, gsl::narrow_cast(len) }; + } + + // We get CF_HDROP when a user copied a file with Ctrl+C in Explorer and pastes that into the terminal (among others). + if (const auto handle = GetClipboardData(CF_HDROP)) + { + const wil::unique_hglobal_locked lock{ handle }; + const auto drop = static_cast(lock.get()); + if (!drop) + { + return {}; + } + + const auto cap = DragQueryFileW(drop, 0, nullptr, 0); + if (cap == 0) + { + return {}; + } + + auto buffer = winrt::impl::hstring_builder{ cap }; + const auto len = DragQueryFileW(drop, 0, buffer.data(), cap + 1); + if (len == 0) + { + return {}; + } + + return buffer.to_hstring(); + } + + return {}; + } + // Function Description: // - This function is called when the `TermControl` requests that we send // it the clipboard's content. @@ -2664,53 +2723,14 @@ namespace winrt::TerminalApp::implementation const auto weakThis = get_weak(); const auto dispatcher = Dispatcher(); const auto globalSettings = _settings.GlobalSettings(); - winrt::hstring text; // GetClipboardData might block for up to 30s for delay-rendered contents. co_await winrt::resume_background(); + winrt::hstring text; + if (const auto clipboard = _openClipboard(nullptr)) { - // According to various reports on the internet, OpenClipboard might - // fail to acquire the internal lock, for instance due to rdpclip.exe. - for (int attempts = 1;;) - { - if (OpenClipboard(nullptr)) - { - break; - } - - if (attempts > 5) - { - co_return; - } - - attempts++; - Sleep(10 * attempts); - } - - const auto clipboardCleanup = wil::scope_exit([]() { - CloseClipboard(); - }); - - const auto data = GetClipboardData(CF_UNICODETEXT); - if (!data) - { - co_return; - } - - const auto str = static_cast(GlobalLock(data)); - if (!str) - { - co_return; - } - - const auto dataCleanup = wil::scope_exit([&]() { - GlobalUnlock(data); - }); - - const auto maxLength = GlobalSize(data) / sizeof(wchar_t); - const auto length = wcsnlen(str, maxLength); - text = winrt::hstring{ str, gsl::narrow_cast(length) }; + text = _extractClipboard(); } if (globalSettings.TrimPaste()) diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index 80e87a750b0..d298f2ec571 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -121,12 +121,7 @@ namespace winrt::TerminalApp::implementation void TerminalTab::_BellIndicatorTimerTick(const Windows::Foundation::IInspectable& /*sender*/, const Windows::Foundation::IInspectable& /*e*/) { ShowBellIndicator(false); - // Just do a sanity check that the timer still exists before we stop it - if (_bellIndicatorTimer.has_value()) - { - _bellIndicatorTimer->Stop(); - _bellIndicatorTimer = std::nullopt; - } + _bellIndicatorTimer.Stop(); } // Method Description: @@ -367,14 +362,13 @@ namespace winrt::TerminalApp::implementation { ASSERT_UI_THREAD(); - if (!_bellIndicatorTimer.has_value()) + if (!_bellIndicatorTimer) { - DispatcherTimer bellIndicatorTimer; - bellIndicatorTimer.Interval(std::chrono::milliseconds(2000)); - bellIndicatorTimer.Tick({ get_weak(), &TerminalTab::_BellIndicatorTimerTick }); - bellIndicatorTimer.Start(); - _bellIndicatorTimer.emplace(std::move(bellIndicatorTimer)); + _bellIndicatorTimer.Interval(std::chrono::milliseconds(2000)); + _bellIndicatorTimer.Tick({ get_weak(), &TerminalTab::_BellIndicatorTimerTick }); } + + _bellIndicatorTimer.Start(); } // Method Description: @@ -965,9 +959,14 @@ namespace winrt::TerminalApp::implementation events.TitleChanged = content.TitleChanged( winrt::auto_revoke, [dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + // The lambda lives in the `std::function`-style container owned by `control`. That is, when the + // `control` gets destroyed the lambda struct also gets destroyed. In other words, we need to + // copy `weakThis` onto the stack, because that's the only thing that gets captured in coroutines. + // See: https://devblogs.microsoft.com/oldnewthing/20211103-00/?p=105870 + const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); // Check if Tab's lifetime has expired - if (auto tab{ weakThis.get() }) + if (auto tab{ weakThisCopy.get() }) { // The title of the control changed, but not necessarily the title of the tab. // Set the tab's text to the active panes' text. @@ -978,8 +977,9 @@ namespace winrt::TerminalApp::implementation events.TabColorChanged = content.TabColorChanged( winrt::auto_revoke, [dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); - if (auto tab{ weakThis.get() }) + if (auto tab{ weakThisCopy.get() }) { // The control's tabColor changed, but it is not necessarily the // active control in this tab. We'll just recalculate the @@ -991,9 +991,10 @@ namespace winrt::TerminalApp::implementation events.TaskbarProgressChanged = content.TaskbarProgressChanged( winrt::auto_revoke, [dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); // Check if Tab's lifetime has expired - if (auto tab{ weakThis.get() }) + if (auto tab{ weakThisCopy.get() }) { tab->_UpdateProgressState(); } @@ -1002,8 +1003,9 @@ namespace winrt::TerminalApp::implementation events.ConnectionStateChanged = content.ConnectionStateChanged( winrt::auto_revoke, [dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); - if (auto tab{ weakThis.get() }) + if (auto tab{ weakThisCopy.get() }) { tab->_UpdateConnectionClosedState(); } @@ -1012,6 +1014,7 @@ namespace winrt::TerminalApp::implementation events.ReadOnlyChanged = content.ReadOnlyChanged( winrt::auto_revoke, [dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); if (auto tab{ weakThis.get() }) { @@ -1022,8 +1025,9 @@ namespace winrt::TerminalApp::implementation events.FocusRequested = content.FocusRequested( winrt::auto_revoke, [dispatcher, weakThis](auto sender, auto) -> winrt::fire_and_forget { + const auto weakThisCopy = weakThis; co_await wil::resume_foreground(dispatcher); - if (const auto tab{ weakThis.get() }) + if (const auto tab{ weakThisCopy.get() }) { if (tab->_focused()) { @@ -1715,6 +1719,18 @@ namespace winrt::TerminalApp::implementation return _zoomedPane != nullptr; } + TermControl _termControlFromPane(const auto& pane) + { + if (const auto content{ pane->GetContent() }) + { + if (const auto termContent{ content.try_as() }) + { + return termContent.GetTerminal(); + } + } + return nullptr; + } + // Method Description: // - Toggle read-only mode on the active pane // - If a parent pane is selected, this will ensure that all children have @@ -1726,14 +1742,14 @@ namespace winrt::TerminalApp::implementation auto hasReadOnly = false; auto allReadOnly = true; _activePane->WalkTree([&](const auto& p) { - if (const auto& control{ p->GetTerminalControl() }) + if (const auto& control{ _termControlFromPane(p) }) { hasReadOnly |= control.ReadOnly(); allReadOnly &= control.ReadOnly(); } }); _activePane->WalkTree([&](const auto& p) { - if (const auto& control{ p->GetTerminalControl() }) + if (const auto& control{ _termControlFromPane(p) }) { // If all controls have the same read only state then just toggle if (allReadOnly || !hasReadOnly) @@ -1758,14 +1774,14 @@ namespace winrt::TerminalApp::implementation auto hasReadOnly = false; auto allReadOnly = true; _activePane->WalkTree([&](const auto& p) { - if (const auto& control{ p->GetTerminalControl() }) + if (const auto& control{ _termControlFromPane(p) }) { hasReadOnly |= control.ReadOnly(); allReadOnly &= control.ReadOnly(); } }); _activePane->WalkTree([&](const auto& p) { - if (const auto& control{ p->GetTerminalControl() }) + if (const auto& control{ _termControlFromPane(p) }) { // If all controls have the same read only state then just disable if (allReadOnly || !hasReadOnly) @@ -1850,7 +1866,7 @@ namespace winrt::TerminalApp::implementation { return; } - if (const auto& control{ p->GetTerminalControl() }) + if (const auto& control{ _termControlFromPane(p) }) { auto it = _contentEvents.find(*paneId); if (it != _contentEvents.end()) diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/TerminalTab.h index 1030e00e087..df1e9102698 100644 --- a/src/cascadia/TerminalApp/TerminalTab.h +++ b/src/cascadia/TerminalApp/TerminalTab.h @@ -155,7 +155,7 @@ namespace winrt::TerminalApp::implementation void _Setup(); - std::optional _bellIndicatorTimer; + SafeDispatcherTimer _bellIndicatorTimer; void _BellIndicatorTimerTick(const Windows::Foundation::IInspectable& sender, const Windows::Foundation::IInspectable& e); void _MakeTabViewItem() override; diff --git a/src/cascadia/TerminalApp/Toast.h b/src/cascadia/TerminalApp/Toast.h index 40d3df7cf55..63b2b5a0770 100644 --- a/src/cascadia/TerminalApp/Toast.h +++ b/src/cascadia/TerminalApp/Toast.h @@ -34,5 +34,5 @@ class Toast : public std::enable_shared_from_this private: winrt::Microsoft::UI::Xaml::Controls::TeachingTip _tip; - winrt::Windows::UI::Xaml::DispatcherTimer _timer; + SafeDispatcherTimer _timer; }; diff --git a/src/cascadia/TerminalApp/pch.h b/src/cascadia/TerminalApp/pch.h index c4a75ff4350..111431c2a09 100644 --- a/src/cascadia/TerminalApp/pch.h +++ b/src/cascadia/TerminalApp/pch.h @@ -84,6 +84,8 @@ TRACELOGGING_DECLARE_PROVIDER(g_hTerminalAppProvider); #include "til.h" #include +#include + #include #include // must go after the CoreDispatcher type is defined diff --git a/src/cascadia/TerminalConnection/AzureConnection.cpp b/src/cascadia/TerminalConnection/AzureConnection.cpp index 3844979b488..157d888f65c 100644 --- a/src/cascadia/TerminalConnection/AzureConnection.cpp +++ b/src/cascadia/TerminalConnection/AzureConnection.cpp @@ -77,8 +77,14 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation { if (settings) { - _initialRows = gsl::narrow(winrt::unbox_value_or(settings.TryLookup(L"initialRows").try_as(), _initialRows)); - _initialCols = gsl::narrow(winrt::unbox_value_or(settings.TryLookup(L"initialCols").try_as(), _initialCols)); + _initialRows = unbox_prop_or(settings, L"initialRows", _initialRows); + _initialCols = unbox_prop_or(settings, L"initialCols", _initialCols); + _sessionId = unbox_prop_or(settings, L"sessionId", _sessionId); + } + + if (_sessionId == guid{}) + { + _sessionId = Utils::CreateGuid(); } } @@ -398,6 +404,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation switch (bufferType) { + case WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE: case WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE: case WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE: { @@ -603,6 +610,15 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation // Wait for user authentication and obtain the access/refresh tokens auto authenticatedResponse = _WaitForUser(devCode, pollInterval, expiresIn); + + // If user closed tab, `_WaitForUser` returns nullptr + // This also occurs if the connection times out, when polling time exceeds the expiry time + if (!authenticatedResponse) + { + _transitionToState(ConnectionState::Failed); + return; + } + _setAccessToken(authenticatedResponse.GetNamedString(L"access_token")); _refreshToken = authenticatedResponse.GetNamedString(L"refresh_token"); @@ -797,7 +813,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation // - an optional HTTP method (defaults to POST if content is present, GET otherwise) // Return value: // - the response from the server as a json value - WDJ::JsonObject AzureConnection::_SendRequestReturningJson(std::wstring_view uri, const WWH::IHttpContent& content, WWH::HttpMethod method) + WDJ::JsonObject AzureConnection::_SendRequestReturningJson(std::wstring_view uri, const WWH::IHttpContent& content, WWH::HttpMethod method, const Windows::Foundation::Uri referer) { if (!method) { @@ -810,6 +826,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation auto headers{ request.Headers() }; headers.Accept().TryParseAdd(L"application/json"); + if (referer) + { + headers.Referer(referer); + } + const auto response{ _httpClient.SendRequestAsync(request).get() }; const auto string{ response.Content().ReadAsStringAsync().get() }; const auto jsonResult{ WDJ::JsonObject::Parse(string) }; @@ -974,17 +995,56 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation auto uri{ fmt::format(L"{}terminals?cols={}&rows={}&version=2019-01-01&shell={}", _cloudShellUri, _initialCols, _initialRows, shellType) }; WWH::HttpStringContent content{ - L"", + L"{}", WSS::UnicodeEncoding::Utf8, // LOAD-BEARING. the API returns "'content-type' should be 'application/json' or 'multipart/form-data'" L"application/json" }; - const auto terminalResponse = _SendRequestReturningJson(uri, content); + const auto terminalResponse = _SendRequestReturningJson(uri, content, WWH::HttpMethod::Post(), Windows::Foundation::Uri(_cloudShellUri)); _terminalID = terminalResponse.GetNamedString(L"id"); + // we have to do some post-handling to get the proper socket endpoint + // the logic here is based on the way the cloud shell team itself does it + winrt::hstring finalSocketUri; + const std::wstring_view wCloudShellUri{ _cloudShellUri }; + + if (wCloudShellUri.find(L"servicebus") == std::wstring::npos) + { + // wCloudShellUri does not contain the word "servicebus", we can just use it to make the final URI + + // remove the "https" from the cloud shell URI + const auto uriWithoutProtocol = wCloudShellUri.substr(5); + + finalSocketUri = fmt::format(FMT_COMPILE(L"wss{}terminals/{}"), uriWithoutProtocol, _terminalID); + } + else + { + // if wCloudShellUri contains the word "servicebus", that means the returned socketUri is of the form + // wss://ccon-prod-westus-aci-03.servicebus.windows.net/cc-AAAA-AAAAAAAA//aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + // we need to change it to: + // wss://ccon-prod-westus-aci-03.servicebus.windows.net/$hc/cc-AAAA-AAAAAAAA/terminals/aaaaaaaaaaaaaaaaaaaaaa + + const auto socketUri = terminalResponse.GetNamedString(L"socketUri"); + const std::wstring_view wSocketUri{ socketUri }; + + // get the substring up until the ".net" + const auto dotNetStart = wSocketUri.find(L".net"); + THROW_HR_IF(E_UNEXPECTED, dotNetStart == std::wstring::npos); + const auto dotNetEnd = dotNetStart + 4; + const auto wSocketUriBody = wSocketUri.substr(0, dotNetEnd); + + // get the portion between the ".net" and the "//" (this is the cc-AAAA-AAAAAAAA part) + const auto lastDoubleSlashPos = wSocketUri.find_last_of(L"//"); + THROW_HR_IF(E_UNEXPECTED, lastDoubleSlashPos == std::wstring::npos); + const auto wSocketUriMiddle = wSocketUri.substr(dotNetEnd, lastDoubleSlashPos - (dotNetEnd)); + + // piece together the final uri, adding in the "$hc" and "terminals" where needed + finalSocketUri = fmt::format(FMT_COMPILE(L"{}/$hc{}terminals/{}"), wSocketUriBody, wSocketUriMiddle, _terminalID); + } + // Return the uri - return terminalResponse.GetNamedString(L"socketUri"); + return winrt::hstring{ finalSocketUri }; } // Method description: diff --git a/src/cascadia/TerminalConnection/AzureConnection.h b/src/cascadia/TerminalConnection/AzureConnection.h index cedd76757af..f8a47b5009d 100644 --- a/src/cascadia/TerminalConnection/AzureConnection.h +++ b/src/cascadia/TerminalConnection/AzureConnection.h @@ -8,12 +8,12 @@ #include #include -#include "ConnectionStateHolder.h" +#include "BaseTerminalConnection.h" #include "AzureClient.h" namespace winrt::Microsoft::Terminal::TerminalConnection::implementation { - struct AzureConnection : AzureConnectionT, ConnectionStateHolder + struct AzureConnection : AzureConnectionT, BaseTerminalConnection { static winrt::guid ConnectionType() noexcept; static bool IsAzureConnectionAvailable() noexcept; @@ -68,7 +68,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation void _WriteStringWithNewline(const std::wstring_view str); void _WriteCaughtExceptionRecord(); - winrt::Windows::Data::Json::JsonObject _SendRequestReturningJson(std::wstring_view uri, const winrt::Windows::Web::Http::IHttpContent& content = nullptr, winrt::Windows::Web::Http::HttpMethod method = nullptr); + winrt::Windows::Data::Json::JsonObject _SendRequestReturningJson(std::wstring_view uri, const winrt::Windows::Web::Http::IHttpContent& content = nullptr, winrt::Windows::Web::Http::HttpMethod method = nullptr, const winrt::Windows::Foundation::Uri referer = nullptr); void _setAccessToken(std::wstring_view accessToken); winrt::Windows::Data::Json::JsonObject _GetDeviceCode(); winrt::Windows::Data::Json::JsonObject _WaitForUser(const winrt::hstring& deviceCode, int pollInterval, int expiresIn); diff --git a/src/cascadia/TerminalConnection/ConnectionStateHolder.h b/src/cascadia/TerminalConnection/BaseTerminalConnection.h similarity index 84% rename from src/cascadia/TerminalConnection/ConnectionStateHolder.h rename to src/cascadia/TerminalConnection/BaseTerminalConnection.h index 947e16788f3..3cc98627c92 100644 --- a/src/cascadia/TerminalConnection/ConnectionStateHolder.h +++ b/src/cascadia/TerminalConnection/BaseTerminalConnection.h @@ -4,13 +4,28 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation { template - struct ConnectionStateHolder + struct BaseTerminalConnection { public: - ConnectionState State() const noexcept { return _connectionState; } + winrt::guid SessionId() const noexcept + { + return _sessionId; + } + + ConnectionState State() const noexcept + { + return _connectionState; + } + TYPED_EVENT(StateChanged, ITerminalConnection, winrt::Windows::Foundation::IInspectable); protected: + template + U unbox_prop_or(const Windows::Foundation::Collections::ValueSet& blob, std::wstring_view key, U defaultValue) + { + return winrt::unbox_value_or(blob.TryLookup(key).try_as(), defaultValue); + } + #pragma warning(push) #pragma warning(disable : 26447) // Analyzer is still upset about noexcepts throwing even with function level try. // Method Description: @@ -86,6 +101,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation return _isStateOneOf(ConnectionState::Connected); } + winrt::guid _sessionId{}; + private: std::atomic _connectionState{ ConnectionState::NotConnected }; mutable std::mutex _stateMutex; diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 5b20f0d6ef1..67c3e5699bd 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -85,18 +85,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation auto environment = _initialEnv; { - // Convert connection Guid to string and ignore the enclosing '{}'. - auto wsGuid{ Utils::GuidToString(_guid) }; - wsGuid.pop_back(); - - const auto guidSubStr = std::wstring_view{ wsGuid }.substr(1); - // Ensure every connection has the unique identifier in the environment. - environment.as_map().insert_or_assign(L"WT_SESSION", guidSubStr.data()); + // Convert connection Guid to string and ignore the enclosing '{}'. + environment.as_map().insert_or_assign(L"WT_SESSION", Utils::GuidToPlainString(_sessionId)); // The profile Guid does include the enclosing '{}' - const auto profileGuid{ Utils::GuidToString(_profileGuid) }; - environment.as_map().insert_or_assign(L"WT_PROFILE_ID", profileGuid.data()); + environment.as_map().insert_or_assign(L"WT_PROFILE_ID", Utils::GuidToString(_profileGuid)); // WSLENV is a colon-delimited list of environment variables (+flags) that should appear inside WSL // https://devblogs.microsoft.com/commandline/share-environment-vars-between-wsl-and-windows/ @@ -171,7 +165,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation g_hTerminalConnectionProvider, "ConPtyConnected", TraceLoggingDescription("Event emitted when ConPTY connection is started"), - TraceLoggingGuid(_guid, "SessionGuid", "The WT_SESSION's GUID"), + TraceLoggingGuid(_sessionId, "SessionGuid", "The WT_SESSION's GUID"), TraceLoggingWideString(_clientName.c_str(), "Client", "The attached client process"), TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); @@ -189,7 +183,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation TERMINAL_STARTUP_INFO startupInfo) : _rows{ 25 }, _cols{ 80 }, - _guid{ Utils::CreateGuid() }, _inPipe{ hIn }, _outPipe{ hOut } { @@ -249,12 +242,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation return vs; } - template - T unbox_prop_or(const Windows::Foundation::Collections::ValueSet& blob, std::wstring_view key, T defaultValue) - { - return winrt::unbox_value_or(blob.TryLookup(key).try_as(), defaultValue); - } - void ConptyConnection::Initialize(const Windows::Foundation::Collections::ValueSet& settings) { if (settings) @@ -268,7 +255,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation _startingTitle = unbox_prop_or(settings, L"startingTitle", _startingTitle); _rows = unbox_prop_or(settings, L"initialRows", _rows); _cols = unbox_prop_or(settings, L"initialCols", _cols); - _guid = unbox_prop_or(settings, L"guid", _guid); + _sessionId = unbox_prop_or(settings, L"sessionId", _sessionId); _environment = settings.TryLookup(L"environment").try_as(); if constexpr (Feature_VtPassthroughMode::IsEnabled()) { @@ -299,17 +286,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation _initialEnv = til::env::from_current_environment(); } } - - if (_guid == guid{}) - { - _guid = Utils::CreateGuid(); - } } - } - winrt::guid ConptyConnection::Guid() const noexcept - { - return _guid; + if (_sessionId == guid{}) + { + _sessionId = Utils::CreateGuid(); + } } winrt::hstring ConptyConnection::Commandline() const @@ -382,7 +364,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation g_hTerminalConnectionProvider, "ConPtyConnectedToDefterm", TraceLoggingDescription("Event emitted when ConPTY connection is started, for a defterm session"), - TraceLoggingGuid(_guid, "SessionGuid", "The WT_SESSION's GUID"), + TraceLoggingGuid(_sessionId, "SessionGuid", "The WT_SESSION's GUID"), TraceLoggingWideString(_clientName.c_str(), "Client", "The attached client process"), TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); @@ -686,7 +668,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation TraceLoggingWrite(g_hTerminalConnectionProvider, "ReceivedFirstByte", TraceLoggingDescription("An event emitted when the connection receives the first byte"), - TraceLoggingGuid(_guid, "SessionGuid", "The WT_SESSION's GUID"), + TraceLoggingGuid(_sessionId, "SessionGuid", "The WT_SESSION's GUID"), TraceLoggingFloat64(delta.count(), "Duration"), TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h index 0105104c130..a1b622081f0 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.h +++ b/src/cascadia/TerminalConnection/ConptyConnection.h @@ -4,14 +4,14 @@ #pragma once #include "ConptyConnection.g.h" -#include "ConnectionStateHolder.h" +#include "BaseTerminalConnection.h" #include "ITerminalHandoff.h" #include namespace winrt::Microsoft::Terminal::TerminalConnection::implementation { - struct ConptyConnection : ConptyConnectionT, ConnectionStateHolder + struct ConptyConnection : ConptyConnectionT, BaseTerminalConnection { ConptyConnection(const HANDLE hSig, const HANDLE hIn, @@ -36,7 +36,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation void ReparentWindow(const uint64_t newParent); - winrt::guid Guid() const noexcept; winrt::hstring Commandline() const; winrt::hstring StartingTitle() const; WORD ShowWindow() const noexcept; @@ -77,7 +76,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation hstring _startingTitle{}; bool _initialVisibility{ true }; Windows::Foundation::Collections::ValueSet _environment{ nullptr }; - guid _guid{}; // A unique session identifier for connected client hstring _clientName{}; // The name of the process hosted by this ConPTY connection (as of launch). bool _receivedFirstByte{ false }; diff --git a/src/cascadia/TerminalConnection/ConptyConnection.idl b/src/cascadia/TerminalConnection/ConptyConnection.idl index 5cc0fb6f1d8..8e0ad44c62a 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.idl +++ b/src/cascadia/TerminalConnection/ConptyConnection.idl @@ -10,7 +10,6 @@ namespace Microsoft.Terminal.TerminalConnection [default_interface] runtimeclass ConptyConnection : ITerminalConnection { ConptyConnection(); - Guid Guid { get; }; String Commandline { get; }; String StartingTitle { get; }; UInt16 ShowWindow { get; }; diff --git a/src/cascadia/TerminalConnection/EchoConnection.h b/src/cascadia/TerminalConnection/EchoConnection.h index 25ac44846fd..2b7e84a76a0 100644 --- a/src/cascadia/TerminalConnection/EchoConnection.h +++ b/src/cascadia/TerminalConnection/EchoConnection.h @@ -18,6 +18,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation void Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/) const noexcept {}; + winrt::guid SessionId() const noexcept { return {}; } ConnectionState State() const noexcept { return ConnectionState::Connected; } WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler); diff --git a/src/cascadia/TerminalConnection/ITerminalConnection.idl b/src/cascadia/TerminalConnection/ITerminalConnection.idl index 06137e83da4..bae69c765fd 100644 --- a/src/cascadia/TerminalConnection/ITerminalConnection.idl +++ b/src/cascadia/TerminalConnection/ITerminalConnection.idl @@ -25,8 +25,9 @@ namespace Microsoft.Terminal.TerminalConnection void Close(); event TerminalOutputHandler TerminalOutput; - event Windows.Foundation.TypedEventHandler StateChanged; + + Guid SessionId { get; }; ConnectionState State { get; }; }; } diff --git a/src/cascadia/TerminalConnection/TerminalConnection.vcxproj b/src/cascadia/TerminalConnection/TerminalConnection.vcxproj index 3f7ee847294..f0a2e4e6b49 100644 --- a/src/cascadia/TerminalConnection/TerminalConnection.vcxproj +++ b/src/cascadia/TerminalConnection/TerminalConnection.vcxproj @@ -17,6 +17,7 @@ + ConnectionInformation.idl diff --git a/src/cascadia/TerminalConnection/TerminalConnection.vcxproj.filters b/src/cascadia/TerminalConnection/TerminalConnection.vcxproj.filters index a5e9593ed50..11a0227b315 100644 --- a/src/cascadia/TerminalConnection/TerminalConnection.vcxproj.filters +++ b/src/cascadia/TerminalConnection/TerminalConnection.vcxproj.filters @@ -26,6 +26,7 @@ + @@ -34,11 +35,9 @@ - - - + diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 7925fc16eb8..68702b27500 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -10,11 +10,11 @@ #include #include +#include #include #include #include "EventArgs.h" -#include "../../types/inc/GlyphWidth.hpp" #include "../../buffer/out/search.h" #include "../../renderer/atlas/AtlasEngine.h" #include "../../renderer/dx/DxRenderer.hpp" @@ -443,6 +443,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - void ControlCore::_sendInputToConnection(std::wstring_view wstr) { + if (wstr.empty()) + { + return; + } + + // The connection may call functions like WriteFile() which may block indefinitely. + // It's important we don't hold any mutexes across such calls. + _terminal->_assertUnlocked(); + if (_isReadOnly) { _raiseReadOnlyWarning(); @@ -492,8 +501,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation _handleControlC(); } - const auto lock = _terminal->LockForWriting(); - return _terminal->SendCharEvent(ch, scanCode, modifiers); + TerminalInput::OutputType out; + { + const auto lock = _terminal->LockForReading(); + out = _terminal->SendCharEvent(ch, scanCode, modifiers); + } + if (out) + { + _sendInputToConnection(*out); + return true; + } + return false; } void ControlCore::_handleControlC() @@ -602,46 +620,56 @@ namespace winrt::Microsoft::Terminal::Control::implementation const ControlKeyStates modifiers, const bool keyDown) { - const auto lock = _terminal->LockForWriting(); + if (!vkey) + { + return true; + } - // Update the selection, if it's present - // GH#8522, GH#3758 - Only modify the selection on key _down_. If we - // modify on key up, then there's chance that we'll immediately dismiss - // a selection created by an action bound to a keydown. - if (_shouldTryUpdateSelection(vkey) && keyDown) + TerminalInput::OutputType out; { - // try to update the selection - if (const auto updateSlnParams{ _terminal->ConvertKeyEventToUpdateSelectionParams(modifiers, vkey) }) - { - _terminal->UpdateSelection(updateSlnParams->first, updateSlnParams->second, modifiers); - _updateSelectionUI(); - return true; - } + const auto lock = _terminal->LockForWriting(); - // GH#8791 - don't dismiss selection if Windows key was also pressed as a key-combination. - if (!modifiers.IsWinPressed()) + // Update the selection, if it's present + // GH#8522, GH#3758 - Only modify the selection on key _down_. If we + // modify on key up, then there's chance that we'll immediately dismiss + // a selection created by an action bound to a keydown. + if (_shouldTryUpdateSelection(vkey) && keyDown) { - _terminal->ClearSelection(); - _updateSelectionUI(); - } + // try to update the selection + if (const auto updateSlnParams{ _terminal->ConvertKeyEventToUpdateSelectionParams(modifiers, vkey) }) + { + _terminal->UpdateSelection(updateSlnParams->first, updateSlnParams->second, modifiers); + _updateSelectionUI(); + return true; + } - // When there is a selection active, escape should clear it and NOT flow through - // to the terminal. With any other keypress, it should clear the selection AND - // flow through to the terminal. - if (vkey == VK_ESCAPE) - { - return true; + // GH#8791 - don't dismiss selection if Windows key was also pressed as a key-combination. + if (!modifiers.IsWinPressed()) + { + _terminal->ClearSelection(); + _updateSelectionUI(); + } + + // When there is a selection active, escape should clear it and NOT flow through + // to the terminal. With any other keypress, it should clear the selection AND + // flow through to the terminal. + if (vkey == VK_ESCAPE) + { + return true; + } } - } - // If the terminal translated the key, mark the event as handled. - // This will prevent the system from trying to get the character out - // of it and sending us a CharacterReceived event. - return vkey ? _terminal->SendKeyEvent(vkey, - scanCode, - modifiers, - keyDown) : - true; + // If the terminal translated the key, mark the event as handled. + // This will prevent the system from trying to get the character out + // of it and sending us a CharacterReceived event. + out = _terminal->SendKeyEvent(vkey, scanCode, modifiers, keyDown); + } + if (out) + { + _sendInputToConnection(*out); + return true; + } + return false; } bool ControlCore::SendMouseEvent(const til::point viewportPos, @@ -650,8 +678,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation const short wheelDelta, const TerminalInput::MouseButtonState state) { - const auto lock = _terminal->LockForWriting(); - return _terminal->SendMouseEvent(viewportPos, uiButton, states, wheelDelta, state); + TerminalInput::OutputType out; + { + const auto lock = _terminal->LockForReading(); + out = _terminal->SendMouseEvent(viewportPos, uiButton, states, wheelDelta, state); + } + if (out) + { + _sendInputToConnection(*out); + return true; + } + return false; } void ControlCore::UserScrollViewport(const int viewTop) @@ -1213,44 +1250,23 @@ namespace winrt::Microsoft::Terminal::Control::implementation return false; } - // extract text from buffer - // RetrieveSelectedTextFromBuffer will lock while it's reading - const auto bufferData = _terminal->RetrieveSelectedTextFromBuffer(singleLine); - - // convert text: vector --> string - std::wstring textData; - for (const auto& text : bufferData.text) - { - textData += text; - } - - const auto bgColor = _terminal->GetAttributeColors({}).second; + // use action's copyFormatting if it's present, else fallback to globally + // set copyFormatting. + const auto copyFormats = formats != nullptr ? formats.Value() : _settings->CopyFormatting(); - // convert text to HTML format - // GH#5347 - Don't provide a title for the generated HTML, as many - // web applications will paste the title first, followed by the HTML - // content, which is unexpected. - const auto htmlData = formats == nullptr || WI_IsFlagSet(formats.Value(), CopyFormat::HTML) ? - TextBuffer::GenHTML(bufferData, - _actualFont.GetUnscaledSize().height, - _actualFont.GetFaceName(), - bgColor) : - ""; + const auto copyHtml = WI_IsFlagSet(copyFormats, CopyFormat::HTML); + const auto copyRtf = WI_IsFlagSet(copyFormats, CopyFormat::RTF); - // convert to RTF format - const auto rtfData = formats == nullptr || WI_IsFlagSet(formats.Value(), CopyFormat::RTF) ? - TextBuffer::GenRTF(bufferData, - _actualFont.GetUnscaledSize().height, - _actualFont.GetFaceName(), - bgColor) : - ""; + // extract text from buffer + // RetrieveSelectedTextFromBuffer will lock while it's reading + const auto& [textData, htmlData, rtfData] = _terminal->RetrieveSelectedTextFromBuffer(singleLine, copyHtml, copyRtf); // send data up for clipboard _CopyToClipboardHandlers(*this, winrt::make(winrt::hstring{ textData }, winrt::to_hstring(htmlData), winrt::to_hstring(rtfData), - formats)); + copyFormats)); return true; } @@ -1324,8 +1340,19 @@ namespace winrt::Microsoft::Terminal::Control::implementation // before sending it over the terminal's connection. void ControlCore::PasteText(const winrt::hstring& hstr) { + using namespace ::Microsoft::Console::Utils; + + auto filtered = FilterStringForPaste(hstr, CarriageReturnNewline | ControlCodes); + if (BracketedPasteEnabled()) + { + filtered.insert(0, L"\x1b[200~"); + filtered.append(L"\x1b[201~"); + } + + // It's important to not hold the terminal lock while calling this function as sending the data may take a long time. + _sendInputToConnection(filtered); + const auto lock = _terminal->LockForWriting(); - _terminal->WritePastedText(hstr); _terminal->ClearSelection(); _updateSelectionUI(); _terminal->TrySnapOnInput(); @@ -1564,24 +1591,28 @@ namespace winrt::Microsoft::Terminal::Control::implementation return _terminal->IsSelectionActive(); } + // Method Description: + // - Checks if the currently active selection spans multiple lines + // Return Value: + // - true if selection is multi-line + bool ControlCore::HasMultiLineSelection() const + { + const auto lock = _terminal->LockForReading(); + assert(_terminal->IsSelectionActive()); // should only be called when selection is active + return _terminal->GetSelectionAnchor().y != _terminal->GetSelectionEnd().y; + } + bool ControlCore::CopyOnSelect() const { return _settings->CopyOnSelect(); } - Windows::Foundation::Collections::IVector ControlCore::SelectedText(bool trimTrailingWhitespace) const + winrt::hstring ControlCore::SelectedText(bool trimTrailingWhitespace) const { // RetrieveSelectedTextFromBuffer will lock while it's reading const auto lock = _terminal->LockForReading(); - const auto internalResult{ _terminal->RetrieveSelectedTextFromBuffer(trimTrailingWhitespace).text }; - - auto result = winrt::single_threaded_vector(); - - for (const auto& row : internalResult) - { - result.Append(winrt::hstring{ row }); - } - return result; + const auto internalResult{ _terminal->RetrieveSelectedTextFromBuffer(!trimTrailingWhitespace) }; + return winrt::hstring{ internalResult.plainText }; } ::Microsoft::Console::Render::IRenderData* ControlCore::GetRenderData() const @@ -1604,6 +1635,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation if (_searcher.ResetIfStale(*GetRenderData(), text, !goForward, !caseSensitive)) { + _searcher.HighlightResults(); _searcher.MoveToCurrentSelection(); _cachedSearchResultRows = {}; } @@ -1620,7 +1652,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation // DO NOT call _updateSelectionUI() here. // We don't want to show the markers so manually tell it to clear it. _terminal->SetBlockSelection(false); - _renderer->TriggerSelection(); _UpdateSelectionMarkersHandlers(*this, winrt::make(true)); foundResults->TotalMatches(gsl::narrow(_searcher.Results().size())); @@ -1628,6 +1659,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _terminal->AlwaysNotifyOnBufferRotation(true); } + _renderer->TriggerSelection(); // Raise a FoundMatch event, which the control will use to notify // narrator if there was any results in the buffer @@ -1894,17 +1926,27 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto endPoint = goRight ? clampedClick : cursorPos; const auto delta = _terminal->GetTextBuffer().GetCellDistance(startPoint, endPoint); - const WORD key = goRight ? VK_RIGHT : VK_LEFT; + + std::wstring buffer; + const auto append = [&](TerminalInput::OutputType&& out) { + if (out) + { + buffer.append(std::move(*out)); + } + }; + // Send an up and a down once per cell. This won't // accurately handle wide characters, or continuation // prompts, or cases where a single escape character in the // command (e.g. ^[) takes up two cells. for (size_t i = 0u; i < delta; i++) { - _terminal->SendKeyEvent(key, 0, {}, true); - _terminal->SendKeyEvent(key, 0, {}, false); + append(_terminal->SendKeyEvent(key, 0, {}, true)); + append(_terminal->SendKeyEvent(key, 0, {}, false)); } + + _sendInputToConnection(buffer); } } } @@ -1915,7 +1957,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - Updates the renderer's representation of the selection as well as the selection marker overlay in TermControl void ControlCore::_updateSelectionUI() { - const auto lock = _terminal->LockForWriting(); _renderer->TriggerSelection(); // only show the markers if we're doing a keyboard selection or in mark mode const bool showMarkers{ _terminal->SelectionMode() >= ::Microsoft::Terminal::Core::Terminal::SelectionInteractionMode::Keyboard }; @@ -2247,14 +2288,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation void ControlCore::_focusChanged(bool focused) { - // GH#13461 - temporarily turn off read-only mode, send the focus event, - // then turn it back on. Even in focus mode, focus events are fine to - // send. We don't want to pop a warning every time the control is - // focused. - const auto previous = std::exchange(_isReadOnly, false); - const auto restore = wil::scope_exit([&]() { _isReadOnly = previous; }); - const auto lock = _terminal->LockForWriting(); - _terminal->FocusChanged(focused); + TerminalInput::OutputType out; + { + const auto lock = _terminal->LockForReading(); + out = _terminal->FocusChanged(focused); + } + if (out && !out->empty()) + { + // _sendInputToConnection() asserts that we aren't in focus mode, + // but window focus events are always fine to send. + _connection.WriteInput(*out); + } } bool ControlCore::_isBackgroundTransparent() diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 90cc5fca4d4..6b513f2bf86 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -156,7 +156,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation int BufferHeight() const; bool HasSelection() const; - Windows::Foundation::Collections::IVector SelectedText(bool trimTrailingWhitespace) const; + bool HasMultiLineSelection() const; + winrt::hstring SelectedText(bool trimTrailingWhitespace) const; bool BracketedPasteEnabled() const noexcept; diff --git a/src/cascadia/TerminalControl/HwndTerminal.cpp b/src/cascadia/TerminalControl/HwndTerminal.cpp index a13ac5b3eda..b5a930f701a 100644 --- a/src/cascadia/TerminalControl/HwndTerminal.cpp +++ b/src/cascadia/TerminalControl/HwndTerminal.cpp @@ -106,9 +106,12 @@ try { try { - const auto lock = publicTerminal->_terminal->LockForWriting(); - const auto bufferData = publicTerminal->_terminal->RetrieveSelectedTextFromBuffer(false); - LOG_IF_FAILED(publicTerminal->_CopyTextToSystemClipboard(bufferData, true)); + Terminal::TextCopyData bufferData; + { + const auto lock = publicTerminal->_terminal->LockForWriting(); + bufferData = publicTerminal->_terminal->RetrieveSelectedTextFromBuffer(false, true, true); + } + LOG_IF_FAILED(publicTerminal->_CopyTextToSystemClipboard(bufferData.plainText, bufferData.html, bufferData.rtf)); publicTerminal->_ClearSelection(); } CATCH_LOG(); @@ -272,7 +275,7 @@ void HwndTerminal::RegisterScrollCallback(std::function cal void HwndTerminal::_WriteTextToConnection(const std::wstring_view input) noexcept { - if (!_pfnWriteCallback) + if (input.empty() || !_pfnWriteCallback) { return; } @@ -666,20 +669,14 @@ try return nullptr; } - TextBuffer::TextAndColor bufferData; + std::wstring selectedText; { const auto lock = publicTerminal->_terminal->LockForWriting(); - bufferData = publicTerminal->_terminal->RetrieveSelectedTextFromBuffer(false); + auto bufferData = publicTerminal->_terminal->RetrieveSelectedTextFromBuffer(false); + selectedText = std::move(bufferData.plainText); publicTerminal->_ClearSelection(); } - // convert text: vector --> string - std::wstring selectedText; - for (const auto& text : bufferData.text) - { - selectedText += text; - } - auto returnText = wil::make_cotaskmem_string_nothrow(selectedText.c_str()); return returnText.release(); } @@ -758,8 +755,17 @@ try WI_IsFlagSet(GetKeyState(VK_RBUTTON), KeyPressed) }; - const auto lock = _terminal->LockForWriting(); - return _terminal->SendMouseEvent(cursorPosition / fontSize, uMsg, getControlKeyState(), wheelDelta, state); + TerminalInput::OutputType out; + { + const auto lock = _terminal->LockForReading(); + out = _terminal->SendMouseEvent(cursorPosition / fontSize, uMsg, getControlKeyState(), wheelDelta, state); + } + if (out) + { + _WriteTextToConnection(*out); + return true; + } + return false; } catch (...) { @@ -784,8 +790,16 @@ try { _uiaProvider->RecordKeyEvent(vkey); } - const auto lock = _terminal->LockForWriting(); - _terminal->SendKeyEvent(vkey, scanCode, modifiers, keyDown); + + TerminalInput::OutputType out; + { + const auto lock = _terminal->LockForReading(); + out = _terminal->SendKeyEvent(vkey, scanCode, modifiers, keyDown); + } + if (out) + { + _WriteTextToConnection(*out); + } } CATCH_LOG(); @@ -797,31 +811,39 @@ try return; } - const auto lock = _terminal->LockForWriting(); - - if (_terminal->IsSelectionActive()) + TerminalInput::OutputType out; { - _ClearSelection(); - if (ch == UNICODE_ESC) + const auto lock = _terminal->LockForWriting(); + + if (_terminal->IsSelectionActive()) + { + _ClearSelection(); + if (ch == UNICODE_ESC) + { + // ESC should clear any selection before it triggers input. + // Other characters pass through. + return; + } + } + + if (ch == UNICODE_TAB) { - // ESC should clear any selection before it triggers input. - // Other characters pass through. + // TAB was handled as a keydown event (cf. Terminal::SendKeyEvent) return; } - } - if (ch == UNICODE_TAB) - { - // TAB was handled as a keydown event (cf. Terminal::SendKeyEvent) - return; - } + auto modifiers = getControlKeyState(); + if (WI_IsFlagSet(flags, ENHANCED_KEY)) + { + modifiers |= ControlKeyStates::EnhancedKey; + } - auto modifiers = getControlKeyState(); - if (WI_IsFlagSet(flags, ENHANCED_KEY)) + out = _terminal->SendCharEvent(ch, scanCode, modifiers); + } + if (out) { - modifiers |= ControlKeyStates::EnhancedKey; + _WriteTextToConnection(*out); } - _terminal->SendCharEvent(ch, scanCode, modifiers); } CATCH_LOG(); @@ -938,22 +960,16 @@ void __stdcall TerminalKillFocus(void* terminal) // Routine Description: // - Copies the text given onto the global system clipboard. // Arguments: -// - rows - Rows of text data to copy -// - fAlsoCopyFormatting - true if the color and formatting should also be copied, false otherwise -HRESULT HwndTerminal::_CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, const bool fAlsoCopyFormatting) +// - text - selected text in plain-text format +// - htmlData - selected text in HTML format +// - rtfData - selected text in RTF format +HRESULT HwndTerminal::_CopyTextToSystemClipboard(const std::wstring& text, const std::string& htmlData, const std::string& rtfData) const try { RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _terminal); - std::wstring finalString; - - // Concatenate strings into one giant string to put onto the clipboard. - for (const auto& str : rows.text) - { - finalString += str; - } // allocate the final clipboard data - const auto cchNeeded = finalString.size() + 1; + const auto cchNeeded = text.size() + 1; const auto cbNeeded = sizeof(wchar_t) * cchNeeded; wil::unique_hglobal globalHandle(GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, cbNeeded)); RETURN_LAST_ERROR_IF_NULL(globalHandle.get()); @@ -963,7 +979,7 @@ try // The pattern gets a bit strange here because there's no good wil built-in for global lock of this type. // Try to copy then immediately unlock. Don't throw until after (so the hglobal won't be freed until we unlock). - const auto hr = StringCchCopyW(pwszClipboard, cchNeeded, finalString.data()); + const auto hr = StringCchCopyW(pwszClipboard, cchNeeded, text.data()); GlobalUnlock(globalHandle.get()); RETURN_IF_FAILED(hr); @@ -978,21 +994,14 @@ try RETURN_LAST_ERROR_IF(!EmptyClipboard()); RETURN_LAST_ERROR_IF_NULL(SetClipboardData(CF_UNICODETEXT, globalHandle.get())); - if (fAlsoCopyFormatting) + if (!htmlData.empty()) { - const auto& fontData = _actualFont; - const int iFontHeightPoints = fontData.GetUnscaledSize().height; // this renderer uses points already - COLORREF bgColor; - { - const auto lock = _terminal->LockForReading(); - bgColor = _terminal->GetAttributeColors({}).second; - } - - auto HTMLToPlaceOnClip = TextBuffer::GenHTML(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor); - _CopyToSystemClipboard(HTMLToPlaceOnClip, L"HTML Format"); + RETURN_IF_FAILED(_CopyToSystemClipboard(htmlData, L"HTML Format")); + } - auto RTFToPlaceOnClip = TextBuffer::GenRTF(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor); - _CopyToSystemClipboard(RTFToPlaceOnClip, L"Rich Text Format"); + if (!rtfData.empty()) + { + RETURN_IF_FAILED(_CopyToSystemClipboard(rtfData, L"Rich Text Format")); } } @@ -1010,7 +1019,7 @@ CATCH_RETURN() // Arguments: // - stringToCopy - The string to copy // - lpszFormat - the name of the format -HRESULT HwndTerminal::_CopyToSystemClipboard(std::string stringToCopy, LPCWSTR lpszFormat) +HRESULT HwndTerminal::_CopyToSystemClipboard(const std::string& stringToCopy, LPCWSTR lpszFormat) const { const auto cbData = stringToCopy.size() + 1; // +1 for '\0' if (cbData) diff --git a/src/cascadia/TerminalControl/HwndTerminal.hpp b/src/cascadia/TerminalControl/HwndTerminal.hpp index b88acd03504..6646fd56506 100644 --- a/src/cascadia/TerminalControl/HwndTerminal.hpp +++ b/src/cascadia/TerminalControl/HwndTerminal.hpp @@ -109,8 +109,8 @@ struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo void _UpdateFont(int newDpi); void _WriteTextToConnection(const std::wstring_view text) noexcept; - HRESULT _CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, const bool fAlsoCopyFormatting); - HRESULT _CopyToSystemClipboard(std::string stringToCopy, LPCWSTR lpszFormat); + HRESULT _CopyTextToSystemClipboard(const std::wstring& text, const std::string& htmlData, const std::string& rtfData) const; + HRESULT _CopyToSystemClipboard(const std::string& stringToCopy, LPCWSTR lpszFormat) const; void _PasteTextFromClipboard() noexcept; const unsigned int _NumberOfClicks(til::point clickPos, std::chrono::steady_clock::time_point clickTime) noexcept; diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index a64dd4f7987..2ccfbd5b90d 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -3,6 +3,7 @@ import "IKeyBindings.idl"; import "IControlAppearance.idl"; +import "EventArgs.idl"; namespace Microsoft.Terminal.Control { @@ -48,6 +49,7 @@ namespace Microsoft.Terminal.Control Microsoft.Terminal.Control.IKeyBindings KeyBindings { get; }; Boolean CopyOnSelect { get; }; + Microsoft.Terminal.Control.CopyFormat CopyFormatting { get; }; Boolean FocusFollowMouse { get; }; String Commandline { get; }; diff --git a/src/cascadia/TerminalControl/ICoreState.idl b/src/cascadia/TerminalControl/ICoreState.idl index ed60ab4ea6c..0073b70788a 100644 --- a/src/cascadia/TerminalControl/ICoreState.idl +++ b/src/cascadia/TerminalControl/ICoreState.idl @@ -46,7 +46,8 @@ namespace Microsoft.Terminal.Control Int32 BufferHeight { get; }; Boolean HasSelection { get; }; - IVector SelectedText(Boolean trimTrailingWhitespace); + Boolean HasMultiLineSelection { get; }; + String SelectedText(Boolean trimTrailingWhitespace); Boolean BracketedPasteEnabled { get; }; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 4c59d027a52..5978149cd36 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -57,10 +57,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _isInternalScrollBarUpdate{ false }, _autoScrollVelocity{ 0 }, _autoScrollingPointerPoint{ std::nullopt }, - _autoScrollTimer{}, _lastAutoScrollUpdateTime{ std::nullopt }, - _cursorTimer{}, - _blinkTimer{}, _searchBox{ nullptr } { InitializeComponent(); @@ -419,10 +416,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Currently we populate the search box only if a single line is selected. // Empirically, multi-line selection works as well on sample scenarios, // but since code paths differ, extra work is required to ensure correctness. - auto bufferText = _core.SelectedText(true); - if (bufferText.Size() == 1) + if (!_core.HasMultiLineSelection()) { - const auto selectedLine{ bufferText.GetAt(0) }; + const auto selectedLine{ _core.SelectedText(true) }; _searchBox->PopulateTextbox(selectedLine); } } @@ -1087,10 +1083,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation if (blinkTime != INFINITE) { // Create a timer - DispatcherTimer cursorTimer; - cursorTimer.Interval(std::chrono::milliseconds(blinkTime)); - cursorTimer.Tick({ get_weak(), &TermControl::_CursorTimerTick }); - _cursorTimer.emplace(std::move(cursorTimer)); + _cursorTimer.Interval(std::chrono::milliseconds(blinkTime)); + _cursorTimer.Tick({ get_weak(), &TermControl::_CursorTimerTick }); // As of GH#6586, don't start the cursor timer immediately, and // don't show the cursor initially. We'll show the cursor and start // the timer when the control is first focused. @@ -1105,13 +1099,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation _core.CursorOn(_focused || _displayCursorWhileBlurred()); if (_displayCursorWhileBlurred()) { - _cursorTimer->Start(); + _cursorTimer.Start(); } } else { - // The user has disabled cursor blinking - _cursorTimer = std::nullopt; + _cursorTimer.Destroy(); } // Set up blinking attributes @@ -1120,16 +1113,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation if (animationsEnabled && blinkTime != INFINITE) { // Create a timer - DispatcherTimer blinkTimer; - blinkTimer.Interval(std::chrono::milliseconds(blinkTime)); - blinkTimer.Tick({ get_weak(), &TermControl::_BlinkTimerTick }); - blinkTimer.Start(); - _blinkTimer.emplace(std::move(blinkTimer)); + _blinkTimer.Interval(std::chrono::milliseconds(blinkTime)); + _blinkTimer.Tick({ get_weak(), &TermControl::_BlinkTimerTick }); + _blinkTimer.Start(); } else { // The user has disabled blinking - _blinkTimer = std::nullopt; + _blinkTimer.Destroy(); } // Now that the renderer is set up, update the appearance for initialization @@ -1345,7 +1336,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Alt, so we should be ignoring the individual keydowns. The character // will be sent through the TSFInputControl. See GH#1401 for more // details - if (modifiers.IsAltPressed() && + if (modifiers.IsAltPressed() && !modifiers.IsCtrlPressed() && (vkey >= VK_NUMPAD0 && vkey <= VK_NUMPAD9)) { e.Handled(true); @@ -1498,7 +1489,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Manually show the cursor when a key is pressed. Restarting // the timer prevents flickering. _core.CursorOn(_core.SelectionMode() != SelectionInteractionMode::Mark); - _cursorTimer->Start(); + _cursorTimer.Start(); } return handled; @@ -1973,12 +1964,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation { // When the terminal focuses, show the cursor immediately _core.CursorOn(_core.SelectionMode() != SelectionInteractionMode::Mark); - _cursorTimer->Start(); + _cursorTimer.Start(); } if (_blinkTimer) { - _blinkTimer->Start(); + _blinkTimer.Start(); } // Only update the appearance here if an unfocused config exists - if an @@ -2021,13 +2012,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation if (_cursorTimer && !_displayCursorWhileBlurred()) { - _cursorTimer->Stop(); + _cursorTimer.Stop(); _core.CursorOn(false); } if (_blinkTimer) { - _blinkTimer->Stop(); + _blinkTimer.Stop(); } // Check if there is an unfocused config we should set the appearance to @@ -2278,7 +2269,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Disconnect the TSF input control so it doesn't receive EditContext events. TSFInputControl().Close(); + + // At the time of writing, closing the last tab of a window inexplicably + // does not lead to the destruction of the remaining TermControl instance(s). + // On Win10 we don't destroy window threads due to bugs in DesktopWindowXamlSource. + // In turn, we leak TermControl instances. This results in constant HWND messages + // while the thread is supposed to be idle. Stop these timers avoids this. _autoScrollTimer.Stop(); + _bellLightTimer.Stop(); + _cursorTimer.Stop(); + _blinkTimer.Stop(); if (!_detached) { @@ -3129,20 +3129,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation _bellDarkAnimation.Duration(winrt::Windows::Foundation::TimeSpan(std::chrono::milliseconds(TerminalWarningBellInterval))); } - // Similar to the animation, only initialize the timer here - if (!_bellLightTimer) - { - _bellLightTimer = {}; - _bellLightTimer.Interval(std::chrono::milliseconds(TerminalWarningBellInterval)); - _bellLightTimer.Tick({ get_weak(), &TermControl::_BellLightOff }); - } - Windows::Foundation::Numerics::float2 zeroSize{ 0, 0 }; // If the grid has 0 size or if the bell timer is // already active, do nothing if (RootGrid().ActualSize() != zeroSize && !_bellLightTimer.IsEnabled()) { - // Start the timer, when the timer ticks we switch off the light + _bellLightTimer.Interval(std::chrono::milliseconds(TerminalWarningBellInterval)); + _bellLightTimer.Tick({ get_weak(), &TermControl::_BellLightOff }); _bellLightTimer.Start(); // Switch on the light and animate the intensity to fade out @@ -3162,15 +3155,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation void TermControl::_BellLightOff(const Windows::Foundation::IInspectable& /* sender */, const Windows::Foundation::IInspectable& /* e */) { - if (_bellLightTimer) - { - // Stop the timer and switch off the light - _bellLightTimer.Stop(); + // Stop the timer and switch off the light + _bellLightTimer.Stop(); - if (!_IsClosing()) - { - VisualBellLight::SetIsTarget(RootGrid(), false); - } + if (!_IsClosing()) + { + VisualBellLight::SetIsTarget(RootGrid(), false); } } @@ -3496,7 +3486,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation { return _core.HasSelection(); } - Windows::Foundation::Collections::IVector TermControl::SelectedText(bool trimTrailingWhitespace) const + bool TermControl::HasMultiLineSelection() const + { + return _core.HasMultiLineSelection(); + } + winrt::hstring TermControl::SelectedText(bool trimTrailingWhitespace) const { return _core.SelectedText(trimTrailingWhitespace); } @@ -3729,9 +3723,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation { // If we should be ALWAYS displaying the cursor, turn it on and start blinking. _core.CursorOn(true); - if (_cursorTimer.has_value()) + if (_cursorTimer) { - _cursorTimer->Start(); + _cursorTimer.Start(); } } else @@ -3740,9 +3734,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation // blinking. (if we're focused, then we're already doing the right // thing) const auto focused = FocusState() != FocusState::Unfocused; - if (!focused && _cursorTimer.has_value()) + if (!focused && _cursorTimer) { - _cursorTimer->Stop(); + _cursorTimer.Stop(); } _core.CursorOn(focused); } diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 441ca5f5763..7dc74d7136b 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -72,7 +72,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation int BufferHeight() const; bool HasSelection() const; - Windows::Foundation::Collections::IVector SelectedText(bool trimTrailingWhitespace) const; + bool HasMultiLineSelection() const; + winrt::hstring SelectedText(bool trimTrailingWhitespace) const; bool BracketedPasteEnabled() const noexcept; @@ -236,16 +237,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation // viewport. View is then scrolled to 'follow' the cursor. double _autoScrollVelocity; std::optional _autoScrollingPointerPoint; - Windows::UI::Xaml::DispatcherTimer _autoScrollTimer; + SafeDispatcherTimer _autoScrollTimer; std::optional _lastAutoScrollUpdateTime; bool _pointerPressedInBounds{ false }; winrt::Windows::UI::Composition::ScalarKeyFrameAnimation _bellLightAnimation{ nullptr }; winrt::Windows::UI::Composition::ScalarKeyFrameAnimation _bellDarkAnimation{ nullptr }; - Windows::UI::Xaml::DispatcherTimer _bellLightTimer{ nullptr }; + SafeDispatcherTimer _bellLightTimer; - std::optional _cursorTimer; - std::optional _blinkTimer; + SafeDispatcherTimer _cursorTimer; + SafeDispatcherTimer _blinkTimer; winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker; bool _showMarksInScrollbar{ false }; diff --git a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp index c08bd6c66a1..257eb7de01e 100644 --- a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp +++ b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp @@ -92,8 +92,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation winrt::Windows::Foundation::IInspectable XamlUiaTextRange::GetAttributeValue(int32_t textAttributeId) const { // Call the function off of the underlying UiaTextRange. - VARIANT result; - THROW_IF_FAILED(_uiaProvider->GetAttributeValue(textAttributeId, &result)); + wil::unique_variant result; + THROW_IF_FAILED(_uiaProvider->GetAttributeValue(textAttributeId, result.addressof())); // Convert the resulting VARIANT into a format that is consumable by XAML. switch (result.vt) @@ -189,9 +189,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation winrt::hstring XamlUiaTextRange::GetText(int32_t maxLength) const { - BSTR returnVal; - THROW_IF_FAILED(_uiaProvider->GetText(maxLength, &returnVal)); - return winrt::to_hstring(returnVal); + wil::unique_bstr returnVal; + THROW_IF_FAILED(_uiaProvider->GetText(maxLength, returnVal.put())); + return winrt::hstring{ returnVal.get(), SysStringLen(returnVal.get()) }; } int32_t XamlUiaTextRange::Move(XamlAutomation::TextUnit unit, diff --git a/src/cascadia/TerminalControl/pch.h b/src/cascadia/TerminalControl/pch.h index 98f905ef088..f7be9ae1eac 100644 --- a/src/cascadia/TerminalControl/pch.h +++ b/src/cascadia/TerminalControl/pch.h @@ -73,7 +73,8 @@ TRACELOGGING_DECLARE_PROVIDER(g_hTerminalControlProvider); #include #include -#include "ThrottledFunc.h" +#include +#include #include #include // must go after the CoreDispatcher type is defined diff --git a/src/cascadia/TerminalCore/ITerminalInput.hpp b/src/cascadia/TerminalCore/ITerminalInput.hpp index 480494de1f6..6adbb0374b5 100644 --- a/src/cascadia/TerminalCore/ITerminalInput.hpp +++ b/src/cascadia/TerminalCore/ITerminalInput.hpp @@ -16,9 +16,10 @@ namespace Microsoft::Terminal::Core ITerminalInput& operator=(const ITerminalInput&) = default; ITerminalInput& operator=(ITerminalInput&&) = default; - virtual bool SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states, const bool keyDown) = 0; - virtual bool SendMouseEvent(const til::point viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state) = 0; - virtual bool SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) = 0; + virtual [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states, const bool keyDown) = 0; + virtual [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType SendMouseEvent(const til::point viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state) = 0; + virtual [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) = 0; + virtual [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType FocusChanged(const bool focused) = 0; [[nodiscard]] virtual HRESULT UserResize(const til::size size) noexcept = 0; virtual void UserScrollViewport(const int viewTop) = 0; @@ -26,8 +27,6 @@ namespace Microsoft::Terminal::Core virtual void TrySnapOnInput() = 0; - virtual void FocusChanged(const bool focused) = 0; - protected: ITerminalInput() = default; }; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index fe2417b1f97..3b086e3c143 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -30,6 +30,15 @@ Terminal::Terminal() _renderSettings.SetColorAlias(ColorAlias::DefaultBackground, TextColor::DEFAULT_BACKGROUND, RGB(0, 0, 0)); } +#pragma warning(suppress : 26455) // default constructor is throwing, too much effort to rearrange at this time. +Terminal::Terminal(TestDummyMarker) : + Terminal{} +{ +#ifndef NDEBUG + _suppressLockChecks = true; +#endif +} + void Terminal::Create(til::size viewportSize, til::CoordType scrollbackLines, Renderer& renderer) { _mutableViewport = Viewport::FromDimensions({ 0, 0 }, viewportSize); @@ -425,24 +434,6 @@ void Terminal::Write(std::wstring_view stringView) } } -void Terminal::WritePastedText(std::wstring_view stringView) -{ - const auto option = ::Microsoft::Console::Utils::FilterOption::CarriageReturnNewline | - ::Microsoft::Console::Utils::FilterOption::ControlCodes; - - auto filtered = ::Microsoft::Console::Utils::FilterStringForPaste(stringView, option); - if (IsXtermBracketedPasteModeEnabled()) - { - filtered.insert(0, L"\x1b[200~"); - filtered.append(L"\x1b[201~"); - } - - if (_pfnWriteInput) - { - _pfnWriteInput(filtered); - } -} - // Method Description: // - Attempts to snap to the bottom of the buffer, if SnapOnInput is true. Does // nothing if SnapOnInput is set to false, or we're already at the bottom of @@ -606,10 +597,10 @@ std::optional Terminal::GetHyperlinkIntervalFromViewportPos // Return Value: // - true if we translated the key event, and it should not be processed any further. // - false if we did not translate the key, and it should be processed into a character. -bool Terminal::SendKeyEvent(const WORD vkey, - const WORD scanCode, - const ControlKeyStates states, - const bool keyDown) +TerminalInput::OutputType Terminal::SendKeyEvent(const WORD vkey, + const WORD scanCode, + const ControlKeyStates states, + const bool keyDown) { // GH#6423 - don't snap on this key if the key that was pressed was a // modifier key. We'll wait for a real keystroke to snap to the bottom. @@ -627,7 +618,7 @@ bool Terminal::SendKeyEvent(const WORD vkey, // GH#7064 if (vkey == 0 || vkey >= 0xff) { - return false; + return {}; } // While not explicitly permitted, a wide range of software, including Windows' own touch keyboard, @@ -637,7 +628,7 @@ bool Terminal::SendKeyEvent(const WORD vkey, const auto sc = scanCode ? scanCode : _ScanCodeFromVirtualKey(vkey); if (sc == 0) { - return false; + return {}; } const auto isAltOnlyPressed = states.IsAltPressed() && !states.IsCtrlPressed(); @@ -665,11 +656,11 @@ bool Terminal::SendKeyEvent(const WORD vkey, // See the method description for more information. if (keyDown && !isAltOnlyPressed && vkey != VK_TAB && ch != UNICODE_NULL) { - return false; + return {}; } const auto keyEv = SynthesizeKeyEvent(keyDown, 1, vkey, sc, ch, states.Value()); - return _handleTerminalInputResult(_getTerminalInput().HandleKey(keyEv)); + return _getTerminalInput().HandleKey(keyEv); } // Method Description: @@ -686,14 +677,14 @@ bool Terminal::SendKeyEvent(const WORD vkey, // Return Value: // - true if we translated the key event, and it should not be processed any further. // - false if we did not translate the key, and it should be processed into a character. -bool Terminal::SendMouseEvent(til::point viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const TerminalInput::MouseButtonState state) +TerminalInput::OutputType Terminal::SendMouseEvent(til::point viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const TerminalInput::MouseButtonState state) { // GH#6401: VT applications should be able to receive mouse events from outside the // terminal buffer. This is likely to happen when the user drags the cursor offscreen. // We shouldn't throw away perfectly good events when they're offscreen, so we just // clamp them to be within the range [(0, 0), (W, H)]. _GetMutableViewport().ToOrigin().Clamp(viewportPos); - return _handleTerminalInputResult(_getTerminalInput().HandleMouse(viewportPos, uiButton, GET_KEYSTATE_WPARAM(states.Value()), wheelDelta, state)); + return _getTerminalInput().HandleMouse(viewportPos, uiButton, GET_KEYSTATE_WPARAM(states.Value()), wheelDelta, state); } // Method Description: @@ -708,7 +699,7 @@ bool Terminal::SendMouseEvent(til::point viewportPos, const unsigned int uiButto // Return Value: // - true if we translated the character event, and it should not be processed any further. // - false otherwise. -bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) +TerminalInput::OutputType Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) { auto vkey = _TakeVirtualKeyFromLastKeyEvent(scanCode); if (vkey == 0 && scanCode != 0) @@ -746,7 +737,7 @@ bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const Contro } const auto keyDown = SynthesizeKeyEvent(true, 1, vkey, scanCode, ch, states.Value()); - return _handleTerminalInputResult(_getTerminalInput().HandleKey(keyDown)); + return _getTerminalInput().HandleKey(keyDown); } // Method Description: @@ -757,9 +748,9 @@ bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const Contro // - focused: true if we're focused, false otherwise. // Return Value: // - none -void Terminal::FocusChanged(const bool focused) +TerminalInput::OutputType Terminal::FocusChanged(const bool focused) { - _handleTerminalInputResult(_getTerminalInput().HandleFocus(focused)); + return _getTerminalInput().HandleFocus(focused); } // Method Description: @@ -882,20 +873,6 @@ catch (...) return UNICODE_INVALID; } -[[maybe_unused]] bool Terminal::_handleTerminalInputResult(TerminalInput::OutputType&& out) const -{ - if (out) - { - const auto& str = *out; - if (_pfnWriteInput && !str.empty()) - { - _pfnWriteInput(str); - } - return true; - } - return false; -} - // Method Description: // - It's possible for a single scan code on a keyboard to // produce different key codes depending on the keyboard state. @@ -933,7 +910,7 @@ WORD Terminal::_TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept void Terminal::_assertLocked() const noexcept { #ifndef NDEBUG - if (!_readWriteLock.is_locked()) + if (!_suppressLockChecks && !_readWriteLock.is_locked()) { // __debugbreak() has the benefit over assert() that the debugger jumps right here to this line. // That way there's no need to first click any dialogues, etc. The disadvantage of course is that the @@ -943,6 +920,16 @@ void Terminal::_assertLocked() const noexcept #endif } +void Terminal::_assertUnlocked() const noexcept +{ +#ifndef NDEBUG + if (!_suppressLockChecks && _readWriteLock.is_locked()) + { + __debugbreak(); + } +#endif +} + // Method Description: // - Acquire a read lock on the terminal. // Return Value: diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 72c34d2f629..80c5e3cc905 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -60,6 +60,10 @@ class Microsoft::Terminal::Core::Terminal final : using RenderSettings = Microsoft::Console::Render::RenderSettings; public: + struct TestDummyMarker + { + }; + static constexpr bool IsInputKey(WORD vkey) { return vkey != VK_CONTROL && @@ -77,6 +81,7 @@ class Microsoft::Terminal::Core::Terminal final : } Terminal(); + Terminal(TestDummyMarker); void Create(til::size viewportSize, til::CoordType scrollbackLines, @@ -98,9 +103,8 @@ class Microsoft::Terminal::Core::Terminal final : // Write comes from the PTY and goes to our parser to be stored in the output buffer void Write(std::wstring_view stringView); - // WritePastedText comes from our input and goes back to the PTY's input channel - void WritePastedText(std::wstring_view stringView); - + void _assertLocked() const noexcept; + void _assertUnlocked() const noexcept; [[nodiscard]] std::unique_lock LockForReading() const noexcept; [[nodiscard]] std::unique_lock LockForWriting() noexcept; til::recursive_ticket_lock_suspension SuspendLock() noexcept; @@ -167,9 +171,10 @@ class Microsoft::Terminal::Core::Terminal final : #pragma region ITerminalInput // These methods are defined in Terminal.cpp - bool SendKeyEvent(const WORD vkey, const WORD scanCode, const Microsoft::Terminal::Core::ControlKeyStates states, const bool keyDown) override; - bool SendMouseEvent(const til::point viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state) override; - bool SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) override; + [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType SendKeyEvent(const WORD vkey, const WORD scanCode, const Microsoft::Terminal::Core::ControlKeyStates states, const bool keyDown) override; + [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType SendMouseEvent(const til::point viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state) override; + [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) override; + [[nodiscard]] ::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType FocusChanged(const bool focused) override; [[nodiscard]] HRESULT UserResize(const til::size viewportSize) noexcept override; void UserScrollViewport(const int viewTop) override; @@ -179,8 +184,6 @@ class Microsoft::Terminal::Core::Terminal final : bool IsTrackingMouseInput() const noexcept; bool ShouldSendAlternateScroll(const unsigned int uiButton, const int32_t delta) const noexcept; - void FocusChanged(const bool focused) override; - std::wstring GetHyperlinkAtViewportPosition(const til::point viewportPos); std::wstring GetHyperlinkAtBufferPosition(const til::point bufferPos); uint16_t GetHyperlinkIdAtViewportPosition(const til::point viewportPos); @@ -212,10 +215,12 @@ class Microsoft::Terminal::Core::Terminal final : std::pair GetAttributeColors(const TextAttribute& attr) const noexcept override; std::vector GetSelectionRects() noexcept override; + std::vector GetSearchSelectionRects() noexcept override; const bool IsSelectionActive() const noexcept override; const bool IsBlockSelection() const noexcept override; void ClearSelection() override; void SelectNewRegion(const til::point coordStart, const til::point coordEnd) override; + void SelectSearchRegions(std::vector source) override; const til::point GetSelectionAnchor() const noexcept override; const til::point GetSelectionEnd() const noexcept override; const std::wstring_view GetConsoleTitle() const noexcept override; @@ -288,6 +293,13 @@ class Microsoft::Terminal::Core::Terminal final : End = 0x2 }; + struct TextCopyData + { + std::wstring plainText; + std::string html; + std::string rtf; + }; + void MultiClickSelection(const til::point viewportPos, SelectionExpansion expansionMode); void SetSelectionAnchor(const til::point position); void SetSelectionEnd(const til::point position, std::optional newExpansionMode = std::nullopt); @@ -306,9 +318,13 @@ class Microsoft::Terminal::Core::Terminal final : til::point SelectionEndForRendering() const; const SelectionEndpoint SelectionEndpointTarget() const noexcept; - const TextBuffer::TextAndColor RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace); + TextCopyData RetrieveSelectedTextFromBuffer(const bool singleLine, const bool html = false, const bool rtf = false) const; #pragma endregion +#ifndef NDEBUG + bool _suppressLockChecks = false; +#endif + private: std::function _pfnWriteInput; std::function _pfnWarningBell; @@ -370,6 +386,7 @@ class Microsoft::Terminal::Core::Terminal final : til::point pivot; }; std::optional _selection; + std::vector _searchSelections; bool _blockSelection = false; std::wstring _wordDelimiters; SelectionExpansion _multiClickSelectionMode = SelectionExpansion::Char; @@ -431,11 +448,9 @@ class Microsoft::Terminal::Core::Terminal final : static WORD _VirtualKeyFromCharacter(const wchar_t ch) noexcept; static wchar_t _CharacterFromKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states) noexcept; - [[maybe_unused]] bool _handleTerminalInputResult(::Microsoft::Console::VirtualTerminal::TerminalInput::OutputType&& out) const; void _StoreKeyEvent(const WORD vkey, const WORD scanCode) noexcept; WORD _TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept; - void _assertLocked() const noexcept; Console::VirtualTerminal::TerminalInput& _getTerminalInput() noexcept; const Console::VirtualTerminal::TerminalInput& _getTerminalInput() const noexcept; @@ -459,6 +474,7 @@ class Microsoft::Terminal::Core::Terminal final : #pragma region TextSelection // These methods are defined in TerminalSelection.cpp std::vector _GetSelectionRects() const noexcept; + std::vector _GetSearchSelectionRects(Microsoft::Console::Types::Viewport viewport) const noexcept; std::vector _GetSelectionSpans() const noexcept; std::pair _PivotSelection(const til::point targetPos, bool& targetStart) const noexcept; std::pair _ExpandSelectionAnchors(std::pair anchors) const; diff --git a/src/cascadia/TerminalCore/TerminalSelection.cpp b/src/cascadia/TerminalCore/TerminalSelection.cpp index 1978f5738bc..fd440470f86 100644 --- a/src/cascadia/TerminalCore/TerminalSelection.cpp +++ b/src/cascadia/TerminalCore/TerminalSelection.cpp @@ -63,6 +63,40 @@ std::vector Terminal::_GetSelectionRects() const noexcept return result; } +// Method Description: +// - Helper to determine the selected region of the buffer. Used for rendering. +// Return Value: +// - A vector of rectangles representing the regions to select, line by line. They are absolute coordinates relative to the buffer origin. +std::vector Terminal::_GetSearchSelectionRects(Microsoft::Console::Types::Viewport viewport) const noexcept +{ + std::vector result; + try + { + auto lowerIt = std::lower_bound(_searchSelections.begin(), _searchSelections.end(), viewport.Top(), [](const til::inclusive_rect& rect, til::CoordType value) { + return rect.top < value; + }); + + auto upperIt = std::upper_bound(_searchSelections.begin(), _searchSelections.end(), viewport.BottomExclusive(), [](til::CoordType value, const til::inclusive_rect& rect) { + return value < rect.top; + }); + + for (auto selection = lowerIt; selection != upperIt; ++selection) + { + const auto start = til::point{ selection->left, selection->top }; + const auto end = til::point{ selection->right, selection->top }; + const auto adj = _activeBuffer().GetTextRects(start, end, _blockSelection, false); + for (auto a : adj) + { + result.emplace_back(a); + } + } + + return result; + } + CATCH_LOG(); + return result; +} + // Method Description: // - Identical to GetTextRects if it's a block selection, else returns a single span for the whole selection. // Return Value: @@ -824,6 +858,7 @@ void Terminal::_MoveByBuffer(SelectionDirection direction, til::point& pos) noex void Terminal::ClearSelection() { _assertLocked(); + _searchSelections.clear(); _selection = std::nullopt; _selectionMode = SelectionInteractionMode::None; _selectionIsTargetingUrl = false; @@ -832,27 +867,53 @@ void Terminal::ClearSelection() } // Method Description: -// - get wstring text from highlighted portion of text buffer +// - Get text from highlighted portion of text buffer +// - Optionally, get the highlighted text in HTML and RTF formats // Arguments: -// - singleLine: collapse all of the text to one line +// - singleLine: collapse all of the text to one line. (Turns off trailing whitespace trimming) +// - html: also get text in HTML format +// - rtf: also get text in RTF format // Return Value: -// - wstring text from buffer. If extended to multiple lines, each line is separated by \r\n -const TextBuffer::TextAndColor Terminal::RetrieveSelectedTextFromBuffer(bool singleLine) +// - Plain and formatted selected text from buffer. Empty string represents no data for that format. +// - If extended to multiple lines, each line is separated by \r\n +Terminal::TextCopyData Terminal::RetrieveSelectedTextFromBuffer(const bool singleLine, const bool html, const bool rtf) const { - const auto selectionRects = _GetSelectionRects(); + TextCopyData data; + + if (!IsSelectionActive()) + { + return data; + } const auto GetAttributeColors = [&](const auto& attr) { - return _renderSettings.GetAttributeColors(attr); + const auto [fg, bg] = _renderSettings.GetAttributeColors(attr); + const auto ul = _renderSettings.GetAttributeUnderlineColor(attr); + return std::tuple{ fg, bg, ul }; }; - // GH#6740: Block selection should preserve the visual structure: - // - CRLFs need to be added - so the lines structure is preserved - // - We should apply formatting above to wrapped rows as well (newline should be added). - // GH#9706: Trimming of trailing white-spaces in block selection is configurable. - const auto includeCRLF = !singleLine || _blockSelection; - const auto trimTrailingWhitespace = !singleLine && (!_blockSelection || _trimBlockSelection); - const auto formatWrappedRows = _blockSelection; - return _activeBuffer().GetText(includeCRLF, trimTrailingWhitespace, selectionRects, GetAttributeColors, formatWrappedRows); + const auto& textBuffer = _activeBuffer(); + + const auto req = TextBuffer::CopyRequest::FromConfig(textBuffer, _selection->start, _selection->end, singleLine, _blockSelection, _trimBlockSelection); + data.plainText = textBuffer.GetPlainText(req); + + if (html || rtf) + { + const auto bgColor = _renderSettings.GetAttributeColors({}).second; + const auto isIntenseBold = _renderSettings.GetRenderMode(::Microsoft::Console::Render::RenderSettings::Mode::IntenseIsBold); + const auto fontSizePt = _fontInfo.GetUnscaledSize().height; // already in points + const auto& fontName = _fontInfo.GetFaceName(); + + if (html) + { + data.html = textBuffer.GenHTML(req, fontSizePt, fontName, bgColor, isIntenseBold, GetAttributeColors); + } + if (rtf) + { + data.rtf = textBuffer.GenRTF(req, fontSizePt, fontName, bgColor, isIntenseBold, GetAttributeColors); + } + } + + return data; } // Method Description: diff --git a/src/cascadia/TerminalCore/terminalrenderdata.cpp b/src/cascadia/TerminalCore/terminalrenderdata.cpp index 034a65ab784..8dd9b4dae9a 100644 --- a/src/cascadia/TerminalCore/terminalrenderdata.cpp +++ b/src/cascadia/TerminalCore/terminalrenderdata.cpp @@ -150,6 +150,24 @@ catch (...) return {}; } +std::vector Terminal::GetSearchSelectionRects() noexcept +try +{ + std::vector result; + + for (const auto& lineRect : _GetSearchSelectionRects(_GetVisibleViewport())) + { + result.emplace_back(Viewport::FromInclusive(lineRect)); + } + + return result; +} +catch (...) +{ + LOG_CAUGHT_EXCEPTION(); + return {}; +} + void Terminal::SelectNewRegion(const til::point coordStart, const til::point coordEnd) { #pragma warning(push) @@ -188,6 +206,23 @@ void Terminal::SelectNewRegion(const til::point coordStart, const til::point coo SetSelectionEnd(realCoordEnd, SelectionExpansion::Char); } +void Terminal::SelectSearchRegions(std::vector rects) +{ + _searchSelections.clear(); + for (auto& rect : rects) + { + rect.top -= _VisibleStartIndex(); + rect.bottom -= _VisibleStartIndex(); + + const auto realStart = _ConvertToBufferCell(til::point{ rect.left, rect.top }); + const auto realEnd = _ConvertToBufferCell(til::point{ rect.right, rect.bottom }); + + auto rr = til::inclusive_rect{ realStart.x, realStart.y, realEnd.x, realEnd.y }; + + _searchSelections.emplace_back(rr); + } +} + const std::wstring_view Terminal::GetConsoleTitle() const noexcept { _assertLocked(); diff --git a/src/cascadia/TerminalSettingsEditor/Actions.xaml b/src/cascadia/TerminalSettingsEditor/Actions.xaml index ade657307c4..0c48e710e7f 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.xaml +++ b/src/cascadia/TerminalSettingsEditor/Actions.xaml @@ -326,31 +326,29 @@ - - - - - + + + + - - - - - + + + + diff --git a/src/cascadia/TerminalSettingsEditor/AddProfile.xaml b/src/cascadia/TerminalSettingsEditor/AddProfile.xaml index 25a6bbc0a81..e586f2eca93 100644 --- a/src/cascadia/TerminalSettingsEditor/AddProfile.xaml +++ b/src/cascadia/TerminalSettingsEditor/AddProfile.xaml @@ -21,72 +21,70 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.cpp b/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.cpp index 4bd995b6c4a..04e9433bbba 100644 --- a/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.cpp +++ b/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.cpp @@ -178,6 +178,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Name(TableColorNames[index]); Tag(winrt::box_value(index)); Color(color); + + PropertyChanged({ get_weak(), &ColorTableEntry::_PropertyChangedHandler }); } ColorTableEntry::ColorTableEntry(std::wstring_view tag, Windows::UI::Color color) @@ -185,5 +187,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Name(LocalizedNameForEnumName(L"ColorScheme_", tag, L"Text")); Tag(winrt::box_value(tag)); Color(color); + + PropertyChanged({ get_weak(), &ColorTableEntry::_PropertyChangedHandler }); + } + + void ColorTableEntry::_PropertyChangedHandler(const IInspectable& /*sender*/, const PropertyChangedEventArgs& args) + { + const auto propertyName{ args.PropertyName() }; + if (propertyName == L"Color" || propertyName == L"Name") + { + _PropertyChangedHandlers(*this, PropertyChangedEventArgs{ L"AccessibleName" }); + } } + } diff --git a/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.h b/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.h index 539b0636c3c..7c45638e709 100644 --- a/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.h @@ -63,6 +63,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation ColorTableEntry(uint8_t index, Windows::UI::Color color); ColorTableEntry(std::wstring_view tag, Windows::UI::Color color); + hstring AccessibleName() const + { + return hstring{ fmt::format(FMT_COMPILE(L"{} RGB({}, {}, {})"), _Name, _Color.R, _Color.G, _Color.B) }; + } + WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); WINRT_OBSERVABLE_PROPERTY(Windows::UI::Color, Color, _PropertyChangedHandlers); WINRT_OBSERVABLE_PROPERTY(winrt::hstring, Name, _PropertyChangedHandlers); @@ -70,5 +75,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation private: Windows::UI::Color _color; + + void _PropertyChangedHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args); }; }; diff --git a/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.idl b/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.idl index 3247ef125e3..b041c2ab93f 100644 --- a/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/ColorSchemeViewModel.idl @@ -34,5 +34,6 @@ namespace Microsoft.Terminal.Settings.Editor String Name { get; }; IInspectable Tag; Windows.UI.Color Color; + String AccessibleName { get; }; } } diff --git a/src/cascadia/TerminalSettingsEditor/CommonResources.xaml b/src/cascadia/TerminalSettingsEditor/CommonResources.xaml index b274e3cf58c..509e1782c29 100644 --- a/src/cascadia/TerminalSettingsEditor/CommonResources.xaml +++ b/src/cascadia/TerminalSettingsEditor/CommonResources.xaml @@ -115,7 +115,6 @@ diff --git a/src/cascadia/TerminalSettingsEditor/EditColorScheme.xaml b/src/cascadia/TerminalSettingsEditor/EditColorScheme.xaml index 50a0c1357bb..c98f5c8d6c4 100644 --- a/src/cascadia/TerminalSettingsEditor/EditColorScheme.xaml +++ b/src/cascadia/TerminalSettingsEditor/EditColorScheme.xaml @@ -55,10 +55,10 @@ - - - - - + + + + + + + + + + + + - - - - - - - - - - + + + + + + diff --git a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml index 700cfc61754..e8a3ea62900 100644 --- a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml +++ b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml @@ -25,109 +25,107 @@ - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - - + + + + + - - - - + + + + - - - - + + + + - - - - - - + + + + + diff --git a/src/cascadia/TerminalSettingsEditor/Interaction.xaml b/src/cascadia/TerminalSettingsEditor/Interaction.xaml index 295a584032b..d682aaf64b3 100644 --- a/src/cascadia/TerminalSettingsEditor/Interaction.xaml +++ b/src/cascadia/TerminalSettingsEditor/Interaction.xaml @@ -24,75 +24,73 @@ - - - - - - + + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - - + + + + diff --git a/src/cascadia/TerminalSettingsEditor/Launch.xaml b/src/cascadia/TerminalSettingsEditor/Launch.xaml index 71d0827e2b3..5a30fa771d8 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.xaml +++ b/src/cascadia/TerminalSettingsEditor/Launch.xaml @@ -41,244 +41,242 @@ - - - - - - - - - + + + + + + + + - - - - - - + + + + + + - + - + - - - - - + + + + + - - - - - - + + + + + + - - - - - - - - + + + + + + + + - - - - - - + + + + + + - + - + - + - + - - - - - + + + + + - - - - + + + + - - - - + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + - - + + - - + + - - + Width="118" + IsEnabled="{x:Bind local:Converters.InvertBoolean(ViewModel.UseDefaultLaunchPosition), Mode=OneWay}" + Style="{StaticResource LaunchPositionNumberBoxStyle}" + Value="{x:Bind ViewModel.InitialPosY, Mode=TwoWay}" /> + - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - + IsOn="{x:Bind ViewModel.CenterOnLaunch, Mode=TwoWay}" + Style="{StaticResource ToggleSwitchInExpanderStyle}" /> + + - + diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index 2c18ce11c95..fd2ba5107a9 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -278,6 +278,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // Don't navigate to the same page again. return; } + else + { + // If we are navigating to a new page, scroll to the top + SettingsMainPage_ScrollViewer().ScrollToVerticalOffset(0); + } if (const auto navString = clickedItemContainer.Tag().try_as()) { @@ -332,12 +337,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation contentFrame().Navigate(xaml_typename(), winrt::make(profile, *this)); const auto crumb = winrt::make(breadcrumbTag, RS_(L"Profile_Appearance/Header"), BreadcrumbSubPage::Profile_Appearance); _breadcrumbs.Append(crumb); + SettingsMainPage_ScrollViewer().ScrollToVerticalOffset(0); } else if (currentPage == ProfileSubPage::Advanced) { contentFrame().Navigate(xaml_typename(), profile); const auto crumb = winrt::make(breadcrumbTag, RS_(L"Profile_Advanced/Header"), BreadcrumbSubPage::Profile_Advanced); _breadcrumbs.Append(crumb); + SettingsMainPage_ScrollViewer().ScrollToVerticalOffset(0); } } }); @@ -655,6 +662,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { _Navigate(newTag.as(), BreadcrumbSubPage::None); } + // Since we are navigating to a new profile after deletion, scroll up to the top + SettingsMainPage_ScrollViewer().ChangeView(nullptr, 0.0, nullptr); } } diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.xaml b/src/cascadia/TerminalSettingsEditor/MainPage.xaml index ec690e49c9e..92931355ffa 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.xaml +++ b/src/cascadia/TerminalSettingsEditor/MainPage.xaml @@ -170,18 +170,21 @@ - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - + + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - - - - - + + + + + + + + - - - - + + + + - - - - + + + + - - - - - - + + + + + diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Appearance.xaml b/src/cascadia/TerminalSettingsEditor/Profiles_Appearance.xaml index 710eb62ee16..699f977a9eb 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Appearance.xaml +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Appearance.xaml @@ -41,144 +41,142 @@ Margin="{StaticResource StandardIndentMargin}" Style="{StaticResource DisclaimerStyle}" Visibility="{x:Bind Profile.IsBaseLayer}" /> - - - - - - + + + + + - - - - - + - - - - - - - - - - - - - + + + - - - - - - - - - - - - - + + + - + Value="{x:Bind local:Converters.PercentageToPercentageValue(Profile.Opacity), BindBack=Profile.SetAcrylicOpacityPercentageValue, Mode=TwoWay}" /> + Text="{x:Bind local:Converters.AppendPercentageSign(OpacitySlider.Value), Mode=OneWay}" /> - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Base.cpp b/src/cascadia/TerminalSettingsEditor/Profiles_Base.cpp index 32570a016b7..b181ef80c9f 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Base.cpp +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Base.cpp @@ -52,7 +52,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { DeleteButton().Focus(FocusState::Programmatic); _Profile.FocusDeleteButton(false); - ProfilesBase_ScrollView().ChangeView(nullptr, ProfilesBase_ScrollView().ScrollableHeight(), nullptr); } }); } diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Base.xaml b/src/cascadia/TerminalSettingsEditor/Profiles_Base.xaml index f660f40ed90..11885a52fe3 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Base.xaml +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Base.xaml @@ -31,165 +31,162 @@ Margin="{StaticResource StandardIndentMargin}" Style="{StaticResource DisclaimerStyle}" Visibility="{x:Bind Profile.IsBaseLayer}" /> - - + - - - - - + + + + + - - - - - - - - - + + + + - - - + + + + + diff --git a/src/cascadia/TerminalSettingsEditor/Rendering.xaml b/src/cascadia/TerminalSettingsEditor/Rendering.xaml index 96a6ee95c6a..f19f33c145e 100644 --- a/src/cascadia/TerminalSettingsEditor/Rendering.xaml +++ b/src/cascadia/TerminalSettingsEditor/Rendering.xaml @@ -18,28 +18,26 @@ - - - + + - - - - + + + + - - - - + + + + - - - - - - + + + + + diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp index 6f684f91d66..347a180b708 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp @@ -102,34 +102,14 @@ 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)); + Flush(); + } + void ApplicationState::Flush() + { // 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. - void ApplicationState::Reload() const noexcept - { - _read(); - } - - bool ApplicationState::IsStatePath(const winrt::hstring& filename) - { - static const auto sharedPath{ _sharedPath.filename() }; - static const auto elevatedPath{ _elevatedPath.filename() }; - return filename == sharedPath || filename == elevatedPath; } // Method Description: @@ -299,7 +279,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Json::Value ApplicationState::_toJsonWithBlob(Json::Value& root, FileSource parseSource) const noexcept { { - auto state = _state.lock_shared(); + const 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 @@ -326,7 +306,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void ApplicationState::name(const type& value) noexcept \ { \ { \ - auto state = _state.lock(); \ + const auto state = _state.lock(); \ state->name.emplace(value); \ } \ \ diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.h b/src/cascadia/TerminalSettingsModel/ApplicationState.h index 91183bad076..bacffdba2f7 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.h +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.h @@ -63,15 +63,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation ~ApplicationState(); // Methods - void Reload() const noexcept; + void Flush(); void Reset() noexcept; void FromJson(const Json::Value& root, FileSource parseSource) const noexcept; Json::Value ToJson(FileSource parseSource) const noexcept; - // General getters/setters - bool IsStatePath(const winrt::hstring& filename); - // State getters/setters #define MTSM_APPLICATION_STATE_GEN(source, type, name, key, ...) \ type name() const noexcept; \ diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.idl b/src/cascadia/TerminalSettingsModel/ApplicationState.idl index e16daefe336..02856a4d2d7 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.idl +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.idl @@ -28,11 +28,9 @@ namespace Microsoft.Terminal.Settings.Model [default_interface] runtimeclass ApplicationState { static ApplicationState SharedInstance(); - void Reload(); + void Flush(); void Reset(); - Boolean IsStatePath(String filename); - String SettingsHash; Windows.Foundation.Collections.IVector PersistedWindowLayouts; Windows.Foundation.Collections.IVector RecentCommands; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 8964169f207..7510bb0ebf7 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -410,7 +410,7 @@ bool SettingsLoader::FixupUserSettings() { struct CommandlinePatch { - winrt::guid guid; + winrt::guid guid{}; std::wstring_view before; std::wstring_view after; }; @@ -949,25 +949,6 @@ void CascadiaSettings::_researchOnLoad() // Only do this if we're actually being sampled if (TraceLoggingProviderEnabled(g_hSettingsModelProvider, 0, MICROSOFT_KEYWORD_MEASURES)) { - // GH#13936: We're interested in how many users opt out of useAtlasEngine, - // indicating major issues that would require us to disable it by default again. - { - size_t enabled[2]{}; - for (const auto& profile : _activeProfiles) - { - enabled[profile.UseAtlasEngine()]++; - } - - TraceLoggingWrite( - g_hSettingsModelProvider, - "AtlasEngine_Usage", - TraceLoggingDescription("Event emitted upon settings load, containing the number of profiles opted-in/out of useAtlasEngine"), - TraceLoggingUIntPtr(enabled[0], "UseAtlasEngineDisabled", "Number of profiles for which AtlasEngine is disabled"), - TraceLoggingUIntPtr(enabled[1], "UseAtlasEngineEnabled", "Number of profiles for which AtlasEngine is enabled"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - } - // ----------------------------- RE: Themes ---------------------------- const auto numThemes = GlobalSettings().Themes().Size(); const auto themeInUse = GlobalSettings().CurrentTheme().Name(); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index 3ccc0972bea..9c14376d053 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -83,7 +83,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static constexpr bool debugFeaturesDefault{ true }; #endif - winrt::guid _defaultProfile; + winrt::guid _defaultProfile{}; bool _legacyReloadEnvironmentVariables{ true }; winrt::com_ptr _actionMap{ winrt::make_self() }; diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index 15e91a1d7ae..d0dc6bfcca5 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -90,7 +90,7 @@ Author(s): X(hstring, TabTitle, "tabTitle") \ X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \ X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr) \ - X(bool, UseAtlasEngine, "useAtlasEngine", Feature_AtlasEngine::IsEnabled()) \ + X(bool, UseAtlasEngine, "useAtlasEngine", true) \ X(bool, RightClickContextMenu, "experimental.rightClickContextMenu", false) \ X(Windows::Foundation::Collections::IVector, BellSound, "bellSound", nullptr) \ X(bool, Elevate, "elevate", false) \ diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index 617692691ee..580b06089fd 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -356,6 +356,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _WordDelimiters = globalSettings.WordDelimiters(); _CopyOnSelect = globalSettings.CopyOnSelect(); + _CopyFormatting = globalSettings.CopyFormatting(); _FocusFollowMouse = globalSettings.FocusFollowMouse(); _ForceFullRepaintRendering = globalSettings.ForceFullRepaintRendering(); _SoftwareRendering = globalSettings.SoftwareRendering(); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index 60c9b5fb2c2..009e6503da0 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -91,6 +91,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, uint32_t, CursorHeight, DEFAULT_CURSOR_HEIGHT); INHERITABLE_SETTING(Model::TerminalSettings, hstring, WordDelimiters, DEFAULT_WORD_DELIMITERS); INHERITABLE_SETTING(Model::TerminalSettings, bool, CopyOnSelect, false); + INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::CopyFormat, CopyFormatting, 0); INHERITABLE_SETTING(Model::TerminalSettings, bool, FocusFollowMouse, false); INHERITABLE_SETTING(Model::TerminalSettings, bool, TrimBlockSelection, true); INHERITABLE_SETTING(Model::TerminalSettings, bool, DetectURLs, true); @@ -148,7 +149,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, IEnvironmentVariableMap, EnvironmentVariables); INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::ScrollbarState, ScrollState, Microsoft::Terminal::Control::ScrollbarState::Visible); - INHERITABLE_SETTING(Model::TerminalSettings, bool, UseAtlasEngine, false); + INHERITABLE_SETTING(Model::TerminalSettings, bool, UseAtlasEngine, true); INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale); diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index cdd5d6e78dd..360f37afaad 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -174,6 +174,7 @@ "foreground": "#383A42", "background": "#FAFAFA", "cursorColor": "#4F525D", + "selectionBackground": "#4F525D", "black": "#383A42", "red": "#E45649", "green": "#50A14F", @@ -218,6 +219,7 @@ "foreground": "#657B83", "background": "#FDF6E3", "cursorColor": "#002B36", + "selectionBackground": "#073642", "black": "#002B36", "red": "#DC322F", "green": "#859900", @@ -262,6 +264,7 @@ "foreground": "#555753", "background": "#FFFFFF", "cursorColor": "#000000", + "selectionBackground": "#555753", "black": "#000000", "red": "#CC0000", "green": "#4E9A06", diff --git a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp index f34ca5a6bfd..c38c0221c5a 100644 --- a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp +++ b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp @@ -80,9 +80,12 @@ namespace ControlUnitTests void _standardInit(winrt::com_ptr core) { - // "Consolas" ends up with an actual size of 9x21 at 96DPI. So - // let's just arbitrarily start with a 270x420px (30x20 chars) window - core->Initialize(270, 420, 1.0); + // "Consolas" ends up with an actual size of 9x19 at 96DPI. So + // let's just arbitrarily start with a 270x380px (30x20 chars) window + core->Initialize(270, 380, 1.0); +#ifndef NDEBUG + core->_terminal->_suppressLockChecks = true; +#endif VERIFY_IS_TRUE(core->_initializedTerminal); VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height()); } @@ -113,9 +116,12 @@ namespace ControlUnitTests VERIFY_IS_NOT_NULL(core); VERIFY_IS_FALSE(core->_initializedTerminal); - // "Consolas" ends up with an actual size of 9x21 at 96DPI. So - // let's just arbitrarily start with a 270x420px (30x20 chars) window - core->Initialize(270, 420, 1.0); + // "Consolas" ends up with an actual size of 9x19 at 96DPI. So + // let's just arbitrarily start with a 270x380px (30x20 chars) window + core->Initialize(270, 380, 1.0); +#ifndef NDEBUG + core->_terminal->_suppressLockChecks = true; +#endif VERIFY_IS_TRUE(core->_initializedTerminal); VERIFY_ARE_EQUAL(30, core->_terminal->GetViewport().Width()); } diff --git a/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp b/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp index a48aa968944..192cc2ad9d8 100644 --- a/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp +++ b/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp @@ -88,9 +88,12 @@ namespace ControlUnitTests void _standardInit(winrt::com_ptr core, winrt::com_ptr interactivity) { - // "Consolas" ends up with an actual size of 9x21 at 96DPI. So - // let's just arbitrarily start with a 270x420px (30x20 chars) window - core->Initialize(270, 420, 1.0); + // "Consolas" ends up with an actual size of 9x19 at 96DPI. So + // let's just arbitrarily start with a 270x380px (30x20 chars) window + core->Initialize(270, 380, 1.0); +#ifndef NDEBUG + core->_terminal->_suppressLockChecks = true; +#endif VERIFY_IS_TRUE(core->_initializedTerminal); VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height()); interactivity->Initialize(); diff --git a/src/cascadia/UnitTests_Control/MockConnection.h b/src/cascadia/UnitTests_Control/MockConnection.h index b6255543dcd..e8754c8ae96 100644 --- a/src/cascadia/UnitTests_Control/MockConnection.h +++ b/src/cascadia/UnitTests_Control/MockConnection.h @@ -23,6 +23,7 @@ namespace ControlUnitTests void Resize(uint32_t /*rows*/, uint32_t /*columns*/) noexcept {} void Close() noexcept {} + winrt::guid SessionId() const noexcept { return {}; } winrt::Microsoft::Terminal::TerminalConnection::ConnectionState State() const noexcept { return winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::Connected; } WINRT_CALLBACK(TerminalOutput, winrt::Microsoft::Terminal::TerminalConnection::TerminalOutputHandler); diff --git a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp index 48eeacf53c6..f7948e5c59d 100644 --- a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp @@ -87,7 +87,7 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final TEST_METHOD_SETUP(MethodSetup) { // STEP 1: Set up the Terminal - term = std::make_unique(); + term = std::make_unique(Terminal::TestDummyMarker{}); emptyRenderer = std::make_unique(term.get()); term->Create({ TerminalViewWidth, TerminalViewHeight }, 100, *emptyRenderer); @@ -1201,7 +1201,7 @@ void ConptyRoundtripTests::PassthroughHardReset() // Write a Hard Reset VT sequence to the host, it should come through to the Terminal // along with a DECSET sequence to re-enable win32 input and focus events. expectedOutput.push_back("\033c"); - expectedOutput.push_back("\033[?9001;1004h"); + expectedOutput.push_back("\033[?9001h\033[?1004h"); hostSm.ProcessString(L"\033c"); const auto termSecondView = term->GetViewport(); diff --git a/src/cascadia/UnitTests_TerminalCore/InputTest.cpp b/src/cascadia/UnitTests_TerminalCore/InputTest.cpp index 8c6d3245162..01436fcddf3 100644 --- a/src/cascadia/UnitTests_TerminalCore/InputTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/InputTest.cpp @@ -5,40 +5,33 @@ #include #include "../cascadia/TerminalCore/Terminal.hpp" -#include "../renderer/inc/DummyRenderer.hpp" -#include "consoletaeftemplates.hpp" using namespace WEX::Logging; using namespace WEX::TestExecution; using namespace Microsoft::Terminal::Core; -using namespace Microsoft::Console::Render; + +constexpr Microsoft::Console::VirtualTerminal::TerminalInput::OutputType unhandled() +{ + return {}; +} + +constexpr Microsoft::Console::VirtualTerminal::TerminalInput::OutputType escChar(const wchar_t wch) +{ + const wchar_t buffer[2]{ L'\x1b', wch }; + return { { &buffer[0], 2 } }; +} namespace TerminalCoreUnitTests { class InputTest { TEST_CLASS(InputTest); - TEST_CLASS_SETUP(ClassSetup) - { - DummyRenderer renderer; - term.Create({ 100, 100 }, 0, renderer); - auto inputFn = std::bind(&InputTest::_VerifyExpectedInput, this, std::placeholders::_1); - term.SetWriteInputCallback(inputFn); - return true; - }; TEST_METHOD(AltShiftKey); TEST_METHOD(InvalidKeyEvent); - void _VerifyExpectedInput(std::wstring_view actualInput) - { - VERIFY_ARE_EQUAL(expectedinput.size(), actualInput.size()); - VERIFY_ARE_EQUAL(expectedinput, actualInput); - }; - - Terminal term{}; - std::wstring expectedinput{}; + Terminal term{ Terminal::TestDummyMarker{} }; }; void InputTest::AltShiftKey() @@ -46,21 +39,17 @@ namespace TerminalCoreUnitTests // Tests GH:637 // Verify that Alt+a generates a lowercase a on the input - expectedinput = L"\x1b" - "a"; - VERIFY_IS_TRUE(term.SendCharEvent(L'a', 0, ControlKeyStates::LeftAltPressed)); + VERIFY_ARE_EQUAL(escChar(L'a'), term.SendCharEvent(L'a', 0, ControlKeyStates::LeftAltPressed)); // Verify that Alt+shift+a generates a uppercase a on the input - expectedinput = L"\x1b" - "A"; - VERIFY_IS_TRUE(term.SendCharEvent(L'A', 0, ControlKeyStates::LeftAltPressed | ControlKeyStates::ShiftPressed)); + VERIFY_ARE_EQUAL(escChar(L'A'), term.SendCharEvent(L'A', 0, ControlKeyStates::LeftAltPressed | ControlKeyStates::ShiftPressed)); } void InputTest::InvalidKeyEvent() { // Certain applications like AutoHotKey and its keyboard remapping feature, // send us key events using SendInput() whose values are outside of the valid range. - VERIFY_IS_FALSE(term.SendKeyEvent(0, 123, {}, true)); - VERIFY_IS_FALSE(term.SendKeyEvent(255, 123, {}, true)); + VERIFY_ARE_EQUAL(unhandled(), term.SendKeyEvent(0, 123, {}, true)); + VERIFY_ARE_EQUAL(unhandled(), term.SendKeyEvent(255, 123, {}, true)); } } diff --git a/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp index 8e9971abdff..667de3ad469 100644 --- a/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp @@ -38,7 +38,7 @@ void ScreenSizeLimitsTest::ScreenWidthAndHeightAreClampedToBounds() // Negative values for initial visible row count or column count // are clamped to 1. Too-large positive values are clamped to SHRT_MAX. auto negativeColumnsSettings = winrt::make(10000, 9999999, -1234); - Terminal negativeColumnsTerminal; + Terminal negativeColumnsTerminal{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &negativeColumnsTerminal }; negativeColumnsTerminal.CreateFromSettings(negativeColumnsSettings, renderer); auto actualDimensions = negativeColumnsTerminal.GetViewport().Dimensions(); @@ -47,7 +47,7 @@ void ScreenSizeLimitsTest::ScreenWidthAndHeightAreClampedToBounds() // Zero values are clamped to 1 as well. auto zeroRowsSettings = winrt::make(10000, 0, 9999999); - Terminal zeroRowsTerminal; + Terminal zeroRowsTerminal{ Terminal::TestDummyMarker{} }; zeroRowsTerminal.CreateFromSettings(zeroRowsSettings, renderer); actualDimensions = zeroRowsTerminal.GetViewport().Dimensions(); VERIFY_ARE_EQUAL(actualDimensions.height, 1, L"Row count clamped to 1"); @@ -64,32 +64,32 @@ void ScreenSizeLimitsTest::ScrollbackHistorySizeIsClampedToBounds() // Zero history size is acceptable. auto noHistorySettings = winrt::make(0, visibleRowCount, 100); - Terminal noHistoryTerminal; + Terminal noHistoryTerminal{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &noHistoryTerminal }; noHistoryTerminal.CreateFromSettings(noHistorySettings, renderer); VERIFY_ARE_EQUAL(noHistoryTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount, L"History size of 0 is accepted"); // Negative history sizes are clamped to zero. auto negativeHistorySizeSettings = winrt::make(-100, visibleRowCount, 100); - Terminal negativeHistorySizeTerminal; + Terminal negativeHistorySizeTerminal{ Terminal::TestDummyMarker{} }; negativeHistorySizeTerminal.CreateFromSettings(negativeHistorySizeSettings, renderer); VERIFY_ARE_EQUAL(negativeHistorySizeTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount, L"Negative history size is clamped to 0"); // History size + initial visible rows == SHRT_MAX is acceptable. auto maxHistorySizeSettings = winrt::make(SHRT_MAX - visibleRowCount, visibleRowCount, 100); - Terminal maxHistorySizeTerminal; + Terminal maxHistorySizeTerminal{ Terminal::TestDummyMarker{} }; maxHistorySizeTerminal.CreateFromSettings(maxHistorySizeSettings, renderer); VERIFY_ARE_EQUAL(maxHistorySizeTerminal.GetTextBuffer().TotalRowCount(), SHRT_MAX, L"History size == SHRT_MAX - initial row count is accepted"); // History size + initial visible rows == SHRT_MAX + 1 will be clamped slightly. auto justTooBigHistorySizeSettings = winrt::make(SHRT_MAX - visibleRowCount + 1, visibleRowCount, 100); - Terminal justTooBigHistorySizeTerminal; + Terminal justTooBigHistorySizeTerminal{ Terminal::TestDummyMarker{} }; justTooBigHistorySizeTerminal.CreateFromSettings(justTooBigHistorySizeSettings, renderer); VERIFY_ARE_EQUAL(justTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), SHRT_MAX, L"History size == 1 + SHRT_MAX - initial row count is clamped to SHRT_MAX - initial row count"); // Ridiculously large history sizes are also clamped. auto farTooBigHistorySizeSettings = winrt::make(99999999, visibleRowCount, 100); - Terminal farTooBigHistorySizeTerminal; + Terminal farTooBigHistorySizeTerminal{ Terminal::TestDummyMarker{} }; farTooBigHistorySizeTerminal.CreateFromSettings(farTooBigHistorySizeSettings, renderer); VERIFY_ARE_EQUAL(farTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), SHRT_MAX, L"History size that is far too large is clamped to SHRT_MAX - initial row count"); } @@ -111,7 +111,7 @@ void ScreenSizeLimitsTest::ResizeIsClampedToBounds() auto settings = winrt::make(historySize, initialVisibleRowCount, initialVisibleColCount); Log::Comment(L"First create a terminal with fewer than SHRT_MAX lines"); - Terminal terminal; + Terminal terminal{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &terminal }; terminal.CreateFromSettings(settings, renderer); VERIFY_ARE_EQUAL(terminal.GetTextBuffer().TotalRowCount(), historySize + initialVisibleRowCount); diff --git a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp index 12223e94a63..21022f30333 100644 --- a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp @@ -57,8 +57,9 @@ namespace HRESULT InvalidateCircling(_Out_ bool* /*pForcePaint*/) noexcept { return S_OK; } HRESULT PaintBackground() noexcept { return S_OK; } HRESULT PaintBufferLine(std::span /*clusters*/, til::point /*coord*/, bool /*fTrimLeft*/, bool /*lineWrapped*/) noexcept { return S_OK; } - HRESULT PaintBufferGridLines(GridLineSet /*lines*/, COLORREF /*color*/, size_t /*cchLine*/, til::point /*coordTarget*/) noexcept { return S_OK; } + HRESULT PaintBufferGridLines(GridLineSet /*lines*/, COLORREF /*gridlineColor*/, COLORREF /*underlineColor*/, size_t /*cchLine*/, til::point /*coordTarget*/) noexcept { return S_OK; } HRESULT PaintSelection(const til::rect& /*rect*/) noexcept { return S_OK; } + HRESULT PaintSelections(const std::vector& /*rects*/) noexcept { return S_OK; } HRESULT PaintCursor(const CursorOptions& /*options*/) noexcept { return S_OK; } HRESULT UpdateDrawingBrushes(const TextAttribute& /*textAttributes*/, const RenderSettings& /*renderSettings*/, gsl::not_null /*pData*/, bool /*usingSoftFont*/, bool /*isSettingDefaultBrushes*/) noexcept { return S_OK; } HRESULT UpdateFont(const FontInfoDesired& /*FontInfoDesired*/, _Out_ FontInfo& /*FontInfo*/) noexcept { return S_OK; } @@ -107,7 +108,7 @@ class TerminalCoreUnitTests::ScrollTest final TEST_METHOD_SETUP(MethodSetup) { - _term = std::make_unique<::Microsoft::Terminal::Core::Terminal>(); + _term = std::make_unique<::Microsoft::Terminal::Core::Terminal>(Terminal::TestDummyMarker{}); _scrollBarNotification = std::make_shared>(); _term->SetScrollPositionChangedCallback([scrollBarNotification = _scrollBarNotification](const int top, const int height, const int bottom) { diff --git a/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp b/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp index 894c5b7764f..195eb17fcc3 100644 --- a/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp @@ -46,7 +46,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectUnit) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -59,7 +59,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectArea) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -113,7 +113,7 @@ namespace TerminalCoreUnitTests // Test SetSelectionAnchor(til::point) and SetSelectionEnd(til::point) // Behavior: clamp coord to viewport. auto ValidateSingleClickSelection = [&](til::CoordType scrollback, const til::inclusive_rect& expected) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 10, 10 }, scrollback, renderer); @@ -126,7 +126,7 @@ namespace TerminalCoreUnitTests // Behavior: clamp coord to viewport. // Then, do double click selection. auto ValidateDoubleClickSelection = [&](til::CoordType scrollback, const til::inclusive_rect& expected) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 10, 10 }, scrollback, renderer); @@ -138,7 +138,7 @@ namespace TerminalCoreUnitTests // Behavior: clamp coord to viewport. // Then, do triple click selection. auto ValidateTripleClickSelection = [&](til::CoordType scrollback, const til::inclusive_rect& expected) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 10, 10 }, scrollback, renderer); @@ -171,7 +171,7 @@ namespace TerminalCoreUnitTests - All selection expansion functions will operate as if they were performed at the boundary */ - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 10, 10 }, 0, renderer); @@ -213,7 +213,7 @@ namespace TerminalCoreUnitTests - All selection expansion functions will operate as if they were performed at the boundary */ - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 10, 10 }, 0, renderer); @@ -299,7 +299,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectBoxArea) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -335,7 +335,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectAreaAfterScroll) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; til::CoordType scrollbackLines = 5; term.Create({ 100, 100 }, scrollbackLines, renderer); @@ -385,7 +385,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectWideGlyph_Trailing) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -408,7 +408,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectWideGlyph_Leading) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -431,7 +431,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectWideGlyphsInBoxSelection) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -486,7 +486,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(DoubleClick_GeneralCase) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -509,7 +509,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(DoubleClick_Delimiter) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -530,7 +530,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(DoubleClick_DelimiterClass) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -558,7 +558,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(DoubleClickDrag_Right) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -587,7 +587,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(DoubleClickDrag_Left) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -616,7 +616,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(TripleClick_GeneralCase) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -630,7 +630,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(TripleClickDrag_Horizontal) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -647,7 +647,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(TripleClickDrag_Vertical) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -675,7 +675,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(ShiftClick) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -792,7 +792,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(Pivot) { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); diff --git a/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp b/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp index 683740c155b..9f002db5098 100644 --- a/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp @@ -48,7 +48,7 @@ using namespace TerminalCoreUnitTests; void TerminalApiTest::SetColorTableEntry() { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -67,7 +67,7 @@ void TerminalApiTest::SetColorTableEntry() // PrintString() is called with more code units than the buffer width. void TerminalApiTest::PrintStringOfSurrogatePairs() { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 3, renderer); @@ -134,7 +134,7 @@ void TerminalApiTest::PrintStringOfSurrogatePairs() void TerminalApiTest::CursorVisibility() { // GH#3093 - Cursor Visibility and On states shouldn't affect each other - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -166,7 +166,7 @@ void TerminalApiTest::CursorVisibility() void TerminalApiTest::CursorVisibilityViaStateMachine() { // This is a nearly literal copy-paste of ScreenBufferTests::TestCursorIsOn, adapted for the Terminal - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -218,7 +218,7 @@ void TerminalApiTest::CursorVisibilityViaStateMachine() void TerminalApiTest::CheckDoubleWidthCursor() { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -262,7 +262,7 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlink() { // This is a nearly literal copy-paste of ScreenBufferTests::TestAddHyperlink, adapted for the Terminal - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -288,7 +288,7 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlinkCustomId() { // This is a nearly literal copy-paste of ScreenBufferTests::TestAddHyperlinkCustomId, adapted for the Terminal - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -316,7 +316,7 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlinkCustomIdDifferentUri() { // This is a nearly literal copy-paste of ScreenBufferTests::TestAddHyperlinkCustomId, adapted for the Terminal - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -344,7 +344,7 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlinkCustomIdDifferentUri() void TerminalCoreUnitTests::TerminalApiTest::SetTaskbarProgress() { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); @@ -415,7 +415,7 @@ void TerminalCoreUnitTests::TerminalApiTest::SetTaskbarProgress() void TerminalCoreUnitTests::TerminalApiTest::SetWorkingDirectory() { - Terminal term; + Terminal term{ Terminal::TestDummyMarker{} }; DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); diff --git a/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp b/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp index 1ea9e0be168..d789bfc5583 100644 --- a/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp @@ -56,7 +56,7 @@ class TerminalCoreUnitTests::TerminalBufferTests final TEST_METHOD_SETUP(MethodSetup) { // STEP 1: Set up the Terminal - term = std::make_unique(); + term = std::make_unique(Terminal::TestDummyMarker{}); emptyRenderer = std::make_unique(term.get()); term->Create({ TerminalViewWidth, TerminalViewHeight }, TerminalHistoryLength, *emptyRenderer); return true; diff --git a/src/cascadia/WinRTUtils/WinRTUtils.vcxproj b/src/cascadia/WinRTUtils/WinRTUtils.vcxproj index 959daaddf8f..13052c590be 100644 --- a/src/cascadia/WinRTUtils/WinRTUtils.vcxproj +++ b/src/cascadia/WinRTUtils/WinRTUtils.vcxproj @@ -19,6 +19,7 @@ + @@ -52,4 +53,4 @@ - + \ No newline at end of file diff --git a/src/cascadia/WinRTUtils/WinRTUtils.vcxproj.filters b/src/cascadia/WinRTUtils/WinRTUtils.vcxproj.filters index 5e47f13f57a..a1d17001d20 100644 --- a/src/cascadia/WinRTUtils/WinRTUtils.vcxproj.filters +++ b/src/cascadia/WinRTUtils/WinRTUtils.vcxproj.filters @@ -2,21 +2,21 @@ + + - + + - - - diff --git a/src/cascadia/WinRTUtils/inc/SafeDispatcherTimer.h b/src/cascadia/WinRTUtils/inc/SafeDispatcherTimer.h new file mode 100644 index 00000000000..1a61bb254b6 --- /dev/null +++ b/src/cascadia/WinRTUtils/inc/SafeDispatcherTimer.h @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +// Par for the course, the XAML timer class is "self-referential". Releasing all references +// to an instance will not stop the timer. Only calling Stop() explicitly will achieve that. +struct SafeDispatcherTimer +{ + SafeDispatcherTimer() = default; + SafeDispatcherTimer(SafeDispatcherTimer const&) = delete; + SafeDispatcherTimer& operator=(SafeDispatcherTimer const&) = delete; + SafeDispatcherTimer(SafeDispatcherTimer&&) = delete; + SafeDispatcherTimer& operator=(SafeDispatcherTimer&&) = delete; + + ~SafeDispatcherTimer() + { + Destroy(); + } + + explicit operator bool() const noexcept + { + return _timer != nullptr; + } + + winrt::Windows::Foundation::TimeSpan Interval() + { + return _getTimer().Interval(); + } + + void Interval(winrt::Windows::Foundation::TimeSpan const& value) + { + _getTimer().Interval(value); + } + + bool IsEnabled() + { + return _timer && _timer.IsEnabled(); + } + + void Tick(winrt::Windows::Foundation::EventHandler const& handler) + { + auto& timer = _getTimer(); + if (_token) + { + timer.Tick(_token); + } + _token = timer.Tick(handler); + } + + void Start() + { + _getTimer().Start(); + } + + void Stop() const + { + if (_timer) + { + _timer.Stop(); + } + } + + void Destroy() + { + if (!_timer) + { + return; + } + + _timer.Stop(); + if (_token) + { + _timer.Tick(_token); + } + _timer = nullptr; + _token = {}; + } + +private: + ::winrt::Windows::UI::Xaml::DispatcherTimer& _getTimer() + { + if (!_timer) + { + _timer = ::winrt::Windows::UI::Xaml::DispatcherTimer{}; + } + return _timer; + } + + ::winrt::Windows::UI::Xaml::DispatcherTimer _timer{ nullptr }; + winrt::event_token _token; +}; diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 5af3a80d50a..6a0e0ed698d 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -438,10 +438,7 @@ void AppHost::Close() // After calling _window->Close() we should avoid creating more WinUI related actions. // I suspect WinUI wouldn't like that very much. As such unregister all event handlers first. _revokers = {}; - if (_frameTimer) - { - _frameTimer.Tick(_frameTimerToken); - } + _frameTimer.Destroy(); _showHideWindowThrottler.reset(); _revokeWindowCallbacks(); @@ -538,23 +535,19 @@ void AppHost::LastTabClosed(const winrt::Windows::Foundation::IInspectable& /*se { _windowLogic.ClearPersistedWindowState(); } - - // If the user closes the last tab, in the last window, _by closing the tab_ - // (not by closing the whole window), we need to manually persist an empty - // window state here. That will cause the terminal to re-open with the usual - // settings (not the persisted state) - if (args.ClearPersistedState() && - _windowManager.GetNumberOfPeasants() == 1) - { - _windowLogic.ClearPersistedWindowState(); - } - // Remove ourself from the list of peasants so that we aren't included in // any future requests. This will also mean we block until any existing // event handler finishes. _windowManager.SignalClose(_peasant); - PostQuitMessage(0); + if (Utils::IsWindows11()) + { + PostQuitMessage(0); + } + else + { + PostMessageW(_window->GetInteropHandle(), WM_REFRIGERATE, 0, 0); + } } LaunchPosition AppHost::_GetWindowLaunchPosition() @@ -979,7 +972,8 @@ winrt::Windows::Foundation::IAsyncOperation AppHost::_GetWindowL co_await wil::resume_foreground(_windowLogic.GetRoot().Dispatcher()); const auto strongThis = weakThis.lock(); - if (!strongThis) + // GH #16235: If we don't have a window logic, we're already refrigerating, and won't have our _window either. + if (!strongThis || _windowLogic == nullptr) { co_return layoutJson; } @@ -1189,12 +1183,8 @@ void AppHost::_startFrameTimer() // _updateFrameColor, which will actually handle setting the colors. If we // already have a timer, just start that one. - if (_frameTimer == nullptr) - { - _frameTimer = winrt::Windows::UI::Xaml::DispatcherTimer(); - _frameTimer.Interval(FrameUpdateInterval); - _frameTimerToken = _frameTimer.Tick({ this, &AppHost::_updateFrameColor }); - } + _frameTimer.Tick({ this, &AppHost::_updateFrameColor }); + _frameTimer.Interval(FrameUpdateInterval); _frameTimer.Start(); } @@ -1267,7 +1257,8 @@ winrt::fire_and_forget AppHost::_QuitRequested(const winrt::Windows::Foundation: co_await wil::resume_foreground(_windowLogic.GetRoot().Dispatcher()); const auto strongThis = weakThis.lock(); - if (!strongThis) + // GH #16235: If we don't have a window logic, we're already refrigerating, and won't have our _window either. + if (!strongThis || _windowLogic == nullptr) { co_return; } @@ -1431,7 +1422,7 @@ winrt::fire_and_forget AppHost::_WindowInitializedHandler(const winrt::Windows:: // If we're gone on the other side of this co_await, well, that's fine. Just bail. const auto strongThis = weakThis.lock(); - if (!strongThis) + if (!strongThis || _window == nullptr) { co_return; } diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index fd912aee67d..60b96103124 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +#pragma once + #include "pch.h" #include "NonClientIslandWindow.h" #include "NotificationIcon.h" @@ -9,6 +11,8 @@ class AppHost : public std::enable_shared_from_this { public: + static constexpr DWORD WM_REFRIGERATE = WM_APP + 0; + AppHost(const winrt::TerminalApp::AppLogic& logic, winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args, const winrt::Microsoft::Terminal::Remoting::WindowManager& manager, @@ -56,7 +60,7 @@ class AppHost : public std::enable_shared_from_this std::shared_ptr> _showHideWindowThrottler; std::chrono::time_point _started; - winrt::Windows::UI::Xaml::DispatcherTimer _frameTimer{ nullptr }; + SafeDispatcherTimer _frameTimer; uint32_t _launchShowWindowCommand{ SW_NORMAL }; @@ -165,7 +169,6 @@ class AppHost : public std::enable_shared_from_this void _updateFrameColor(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::Foundation::IInspectable&); winrt::event_token _GetWindowLayoutRequestedToken; - winrt::event_token _frameTimerToken; // Helper struct. By putting these all into one struct, we can revoke them // all at once, by assigning _revokers to a fresh Revokers instance. That'll diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index ee538bfa71a..bab65132266 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -26,6 +26,11 @@ NonClientIslandWindow::NonClientIslandWindow(const ElementTheme& requestedTheme) { } +NonClientIslandWindow::~NonClientIslandWindow() +{ + Close(); +} + void NonClientIslandWindow::Close() { // Avoid further callbacks into XAML/WinUI-land after we've Close()d the DesktopWindowXamlSource diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h index d173ad68c2d..b2cbb5a8a29 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h @@ -30,6 +30,7 @@ class NonClientIslandWindow : public IslandWindow static constexpr const int topBorderVisibleHeight = 1; NonClientIslandWindow(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme) noexcept; + ~NonClientIslandWindow() override; void Refrigerate() noexcept override; diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index 47a11bba38e..5b1e7e496aa 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -5,14 +5,10 @@ #include "WindowEmperor.h" #include "../inc/WindowingBehavior.h" - #include "../../types/inc/utils.hpp" - #include "../WinRTUtils/inc/WtExeUtils.h" - #include "resource.h" #include "NotificationIcon.h" -#include using namespace winrt; using namespace winrt::Microsoft::Terminal; @@ -38,26 +34,6 @@ WindowEmperor::WindowEmperor() noexcept : }); _dispatcher = winrt::Windows::System::DispatcherQueue::GetForCurrentThread(); - - // BODGY - // - // There's a mysterious crash in XAML on Windows 10 if you just let the App - // get dtor'd. By all accounts, it doesn't make sense. To mitigate this, we - // need to intentionally leak a reference to our App. Crazily, if you just - // let the app get cleaned up with the rest of the process when the process - // exits, then it doesn't crash. But if you let it get explicitly dtor'd, it - // absolutely will crash on exit. - // - // GH#15410 has more details. - - auto a{ _app }; - ::winrt::detach_abi(a); -} - -WindowEmperor::~WindowEmperor() -{ - _app.Close(); - _app = nullptr; } void _buildArgsFromCommandline(std::vector& args) @@ -82,7 +58,7 @@ void _buildArgsFromCommandline(std::vector& args) } } -bool WindowEmperor::HandleCommandlineArgs() +void WindowEmperor::HandleCommandlineArgs(int nCmdShow) { std::vector args; _buildArgsFromCommandline(args); @@ -98,28 +74,32 @@ bool WindowEmperor::HandleCommandlineArgs() } } - // Get the requested initial state of the window from our startup info. For - // something like `start /min`, this will set the wShowWindow member to - // SW_SHOWMINIMIZED. We'll need to make sure is bubbled all the way through, - // so we can open a new window with the same state. - STARTUPINFOW si; - GetStartupInfoW(&si); - const uint32_t showWindow = WI_IsFlagSet(si.dwFlags, STARTF_USESHOWWINDOW) ? si.wShowWindow : SW_SHOW; + // GetEnvironmentStringsW() returns a double-null terminated string. + // The hstring(wchar_t*) constructor however only works for regular null-terminated strings. + // Due to that we need to manually search for the terminator. + winrt::hstring env; + { + const wil::unique_environstrings_ptr strings{ GetEnvironmentStringsW() }; + const auto beg = strings.get(); + auto end = beg; - const auto currentEnv{ til::env::from_current_environment() }; + for (; *end; end += wcsnlen(end, SIZE_T_MAX) + 1) + { + } - Remoting::CommandlineArgs eventArgs{ { args }, { cwd }, showWindow, winrt::hstring{ currentEnv.to_string() } }; + env = winrt::hstring{ beg, gsl::narrow(end - beg) }; + } + const Remoting::CommandlineArgs eventArgs{ args, cwd, gsl::narrow_cast(nCmdShow), std::move(env) }; const auto isolatedMode{ _app.Logic().IsolatedMode() }; - const auto result = _manager.ProposeCommandline(eventArgs, isolatedMode); + int exitCode = 0; - const bool makeWindow = result.ShouldCreateWindow(); - if (makeWindow) + if (result.ShouldCreateWindow()) { _createNewWindowThread(Remoting::WindowRequestedArgs{ result, eventArgs }); - _becomeMonarch(); + WaitForWindows(); } else { @@ -127,11 +107,16 @@ bool WindowEmperor::HandleCommandlineArgs() if (!res.Message.empty()) { AppHost::s_DisplayMessageBox(res); - std::quick_exit(res.ExitCode); } + exitCode = res.ExitCode; } - return makeWindow; + // There's a mysterious crash in XAML on Windows 10 if you just let _app get destroyed (GH#15410). + // We also need to ensure that all UI threads exit before WindowEmperor leaves the scope on the main thread (MSFT:46744208). + // Both problems can be solved and the shutdown accelerated by using TerminateProcess. + // std::exit(), etc., cannot be used here, because those use ExitProcess for unpackaged applications. + TerminateProcess(GetCurrentProcess(), gsl::narrow_cast(exitCode)); + __assume(false); } void WindowEmperor::WaitForWindows() @@ -142,6 +127,9 @@ void WindowEmperor::WaitForWindows() TranslateMessage(&message); DispatchMessage(&message); } + + _finalizeSessionPersistence(); + TerminateProcess(GetCurrentProcess(), 0); } void WindowEmperor::_createNewWindowThread(const Remoting::WindowRequestedArgs& args) @@ -584,21 +572,20 @@ LRESULT WindowEmperor::_messageHandler(UINT const message, WPARAM const wParam, // we'll undoubtedly crash. winrt::fire_and_forget WindowEmperor::_close() { - { - auto fridge{ _oldThreads.lock() }; - for (auto& window : *fridge) - { - window->ThrowAway(); - } - fridge->clear(); - } - // Important! Switch back to the main thread for the emperor. That way, the // quit will go to the emperor's message pump. co_await wil::resume_foreground(_dispatcher); PostQuitMessage(0); } +void WindowEmperor::_finalizeSessionPersistence() const +{ + const auto state = ApplicationState::SharedInstance(); + + // Ensure to write the state.json before we TerminateProcess() + state.Flush(); +} + #pragma endregion #pragma region GlobalHotkeys diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.h b/src/cascadia/WindowsTerminal/WindowEmperor.h index 47e8b356195..9f44a276c3c 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.h +++ b/src/cascadia/WindowsTerminal/WindowEmperor.h @@ -24,10 +24,9 @@ class WindowEmperor : public std::enable_shared_from_this { public: WindowEmperor() noexcept; - ~WindowEmperor(); void WaitForWindows(); - bool HandleCommandlineArgs(); + void HandleCommandlineArgs(int nCmdShow); private: void _createNewWindowThread(const winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs& args); @@ -79,6 +78,7 @@ class WindowEmperor : public std::enable_shared_from_this winrt::fire_and_forget _setupGlobalHotkeys(); winrt::fire_and_forget _close(); + void _finalizeSessionPersistence() const; void _createNotificationIcon(); void _destroyNotificationIcon(); diff --git a/src/cascadia/WindowsTerminal/WindowThread.cpp b/src/cascadia/WindowsTerminal/WindowThread.cpp index 2c70741ad1c..bafe6ef97e5 100644 --- a/src/cascadia/WindowsTerminal/WindowThread.cpp +++ b/src/cascadia/WindowsTerminal/WindowThread.cpp @@ -4,6 +4,8 @@ #include "pch.h" #include "WindowThread.h" +using namespace winrt::Microsoft::Terminal::Remoting; + WindowThread::WindowThread(winrt::TerminalApp::AppLogic logic, winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args, winrt::Microsoft::Terminal::Remoting::WindowManager manager, @@ -81,17 +83,6 @@ void WindowThread::RundownForExit() _pumpRemainingXamlMessages(); } -void WindowThread::ThrowAway() -{ - // raise the signal to unblock KeepWarm. We won't have a host, so we'll drop - // out of the message loop to eventually RundownForExit. - // - // This should only be called when the app is fully quitting. After this is - // called on any thread, on win10, we won't be able to call into XAML - // anymore. - _microwaveBuzzer.notify_one(); -} - // Method Description: // - Check if we should keep this window alive, to try it's message loop again. // If we were refrigerated for later, then this will block the thread on the @@ -108,29 +99,24 @@ bool WindowThread::KeepWarm() return true; } - // If we're refrigerated, then wait on the microwave signal, which will be - // raised when we get re-heated by another thread to reactivate us. - - if (_warmWindow != nullptr) + // Even when the _host has been destroyed the HWND will continue receiving messages, in particular WM_DISPATCHNOTIFY at least once a second. This is important to Windows as it keeps your room warm. + MSG msg; + for (;;) { - std::unique_lock lock(_microwave); - _microwaveBuzzer.wait(lock); - - // If ThrowAway() was called, then the buzzer will be signalled without - // setting a new _host. In that case, the app is quitting, for real. We - // just want to exit with false. - const bool reheated = _host != nullptr; - if (reheated) + if (!GetMessageW(&msg, nullptr, 0, 0)) + { + return false; + } + // We're using a single window message (WM_REFRIGERATE) to indicate both + // state transitions. In this case, the window is actually being woken up. + if (msg.message == AppHost::WM_REFRIGERATE) { _UpdateSettingsRequestedToken = _host->UpdateSettingsRequested([this]() { _UpdateSettingsRequestedHandlers(); }); // Re-initialize the host here, on the window thread _host->Initialize(); + return true; } - return reheated; - } - else - { - return false; + DispatchMessageW(&msg); } } @@ -154,24 +140,22 @@ void WindowThread::Refrigerate() // Method Description: // - "Reheat" this thread for reuse. We'll build a new AppHost, and pass in the -// existing window to it. We'll then trigger the _microwaveBuzzer, so KeepWarm -// (which is on the UI thread) will get unblocked, and we can initialize this -// window. -void WindowThread::Microwave( - winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args, - winrt::Microsoft::Terminal::Remoting::Peasant peasant) +// existing window to it. We'll then wake up the thread stuck in KeepWarm(). +void WindowThread::Microwave(WindowRequestedArgs args, Peasant peasant) { + const auto hwnd = _warmWindow->GetInteropHandle(); + _peasant = std::move(peasant); _args = std::move(args); - _host = std::make_shared<::AppHost>(_appLogic, - _args, - _manager, - _peasant, - std::move(_warmWindow)); + _host = std::make_shared(_appLogic, + _args, + _manager, + _peasant, + std::move(_warmWindow)); // raise the signal to unblock KeepWarm and start the window message loop again. - _microwaveBuzzer.notify_one(); + PostMessageW(hwnd, AppHost::WM_REFRIGERATE, 0, 0); } winrt::TerminalApp::TerminalWindow WindowThread::Logic() @@ -198,6 +182,15 @@ int WindowThread::_messagePump() while (GetMessageW(&message, nullptr, 0, 0)) { + // We're using a single window message (WM_REFRIGERATE) to indicate both + // state transitions. In this case, the window is actually being refrigerated. + // This will break us out of our main message loop we'll eventually start + // the loop in WindowThread::KeepWarm to await a call to Microwave(). + if (message.message == AppHost::WM_REFRIGERATE) + { + break; + } + // GH#638 (Pressing F7 brings up both the history AND a caret browsing message) // The Xaml input stack doesn't allow an application to suppress the "caret browsing" // dialog experience triggered when you press F7. Official recommendation from the Xaml diff --git a/src/cascadia/WindowsTerminal/WindowThread.h b/src/cascadia/WindowsTerminal/WindowThread.h index a1af2db6500..c536e4297aa 100644 --- a/src/cascadia/WindowsTerminal/WindowThread.h +++ b/src/cascadia/WindowsTerminal/WindowThread.h @@ -22,7 +22,6 @@ class WindowThread : public std::enable_shared_from_this void Microwave( winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args, winrt::Microsoft::Terminal::Remoting::Peasant peasant); - void ThrowAway(); uint64_t PeasantID(); @@ -43,8 +42,6 @@ class WindowThread : public std::enable_shared_from_this winrt::event_token _UpdateSettingsRequestedToken; std::unique_ptr<::IslandWindow> _warmWindow{ nullptr }; - std::mutex _microwave; - std::condition_variable _microwaveBuzzer; int _messagePump(); void _pumpRemainingXamlMessages(); diff --git a/src/cascadia/WindowsTerminal/main.cpp b/src/cascadia/WindowsTerminal/main.cpp index 09cac10710c..033f247f46d 100644 --- a/src/cascadia/WindowsTerminal/main.cpp +++ b/src/cascadia/WindowsTerminal/main.cpp @@ -83,7 +83,7 @@ static void EnsureNativeArchitecture() } } -int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) +int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int nCmdShow) { TraceLoggingRegister(g_hWindowsTerminalProvider); ::Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hWindowsTerminalProvider); @@ -115,8 +115,5 @@ int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) winrt::init_apartment(winrt::apartment_type::single_threaded); const auto emperor = std::make_shared<::WindowEmperor>(); - if (emperor->HandleCommandlineArgs()) - { - emperor->WaitForWindows(); - } + emperor->HandleCommandlineArgs(nCmdShow); } diff --git a/src/cascadia/WindowsTerminal/pch.h b/src/cascadia/WindowsTerminal/pch.h index eafe88dfb6c..5011dbeba61 100644 --- a/src/cascadia/WindowsTerminal/pch.h +++ b/src/cascadia/WindowsTerminal/pch.h @@ -48,7 +48,7 @@ Module Name: #include // Needed just for XamlIslands to work at all: -#include +#include #include #include #include @@ -63,7 +63,7 @@ Module Name: #include #include #include -#include +#include #include #include #include @@ -91,5 +91,7 @@ TRACELOGGING_DECLARE_PROVIDER(g_hWindowsTerminalProvider); #include "til.h" #include "til/mutex.h" +#include + #include #include // must go after the CoreDispatcher type is defined diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index c18e70e7cb2..1b61775becc 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -72,7 +72,8 @@ X(winrt::Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ X(bool, ForceFullRepaintRendering, false) \ X(bool, SoftwareRendering, false) \ - X(bool, UseAtlasEngine, false) \ + X(bool, UseAtlasEngine, true) \ X(bool, UseBackgroundImageForWindow, false) \ X(bool, ShowMarks, false) \ + X(winrt::Microsoft::Terminal::Control::CopyFormat, CopyFormatting, 0) \ X(bool, RightClickContextMenu, false) diff --git a/src/common.build.pre.props b/src/common.build.pre.props index a1cf37adbda..17d4b603927 100644 --- a/src/common.build.pre.props +++ b/src/common.build.pre.props @@ -131,8 +131,12 @@ C26456: Operator 'A' hides a non-virtual operator 'B' (c.128) I think these rules are for when you fully bought into OOP? We didn't and it breaks WRL and large parts of conhost code. + C26478: Don't use std::move on constant variables. (es.56). + This diagnostic is broken in VS 17.7 which our CI currently uses. It's fixed in 17.8. + C26494: Variable 'index' is uninitialized. Always initialize an object (type. 5). + This diagnostic is broken in VS 17.7 which our CI currently uses. It's fixed in 17.8. --> - 4201;4312;4467;5105;26434;26445;26456;%(DisableSpecificWarnings) + 4201;4312;4467;5105;26434;26445;26456;26478;26494;%(DisableSpecificWarnings) _WINDOWS;EXTERNAL_BUILD;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;%(PreprocessorDefinitions) true precomp.h diff --git a/src/common.nugetversions.targets b/src/common.nugetversions.targets index 1c152920d22..f3cb03f6cb8 100644 --- a/src/common.nugetversions.targets +++ b/src/common.nugetversions.targets @@ -62,7 +62,7 @@ - + @@ -93,7 +93,7 @@ - + diff --git a/src/features.xml b/src/features.xml index 47b00ee5ede..bf48f8741da 100644 --- a/src/features.xml +++ b/src/features.xml @@ -76,16 +76,6 @@ - - Feature_AtlasEngine - If enabled, AtlasEngine is used by default - AlwaysEnabled - - Release - WindowsInbox - - - Feature_AtlasEnginePresentFallback We don't feel super confident in our usage of the Present1 API, so this settings adds a fallback to Present on error @@ -198,4 +188,14 @@ + + Feature_KeypadModeEnabled + Enables the DECKPAM, DECKPNM sequences to work as intended + 16654 + AlwaysDisabled + + Dev + + + diff --git a/src/host/VtInputThread.cpp b/src/host/VtInputThread.cpp index 495a6e51d8b..b9b2aefb0aa 100644 --- a/src/host/VtInputThread.cpp +++ b/src/host/VtInputThread.cpp @@ -29,7 +29,6 @@ VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe, _hThread{}, _u8State{}, _dwThreadId{ 0 }, - _exitRequested{ false }, _pfnSetLookingForDSR{} { THROW_HR_IF(E_HANDLE, _hFile.get() == INVALID_HANDLE_VALUE); @@ -50,40 +49,6 @@ VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe, _pfnSetLookingForDSR = std::bind(&InputStateMachineEngine::SetLookingForDSR, engineRef, std::placeholders::_1); } -// Method Description: -// - Processes a string of input characters. The characters should be UTF-8 -// encoded, and will get converted to wstring to be processed by the -// input state machine. -// Arguments: -// - u8Str - the UTF-8 string received. -// Return Value: -// - S_OK on success, otherwise an appropriate failure. -[[nodiscard]] HRESULT VtInputThread::_HandleRunInput(const std::string_view u8Str) -{ - // Make sure to call the GLOBAL Lock/Unlock, not the gci's lock/unlock. - // Only the global unlock attempts to dispatch ctrl events. If you use the - // gci's unlock, when you press C-c, it won't be dispatched until the - // next console API call. For something like `powershell sleep 60`, - // that won't happen for 60s - LockConsole(); - auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); - - try - { - std::wstring wstr{}; - auto hr = til::u8u16(u8Str, wstr, _u8State); - // If we hit a parsing error, eat it. It's bad utf-8, we can't do anything with it. - if (FAILED(hr)) - { - return S_FALSE; - } - _pInputStateMachine->ProcessString(wstr); - } - CATCH_RETURN(); - - return S_OK; -} - // Function Description: // - Static function used for initializing an instance's ThreadProc. // Arguments: @@ -100,35 +65,51 @@ DWORD WINAPI VtInputThread::StaticVtInputThreadProc(_In_ LPVOID lpParameter) // Method Description: // - Do a single ReadFile from our pipe, and try and handle it. If handling // failed, throw or log, depending on what the caller wants. -// Arguments: -// - throwOnFail: If true, throw an exception if there was an error processing -// the input received. Otherwise, log the error. // Return Value: -// - -void VtInputThread::DoReadInput(const bool throwOnFail) +// - true if you should continue reading +bool VtInputThread::DoReadInput() { - char buffer[256]; + char buffer[4096]; DWORD dwRead = 0; - auto fSuccess = !!ReadFile(_hFile.get(), buffer, ARRAYSIZE(buffer), &dwRead, nullptr); + const auto ok = ReadFile(_hFile.get(), buffer, ARRAYSIZE(buffer), &dwRead, nullptr); + + // The ReadFile() documentations calls out that: + // > If the lpNumberOfBytesRead parameter is zero when ReadFile returns TRUE on a pipe, the other + // > end of the pipe called the WriteFile function with nNumberOfBytesToWrite set to zero. + // But I was unable to replicate any such behavior. I'm not sure it's true anymore. + // + // However, what the documentations fails to mention is that winsock2 (WSA) handles of the \Device\Afd type are + // transparently compatible with ReadFile() and the WSARecv() documentations contains this important information: + // > For byte streams, zero bytes having been read [..] indicates graceful closure and that no more bytes will ever be read. + // In other words, for pipe HANDLE of unknown type you should consider `lpNumberOfBytesRead == 0` as an exit indicator. + // + // Here, `dwRead == 0` fixes a deadlock when exiting conhost while being in use by WSL whose hypervisor pipes are WSA. + if (!ok || dwRead == 0) + { + return false; + } - if (!fSuccess) + // If we hit a parsing error, eat it. It's bad utf-8, we can't do anything with it. + if (FAILED_LOG(til::u8u16({ buffer, gsl::narrow_cast(dwRead) }, _wstr, _u8State))) { - _exitRequested = true; - return; + return true; } - auto hr = _HandleRunInput({ buffer, gsl::narrow_cast(dwRead) }); - if (FAILED(hr)) + try { - if (throwOnFail) - { - _exitRequested = true; - } - else - { - LOG_IF_FAILED(hr); - } + // Make sure to call the GLOBAL Lock/Unlock, not the gci's lock/unlock. + // Only the global unlock attempts to dispatch ctrl events. If you use the + // gci's unlock, when you press C-c, it won't be dispatched until the + // next console API call. For something like `powershell sleep 60`, + // that won't happen for 60s + LockConsole(); + const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); + + _pInputStateMachine->ProcessString(_wstr); } + CATCH_LOG(); + + return true; } void VtInputThread::SetLookingForDSR(const bool looking) noexcept @@ -145,9 +126,8 @@ void VtInputThread::SetLookingForDSR(const bool looking) noexcept // InputStateMachineEngine. void VtInputThread::_InputThread() { - while (!_exitRequested) + while (DoReadInput()) { - DoReadInput(true); } ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()->CloseInput(); } diff --git a/src/host/VtInputThread.hpp b/src/host/VtInputThread.hpp index 7c075b70025..d058c425bc4 100644 --- a/src/host/VtInputThread.hpp +++ b/src/host/VtInputThread.hpp @@ -25,22 +25,20 @@ namespace Microsoft::Console [[nodiscard]] HRESULT Start(); static DWORD WINAPI StaticVtInputThreadProc(_In_ LPVOID lpParameter); - void DoReadInput(const bool throwOnFail); + bool DoReadInput(); void SetLookingForDSR(const bool looking) noexcept; private: - [[nodiscard]] HRESULT _HandleRunInput(const std::string_view u8Str); void _InputThread(); wil::unique_hfile _hFile; wil::unique_handle _hThread; DWORD _dwThreadId; - bool _exitRequested; - std::function _pfnSetLookingForDSR; std::unique_ptr _pInputStateMachine; til::u8state _u8State; + std::wstring _wstr; }; } diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index dd8b635efd9..f9a6b722767 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -263,12 +263,6 @@ bool VtIo::IsUsingVt() const CATCH_RETURN(); } - // GH#4999 - Send a sequence to the connected terminal to request - // win32-input-mode from them. This will enable the connected terminal to - // send us full INPUT_RECORDs as input. If the terminal doesn't understand - // this sequence, it'll just ignore it. - LOG_IF_FAILED(_pVtRenderEngine->RequestWin32Input()); - // MSFT: 15813316 // If the terminal application wants us to inherit the cursor position, // we're going to emit a VT sequence to ask for the cursor position, then @@ -282,12 +276,17 @@ bool VtIo::IsUsingVt() const if (_lookingForCursorPosition && _pVtRenderEngine && _pVtInputThread) { LOG_IF_FAILED(_pVtRenderEngine->RequestCursor()); - while (_lookingForCursorPosition) + while (_lookingForCursorPosition && _pVtInputThread->DoReadInput()) { - _pVtInputThread->DoReadInput(false); } } + // GH#4999 - Send a sequence to the connected terminal to request + // win32-input-mode from them. This will enable the connected terminal to + // send us full INPUT_RECORDs as input. If the terminal doesn't understand + // this sequence, it'll just ignore it. + LOG_IF_FAILED(_pVtRenderEngine->RequestWin32Input()); + if (_pVtInputThread) { LOG_IF_FAILED(_pVtInputThread->Start()); @@ -468,10 +467,6 @@ void VtIo::SendCloseEvent() void VtIo::CorkRenderer(bool corked) const noexcept { _pVtRenderEngine->Cork(corked); - if (!corked) - { - LOG_IF_FAILED(ServiceLocator::LocateGlobals().pRender->PaintFrame()); - } } #ifdef UNIT_TESTING diff --git a/src/host/consoleInformation.cpp b/src/host/consoleInformation.cpp index 4722cebe2b9..074bd56911f 100644 --- a/src/host/consoleInformation.cpp +++ b/src/host/consoleInformation.cpp @@ -35,6 +35,11 @@ void CONSOLE_INFORMATION::UnlockConsole() noexcept _lock.unlock(); } +til::recursive_ticket_lock_suspension CONSOLE_INFORMATION::SuspendLock() noexcept +{ + return _lock.suspend(); +} + ULONG CONSOLE_INFORMATION::GetCSRecursionCount() const noexcept { return _lock.recursion_depth(); @@ -104,7 +109,7 @@ ULONG CONSOLE_INFORMATION::GetCSRecursionCount() const noexcept return STATUS_SUCCESS; } - RIPMSG1(RIP_WARNING, "Console init failed with status 0x%x", Status); + LOG_NTSTATUS_MSG(Status, "Console init failed"); delete gci.ScreenBuffers; gci.ScreenBuffers = nullptr; diff --git a/src/host/directio.cpp b/src/host/directio.cpp index 1187bd4faae..0a00fa25f82 100644 --- a/src/host/directio.cpp +++ b/src/host/directio.cpp @@ -841,7 +841,6 @@ CATCH_RETURN(); _In_ PCD_CREATE_OBJECT_INFORMATION Information, _In_ PCONSOLE_CREATESCREENBUFFER_MSG a) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::CreateConsoleScreenBuffer); const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); // If any buffer type except the one we support is set, it's invalid. diff --git a/src/host/exe/exemain.cpp b/src/host/exe/exemain.cpp index 7aaa45271d9..2b95ba09164 100644 --- a/src/host/exe/exemain.cpp +++ b/src/host/exe/exemain.cpp @@ -222,6 +222,8 @@ int CALLBACK wWinMain( _In_ PWSTR /*pwszCmdLine*/, _In_ int /*nCmdShow*/) { + TraceLoggingRegister(g_hConhostV2EventTraceProvider); + wil::SetResultLoggingCallback(&Tracing::TraceFailure); Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().hInstance = hInstance; ConsoleCheckDebug(); @@ -276,7 +278,7 @@ int CALLBACK wWinMain( { // Only try to register as a handoff target if we are NOT a part of Windows. #if TIL_FEATURE_RECEIVEINCOMINGHANDOFF_ENABLED - if (args.ShouldRunAsComServer() && Microsoft::Console::Internal::DefaultApp::CheckDefaultAppPolicy()) + if (args.ShouldRunAsComServer()) { try { diff --git a/src/host/ft_host/sources b/src/host/ft_host/sources index beeba4e6753..ba6cf411afa 100644 --- a/src/host/ft_host/sources +++ b/src/host/ft_host/sources @@ -55,4 +55,5 @@ TARGETLIBS = \ DELAYLOAD = \ $(DELAYLOAD) \ + icu.dll; \ ext-ms-win-rtcore-ntuser-dpi-l1.dll; \ diff --git a/src/host/ft_host/sources.dep b/src/host/ft_host/sources.dep index 11b0599b179..bc61c95c6b0 100644 --- a/src/host/ft_host/sources.dep +++ b/src/host/ft_host/sources.dep @@ -1,6 +1,3 @@ BUILD_PASS1_CONSUMES= \ onecore\windows\vcpkg|PASS1 \ -BUILD_PASS2_CONSUMES= \ - onecore\windows\core\console\open\src\tools\nihilist|PASS2 \ - diff --git a/src/host/ft_integrity/IntegrityTest.cpp b/src/host/ft_integrity/IntegrityTest.cpp index c7a8472617c..0ca45b0b5d1 100644 --- a/src/host/ft_integrity/IntegrityTest.cpp +++ b/src/host/ft_integrity/IntegrityTest.cpp @@ -214,14 +214,14 @@ void IntegrityTest::_TestValidationHelper(const bool fIsBlockExpected, GetConsoleScreenBufferInfoEx(GetStdHandle(STD_OUTPUT_HANDLE), &csbiex); - LOG_OUTPUT(L"Buffer Size X:%d Y:%d", csbiex.dwSize.width, csbiex.dwSize.height); + LOG_OUTPUT(L"Buffer Size X:%d Y:%d", csbiex.dwSize.X, csbiex.dwSize.Y); - size_t cch = csbiex.dwSize.width; + size_t cch = csbiex.dwSize.X; wistd::unique_ptr stringData = wil::make_unique_nothrow(cch); THROW_IF_NULL_ALLOC(stringData); COORD coordRead = { 0 }; - for (coordRead.y = 0; coordRead.y < 8; coordRead.y++) + for (coordRead.Y = 0; coordRead.Y < 8; coordRead.Y++) { ZeroMemory(stringData.get(), sizeof(wchar_t) * cch); @@ -237,7 +237,7 @@ void IntegrityTest::_TestValidationHelper(const bool fIsBlockExpected, WEX::Common::String strActual; // At position 0, check the integrity. - if (coordRead.y == 0) + if (coordRead.Y == 0) { strExpected = pwszIntegrityExpected; } @@ -246,11 +246,11 @@ void IntegrityTest::_TestValidationHelper(const bool fIsBlockExpected, // For the rest, check whether the API call worked. if (fIsBlockExpected) { - strExpected = _rgpwszExpectedFail[coordRead.y - 1]; + strExpected = _rgpwszExpectedFail[coordRead.Y - 1]; } else { - strExpected = _rgpwszExpectedSuccess[coordRead.y - 1]; + strExpected = _rgpwszExpectedSuccess[coordRead.Y - 1]; } } stringData[strExpected.GetLength()] = L'\0'; @@ -312,7 +312,7 @@ PCWSTR IntegrityTest::s_GetMyIntegrityLevel() DWORD dwIntegrityLevel = 0; // Get the Integrity level. - wistd::unique_ptr tokenLabel; + wil::unique_tokeninfo_ptr tokenLabel; THROW_IF_FAILED(wil::GetTokenInformationNoThrow(tokenLabel, GetCurrentProcessToken())); dwIntegrityLevel = *GetSidSubAuthority(tokenLabel->Label.Sid, diff --git a/src/host/ft_integrity/sources b/src/host/ft_integrity/sources index a7eddd8bef0..0dcd996d5c3 100644 --- a/src/host/ft_integrity/sources +++ b/src/host/ft_integrity/sources @@ -33,7 +33,6 @@ INCLUDES=\ $(COM_INC_PATH); \ $(ONECOREBASE_INTERNAL_INC_PATH_L)\appmodel\test\common; \ $(ONECOREREDIST_INTERNAL_INC_PATH_L)\TAEF; \ - $(ONECORE_PRIV_SDK_INC_PATH); \ $(MINCORE_INTERNAL_PRIV_SDK_INC_PATH_L); \ TARGETLIBS=\ diff --git a/src/host/ft_integrity/sources.dep b/src/host/ft_integrity/sources.dep deleted file mode 100644 index 83c5a0a70ef..00000000000 --- a/src/host/ft_integrity/sources.dep +++ /dev/null @@ -1,6 +0,0 @@ -PUBLIC_PASS1_CONSUMES= \ - onecore\base\appmodel\test\common\testhelper\winrt\private|PASS1 \ - -BUILD_PASS2_CONSUMES= \ - onecore\base\appmodel\test\common\testhelper\samples\nativecxapp\appx|PASS2 \ - diff --git a/src/host/getset.cpp b/src/host/getset.cpp index eec9f720db8..3d371d4b24f 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -41,7 +41,6 @@ void ApiRoutines::GetConsoleInputModeImpl(InputBuffer& context, ULONG& mode) noe { try { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleMode); const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); diff --git a/src/host/host-common.vcxitems b/src/host/host-common.vcxitems index ee482e31a93..5ae8d7a0f3b 100644 --- a/src/host/host-common.vcxitems +++ b/src/host/host-common.vcxitems @@ -43,10 +43,8 @@ - - @@ -97,10 +95,8 @@ - - diff --git a/src/host/init.cpp b/src/host/init.cpp index eb1e554c905..dc4a0c238f5 100644 --- a/src/host/init.cpp +++ b/src/host/init.cpp @@ -50,7 +50,7 @@ void InitSideBySide() // OpenConsole anyways, nothing happens and we get ERROR_SXS_PROCESS_DEFAULT_ALREADY_SET. if (ERROR_SXS_PROCESS_DEFAULT_ALREADY_SET != error) { - RIPMSG1(RIP_WARNING, "InitSideBySide failed create an activation context. Error: %d\r\n", error); + LOG_WIN32_MSG(error, "InitSideBySide failed create an activation context."); } } } diff --git a/src/host/input.cpp b/src/host/input.cpp index ea13a0c0ef5..fd92154020c 100644 --- a/src/host/input.cpp +++ b/src/host/input.cpp @@ -206,7 +206,7 @@ void HandleMenuEvent(const DWORD wParam) EventsWritten = gci.pInputBuffer->Write(SynthesizeMenuEvent(wParam)); if (EventsWritten != 1) { - RIPMSG0(RIP_WARNING, "PutInputInBuffer: EventsWritten != 1, 1 expected"); + LOG_HR_MSG(E_FAIL, "PutInputInBuffer: EventsWritten != 1, 1 expected"); } } catch (...) @@ -230,7 +230,7 @@ void HandleCtrlEvent(const DWORD EventType) gci.CtrlFlags |= CONSOLE_CTRL_CLOSE_FLAG; break; default: - RIPMSG1(RIP_ERROR, "Invalid EventType: 0x%x", EventType); + LOG_HR_MSG(E_INVALIDARG, "Invalid EventType: 0x%x", EventType); } } diff --git a/src/host/inputBuffer.cpp b/src/host/inputBuffer.cpp index 3a4e556e440..184670ffb1d 100644 --- a/src/host/inputBuffer.cpp +++ b/src/host/inputBuffer.cpp @@ -4,13 +4,13 @@ #include "precomp.h" #include "inputBuffer.hpp" -#include "stream.h" -#include "../types/inc/GlyphWidth.hpp" - #include +#include #include "misc.h" +#include "stream.h" #include "../interactivity/inc/ServiceLocator.hpp" +#include "../types/inc/GlyphWidth.hpp" #define INPUT_BUFFER_DEFAULT_INPUT_MODE (ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT | ENABLE_MOUSE_INPUT) @@ -87,10 +87,10 @@ void InputBuffer::Consume(bool isUnicode, std::wstring_view& source, std::span(s.size()), &buffer[0], sizeof(buffer), nullptr, nullptr); THROW_LAST_ERROR_IF(length <= 0); std::string_view slice{ &buffer[0], gsl::narrow_cast(length) }; @@ -98,10 +98,24 @@ void InputBuffer::Consume(bool isUnicode, std::wstring_view& source, std::span& inEvents) } } +void InputBuffer::WriteString(const std::wstring_view& text) +try +{ + if (text.empty()) + { + return; + } + + const auto initiallyEmptyQueue = _storage.empty(); + + _writeString(text); + + if (initiallyEmptyQueue && !_storage.empty()) + { + ServiceLocator::LocateGlobals().hInputEvent.SetEvent(); + } + + WakeUpReadersWaitingForData(); +} +CATCH_LOG() + // This can be considered a "privileged" variant of Write() which allows FOCUS_EVENTs to generate focus VT sequences. // If we didn't do this, someone could write a FOCUS_EVENT_RECORD with WriteConsoleInput, exit without flushing the // input buffer and the next application will suddenly get a "\x1b[I" sequence in their input. See GH#13238. @@ -814,10 +849,7 @@ void InputBuffer::_HandleTerminalInputCallback(const TerminalInput::StringType& return; } - for (const auto& wch : text) - { - _storage.push_back(SynthesizeKeyEvent(true, 1, 0, 0, wch, 0)); - } + _writeString(text); if (!_vtInputShouldSuppress) { @@ -831,6 +863,25 @@ void InputBuffer::_HandleTerminalInputCallback(const TerminalInput::StringType& } } +void InputBuffer::_writeString(const std::wstring_view& text) +{ + for (const auto& wch : text) + { + if (wch == UNICODE_NULL) + { + // Convert null byte back to input event with proper control state + const auto zeroKey = OneCoreSafeVkKeyScanW(0); + uint32_t ctrlState = 0; + WI_SetFlagIf(ctrlState, SHIFT_PRESSED, WI_IsFlagSet(zeroKey, 0x100)); + WI_SetFlagIf(ctrlState, LEFT_CTRL_PRESSED, WI_IsFlagSet(zeroKey, 0x200)); + WI_SetFlagIf(ctrlState, LEFT_ALT_PRESSED, WI_IsFlagSet(zeroKey, 0x400)); + _storage.push_back(SynthesizeKeyEvent(true, 1, LOBYTE(zeroKey), 0, wch, ctrlState)); + continue; + } + _storage.push_back(SynthesizeKeyEvent(true, 1, 0, 0, wch, 0)); + } +} + TerminalInput& InputBuffer::GetTerminalInput() { return _termInput; diff --git a/src/host/inputBuffer.hpp b/src/host/inputBuffer.hpp index ec7cd75b082..019c6e628bd 100644 --- a/src/host/inputBuffer.hpp +++ b/src/host/inputBuffer.hpp @@ -58,6 +58,7 @@ class InputBuffer final : public ConsoleObjectHeader size_t Prepend(const std::span& inEvents); size_t Write(const INPUT_RECORD& inEvent); size_t Write(const std::span& inEvents); + void WriteString(const std::wstring_view& text); void WriteFocusEvent(bool focused) noexcept; bool WriteMouseEvent(til::point position, unsigned int button, short keyState, short wheelDelta); @@ -96,6 +97,7 @@ class InputBuffer final : public ConsoleObjectHeader void _WriteBuffer(const std::span& inRecords, _Out_ size_t& eventsWritten, _Out_ bool& setWaitEvent); bool _CoalesceEvent(const INPUT_RECORD& inEvent) noexcept; void _HandleTerminalInputCallback(const Microsoft::Console::VirtualTerminal::TerminalInput::StringType& text); + void _writeString(const std::wstring_view& text); #ifdef UNIT_TESTING friend class InputBufferTests; diff --git a/src/host/lib/hostlib.vcxproj.filters b/src/host/lib/hostlib.vcxproj.filters index c108f2d1411..0e8177d4bc4 100644 --- a/src/host/lib/hostlib.vcxproj.filters +++ b/src/host/lib/hostlib.vcxproj.filters @@ -99,9 +99,6 @@ Source Files - - Source Files - Source Files @@ -111,9 +108,6 @@ Source Files - - Source Files - Source Files @@ -266,9 +260,6 @@ Header Files - - Header Files - Header Files @@ -326,5 +317,6 @@ + - + \ No newline at end of file diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index a48e189f00d..6cb84a4ef5b 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -33,24 +33,11 @@ ConhostInternalGetSet::ConhostInternalGetSet(_In_ IIoProvider& io) : // - void ConhostInternalGetSet::ReturnResponse(const std::wstring_view response) { - InputEventQueue inEvents; - - // generate a paired key down and key up event for every - // character to be sent into the console's input buffer - for (const auto& wch : response) - { - // This wasn't from a real keyboard, so we're leaving key/scan codes blank. - auto keyEvent = SynthesizeKeyEvent(true, 1, 0, 0, wch, 0); - inEvents.push_back(keyEvent); - keyEvent.Event.KeyEvent.bKeyDown = false; - inEvents.push_back(keyEvent); - } - // TODO GH#4954 During the input refactor we may want to add a "priority" input list // to make sure that "response" input is spooled directly into the application. // We switched this to an append (vs. a prepend) to fix GH#1637, a bug where two CPR // could collide with each other. - _io.GetActiveInputBuffer()->Write(inEvents); + _io.GetActiveInputBuffer()->WriteString(response); } // Routine Description: @@ -95,7 +82,7 @@ til::rect ConhostInternalGetSet::GetViewport() const void ConhostInternalGetSet::SetViewportPosition(const til::point position) { auto& info = _io.GetActiveOutputBuffer(); - THROW_IF_FAILED(info.SetViewportOrigin(true, position, false)); + THROW_IF_FAILED(info.SetViewportOrigin(true, position, true)); // SetViewportOrigin() only updates the virtual bottom (the bottom coordinate of the area // in the text buffer a VT client writes its output into) when it's moving downwards. // But this function is meant to truly move the viewport no matter what. Otherwise `tput reset` breaks. diff --git a/src/host/precomp.h b/src/host/precomp.h index c7f41e06dfe..e28931ebaf4 100644 --- a/src/host/precomp.h +++ b/src/host/precomp.h @@ -66,7 +66,7 @@ Module Name: TRACELOGGING_DECLARE_PROVIDER(g_hConhostV2EventTraceProvider); #include #include -#include "telemetry.hpp" + #include "tracing.hpp" #ifdef BUILDING_INSIDE_WINIDE diff --git a/src/host/readDataCooked.cpp b/src/host/readDataCooked.cpp index 3644dfe78db..a05bed8a46a 100644 --- a/src/host/readDataCooked.cpp +++ b/src/host/readDataCooked.cpp @@ -436,14 +436,17 @@ void COOKED_READ_DATA::_handleChar(wchar_t wch, const DWORD modifiers) if (_ctrlWakeupMask != 0 && wch < L' ' && (_ctrlWakeupMask & (1 << wch))) { - _flushBuffer(); - // The old implementation (all the way since the 90s) overwrote the character at the current cursor position with the given wch. // But simultaneously it incremented the buffer length, which would have only worked if it was written at the end of the buffer. // Press tab past the "f" in the string "foo" and you'd get "f\to " (a trailing whitespace; the initial contents of the buffer back then). // It's unclear whether the original intention was to write at the end of the buffer at all times or to implement an insert mode. // I went with insert mode. + // + // It is important that we don't actually print that character out though, as it's only for the calling application to see. + // That's why we flush the contents before the insertion and then ensure that the _flushBuffer() call in Read() exits early. + _flushBuffer(); _buffer.Replace(_buffer.GetCursorPosition(), 0, &wch, 1); + _buffer.MarkAsClean(); _controlKeyState = modifiers; _transitionState(State::DoneWithWakeupMask); @@ -842,15 +845,12 @@ void COOKED_READ_DATA::_flushBuffer() // If the contents of _buffer became shorter we'll have to erase the previously printed contents. _erase(eraseDistance); - _offsetCursorPosition(-eraseDistance - distanceAfterCursor); + // Using the *Always() variant ensures that we reset the blinking timer, etc., even if the cursor didn't move. + _offsetCursorPositionAlways(-eraseDistance - distanceAfterCursor); _buffer.MarkAsClean(); _distanceCursor = distanceBeforeCursor; _distanceEnd = distanceEnd; - - const auto pos = _screenInfo.GetTextBuffer().GetCursor().GetPosition(); - _screenInfo.MakeCursorVisible(pos); - std::ignore = _screenInfo.SetCursorPosition(pos, true); } // This is just a small helper to fill the next N cells starting at the current cursor position with whitespace. @@ -935,22 +935,31 @@ ptrdiff_t COOKED_READ_DATA::_writeCharsImpl(const std::wstring_view& text, const const auto wch = *it; if (wch == UNICODE_TAB) { - const auto col = _getColumnAtRelativeCursorPosition(distance + cursorOffset); - const auto remaining = width - col; - distance += std::min(remaining, 8 - (col & 7)); buf[0] = L'\t'; len = 1; } else { // In the interactive mode we replace C0 control characters (0x00-0x1f) with ASCII representations like ^C (= 0x03). - distance += 2; buf[0] = L'^'; buf[1] = gsl::narrow_cast(wch + L'@'); len = 2; } - if (!measureOnly) + if (measureOnly) + { + if (wch == UNICODE_TAB) + { + const auto col = _getColumnAtRelativeCursorPosition(distance + cursorOffset); + const auto remaining = width - col; + distance += std::min(remaining, 8 - (col & 7)); + } + else + { + distance += 2; + } + } + else { distance += _writeCharsUnprocessed({ &buf[0], len }); } @@ -1033,15 +1042,21 @@ til::point COOKED_READ_DATA::_offsetPosition(til::point pos, ptrdiff_t distance) }; } -// This moves the cursor `distance`-many cells back up in the buffer. -// It's intended to be used in combination with _writeChars. +// See _offsetCursorPositionAlways(). This wrapper is just here to avoid doing +// expensive cursor movements when there's nothing to move. A no-op wrapper. void COOKED_READ_DATA::_offsetCursorPosition(ptrdiff_t distance) const { - if (distance == 0) + if (distance != 0) { - return; + _offsetCursorPositionAlways(distance); } +} +// This moves the cursor `distance`-many cells around in the buffer. +// It's intended to be used in combination with _writeChars. +// Usually you should use _offsetCursorPosition() to no-op distance==0. +void COOKED_READ_DATA::_offsetCursorPositionAlways(ptrdiff_t distance) const +{ const auto& textBuffer = _screenInfo.GetTextBuffer(); const auto& cursor = textBuffer.GetCursor(); const auto pos = _offsetPosition(cursor.GetPosition(), distance); diff --git a/src/host/readDataCooked.hpp b/src/host/readDataCooked.hpp index 9502eaa5c2d..035c3c96679 100644 --- a/src/host/readDataCooked.hpp +++ b/src/host/readDataCooked.hpp @@ -160,6 +160,7 @@ class COOKED_READ_DATA final : public ReadData ptrdiff_t _writeCharsUnprocessed(const std::wstring_view& text) const; til::point _offsetPosition(til::point pos, ptrdiff_t distance) const; void _offsetCursorPosition(ptrdiff_t distance) const; + void _offsetCursorPositionAlways(ptrdiff_t distance) const; til::CoordType _getColumnAtRelativeCursorPosition(ptrdiff_t distance) const; void _popupPush(PopupKind kind); diff --git a/src/host/renderData.cpp b/src/host/renderData.cpp index d327c0b9d61..d9b094626ba 100644 --- a/src/host/renderData.cpp +++ b/src/host/renderData.cpp @@ -79,6 +79,16 @@ std::vector RenderData::GetSelectionRects() noexcept return result; } +// Method Description: +// - Retrieves one rectangle per line describing the area of the viewport +// that should be highlighted in some way to represent a user-interactive selection +// Return Value: +// - Vector of Viewports describing the area selected +std::vector RenderData::GetSearchSelectionRects() noexcept +{ + return {}; +} + // Method Description: // - Lock the console for reading the contents of the buffer. Ensures that the // contents of the console won't be changed in the middle of a paint @@ -87,14 +97,16 @@ std::vector RenderData::GetSelectionRects() noexcept // they're done with any querying they need to do. void RenderData::LockConsole() noexcept { - ::LockConsole(); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + gci.LockConsole(); } // Method Description: // - Unlocks the console after a call to RenderData::LockConsole. void RenderData::UnlockConsole() noexcept { - ::UnlockConsole(); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + gci.UnlockConsole(); } // Method Description: @@ -369,6 +381,10 @@ void RenderData::SelectNewRegion(const til::point coordStart, const til::point c Selection::Instance().SelectNewRegion(coordStart, coordEnd); } +void RenderData::SelectSearchRegions(std::vector source) +{ +} + // Routine Description: // - Gets the current selection anchor position // Arguments: diff --git a/src/host/renderData.hpp b/src/host/renderData.hpp index 5e649353cd7..52056d7a6f5 100644 --- a/src/host/renderData.hpp +++ b/src/host/renderData.hpp @@ -26,6 +26,7 @@ class RenderData final : const FontInfo& GetFontInfo() const noexcept override; std::vector GetSelectionRects() noexcept override; + std::vector GetSearchSelectionRects() noexcept override; void LockConsole() noexcept override; void UnlockConsole() noexcept override; @@ -54,6 +55,7 @@ class RenderData final : const bool IsBlockSelection() const noexcept override; void ClearSelection() override; void SelectNewRegion(const til::point coordStart, const til::point coordEnd) override; + void SelectSearchRegions(std::vector source) override; const til::point GetSelectionAnchor() const noexcept override; const til::point GetSelectionEnd() const noexcept override; const bool IsUiaDataInitialized() const noexcept override { return true; } diff --git a/src/host/res.rc b/src/host/res.rc index b35e9c87929..6f566147e33 100644 --- a/src/host/res.rc +++ b/src/host/res.rc @@ -61,9 +61,6 @@ BEGIN ID_CONSOLE_FMT_WINDOWTITLE, "%s%s" -/* WIP Audit destination name */ - ID_CONSOLE_WIP_DESTINATIONNAME, "console application" - /* Menu items that replace the standard ones. These don't have the accelerators */ SC_CLOSE, "&Close" diff --git a/src/host/resource.h b/src/host/resource.h index f81be52a827..9ea8cf549b6 100644 --- a/src/host/resource.h +++ b/src/host/resource.h @@ -28,7 +28,6 @@ Author(s): #define ID_CONSOLE_MSGMARKMODE 0x100C #define ID_CONSOLE_MSGSCROLLMODE 0x100D #define ID_CONSOLE_FMT_WINDOWTITLE 0x100E -#define ID_CONSOLE_WIP_DESTINATIONNAME 0x100F // Menu Item strings #define ID_CONSOLE_COPY 0xFFF0 diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index d02bbaf3d67..2701b4e2072 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -34,7 +34,6 @@ SCREEN_INFORMATION::SCREEN_INFORMATION( const TextAttribute popupAttributes, const FontInfo fontInfo) : OutputMode{ ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT }, - ResizingWindow{ 0 }, WheelDelta{ 0 }, HWheelDelta{ 0 }, _textBuffer{ nullptr }, @@ -641,64 +640,35 @@ VOID SCREEN_INFORMATION::UpdateScrollBars() return; } - if (gci.Flags & CONSOLE_UPDATING_SCROLL_BARS) + if (gci.Flags & CONSOLE_UPDATING_SCROLL_BARS || ServiceLocator::LocateConsoleWindow() == nullptr) { return; } gci.Flags |= CONSOLE_UPDATING_SCROLL_BARS; - - if (ServiceLocator::LocateConsoleWindow() != nullptr) - { - ServiceLocator::LocateConsoleWindow()->PostUpdateScrollBars(); - } + LOG_IF_WIN32_BOOL_FALSE(ServiceLocator::LocateConsoleWindow()->PostUpdateScrollBars()); } -VOID SCREEN_INFORMATION::InternalUpdateScrollBars() +SCREEN_INFORMATION::ScrollBarState SCREEN_INFORMATION::FetchScrollBarState() { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto pWindow = ServiceLocator::LocateConsoleWindow(); - WI_ClearFlag(gci.Flags, CONSOLE_UPDATING_SCROLL_BARS); - if (!IsActiveScreenBuffer()) - { - return; - } - - ResizingWindow++; - - if (pWindow != nullptr) - { - const auto buffer = GetBufferSize(); - - // If this is the main buffer, make sure we enable both of the scroll bars. - // The alt buffer likely disabled the scroll bars, this is the only - // way to re-enable it. - if (!_IsAltBuffer()) - { - pWindow->EnableBothScrollBars(); - } - - pWindow->UpdateScrollBar(true, - _IsAltBuffer(), - _viewport.Height(), - gci.IsTerminalScrolling() ? _virtualBottom : buffer.BottomInclusive(), - _viewport.Top()); - pWindow->UpdateScrollBar(false, - _IsAltBuffer(), - _viewport.Width(), - buffer.RightInclusive(), - _viewport.Left()); - } - // Fire off an event to let accessibility apps know the layout has changed. if (_pAccessibilityNotifier) { _pAccessibilityNotifier->NotifyConsoleLayoutEvent(); } - ResizingWindow--; + const auto buffer = GetBufferSize(); + const auto isAltBuffer = _IsAltBuffer(); + const auto maxSizeVer = gci.IsTerminalScrolling() ? _virtualBottom : buffer.BottomInclusive(); + const auto maxSizeHor = buffer.RightInclusive(); + return ScrollBarState{ + .maxSize = { maxSizeHor, maxSizeVer }, + .viewport = _viewport.ToExclusive(), + .isAltBuffer = isAltBuffer, + }; } // Routine Description: @@ -1391,7 +1361,7 @@ try { if ((USHORT)coordNewScreenSize.width >= SHORT_MAX || (USHORT)coordNewScreenSize.height >= SHORT_MAX) { - RIPMSG2(RIP_WARNING, "Invalid screen buffer size (0x%x, 0x%x)", coordNewScreenSize.width, coordNewScreenSize.height); + LOG_HR_MSG(E_INVALIDARG, "Invalid screen buffer size (0x%x, 0x%x)", coordNewScreenSize.width, coordNewScreenSize.height); return STATUS_INVALID_PARAMETER; } diff --git a/src/host/screenInfo.hpp b/src/host/screenInfo.hpp index 09fca19809b..c0351050566 100644 --- a/src/host/screenInfo.hpp +++ b/src/host/screenInfo.hpp @@ -99,8 +99,14 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console bool HasAccessibilityEventing() const noexcept; void NotifyAccessibilityEventing(const til::CoordType sStartX, const til::CoordType sStartY, const til::CoordType sEndX, const til::CoordType sEndY); + struct ScrollBarState + { + til::size maxSize; + til::rect viewport; + bool isAltBuffer = false; + }; void UpdateScrollBars(); - void InternalUpdateScrollBars(); + ScrollBarState FetchScrollBarState(); bool IsMaximizedBoth() const; bool IsMaximizedX() const; @@ -158,7 +164,6 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console bool CursorIsDoubleWidth() const; DWORD OutputMode; - WORD ResizingWindow; // > 0 if we should ignore WM_SIZE messages short WheelDelta; short HWheelDelta; diff --git a/src/host/selection.hpp b/src/host/selection.hpp index 5edb37cd4d8..e7e0dfcb4b4 100644 --- a/src/host/selection.hpp +++ b/src/host/selection.hpp @@ -127,6 +127,7 @@ class Selection DWORD GetPublicSelectionFlags() const noexcept; til::point GetSelectionAnchor() const noexcept; + std::pair GetSelectionAnchors() const noexcept; til::inclusive_rect GetSelectionRectangle() const noexcept; void SetLineSelection(const bool fLineSelectionOn); diff --git a/src/host/selectionInput.cpp b/src/host/selectionInput.cpp index f00bbb4b847..ab08d3f21c3 100644 --- a/src/host/selectionInput.cpp +++ b/src/host/selectionInput.cpp @@ -37,8 +37,6 @@ Selection::KeySelectionEventResult Selection::HandleKeySelectionEvent(const INPU // C-c, C-Ins. C-S-c Is also handled by this case. ((ctrlPressed) && (wVirtualKeyCode == 'C' || wVirtualKeyCode == VK_INSERT))) { - Telemetry::Instance().SetKeyboardTextEditingUsed(); - // copy selection return Selection::KeySelectionEventResult::CopyToClipboard; } @@ -291,8 +289,6 @@ bool Selection::HandleKeyboardLineSelectionEvent(const INPUT_KEY_INFO* const pIn return false; } - Telemetry::Instance().SetKeyboardTextSelectionUsed(); - // if we're not currently selecting anything, start a new mouse selection if (!IsInSelectingState()) { @@ -704,8 +700,6 @@ bool Selection::_HandleColorSelection(const INPUT_KEY_INFO* const pInputKeyInfo) // Clear the selection and call the search / mark function. ClearSelection(); - Telemetry::Instance().LogColorSelectionUsed(); - const auto& textBuffer = gci.renderData.GetTextBuffer(); const auto hits = textBuffer.SearchText(str, true); for (const auto& s : hits) diff --git a/src/host/selectionState.cpp b/src/host/selectionState.cpp index a630b490e14..a7c8a4b553d 100644 --- a/src/host/selectionState.cpp +++ b/src/host/selectionState.cpp @@ -197,6 +197,38 @@ til::point Selection::GetSelectionAnchor() const noexcept return _coordSelectionAnchor; } +// Routine Description: +// - Gets the current selection begin and end (inclusive) anchor positions. The +// first anchor is at the top left, and the second is at the bottom right +// corner of the selection area. +// Return Value: +// - The current selection anchors +std::pair Selection::GetSelectionAnchors() const noexcept +{ + if (!_fSelectionVisible) + { + // return anchors that represent an empty selection + return { { 0, 0 }, { -1, -1 } }; + } + + auto startSelectionAnchor = _coordSelectionAnchor; + + // _coordSelectionAnchor is at one of the corners of _srSelectionRects + // endSelectionAnchor is at the exact opposite corner + til::point endSelectionAnchor; + endSelectionAnchor.x = (_coordSelectionAnchor.x == _srSelectionRect.left) ? _srSelectionRect.right : _srSelectionRect.left; + endSelectionAnchor.y = (_coordSelectionAnchor.y == _srSelectionRect.top) ? _srSelectionRect.bottom : _srSelectionRect.top; + + if (startSelectionAnchor > endSelectionAnchor) + { + return { endSelectionAnchor, startSelectionAnchor }; + } + else + { + return { startSelectionAnchor, endSelectionAnchor }; + } +} + // Routine Description: // - Gets the current selection rectangle // Arguments: diff --git a/src/host/server.h b/src/host/server.h index 3332cc6b383..cfa1ba14dc6 100644 --- a/src/host/server.h +++ b/src/host/server.h @@ -102,6 +102,7 @@ class CONSOLE_INFORMATION : void LockConsole() noexcept; void UnlockConsole() noexcept; + til::recursive_ticket_lock_suspension SuspendLock() noexcept; bool IsConsoleLocked() const noexcept; ULONG GetCSRecursionCount() const noexcept; diff --git a/src/host/sources.inc b/src/host/sources.inc index bf3b16bcae9..f17c5191ac8 100644 --- a/src/host/sources.inc +++ b/src/host/sources.inc @@ -72,7 +72,6 @@ SOURCES = \ ..\_output.cpp \ ..\_stream.cpp \ ..\utils.cpp \ - ..\telemetry.cpp \ ..\tracing.cpp \ ..\registry.cpp \ ..\settings.cpp \ @@ -84,7 +83,6 @@ SOURCES = \ ..\writeData.cpp \ ..\renderData.cpp \ ..\renderFontDefaults.cpp \ - ..\utf8ToWideCharParser.cpp \ ..\conareainfo.cpp \ ..\conimeinfo.cpp \ ..\ConsoleArguments.cpp \ @@ -139,7 +137,6 @@ TARGETLIBS = \ $(ONECOREUAP_EXTERNAL_SDK_LIB_PATH)\d3d11.lib \ $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\api-ms-win-mm-playsound-l1.lib \ $(ONECORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-dwmapi-ext-l1.lib \ - $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-edputil-policy-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-create-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-draw-l1.lib \ @@ -198,6 +195,7 @@ DELAYLOAD = \ DXGI.dll; \ OLEAUT32.dll; \ PROPSYS.dll; \ + icu.dll; \ api-ms-win-core-com-l1.dll; \ api-ms-win-core-registry-l2.dll; \ api-ms-win-mm-playsound-l1.dll; \ @@ -206,7 +204,6 @@ DELAYLOAD = \ api-ms-win-shell-dataobject-l1.dll; \ api-ms-win-shell-namespace-l1.dll; \ ext-ms-win-dwmapi-ext-l1.dll; \ - ext-ms-win-edputil-policy-l1.dll; \ ext-ms-win-usp10-l1.dll; \ ext-ms-win-gdi-dc-l1.dll; \ ext-ms-win-gdi-dc-create-l1.dll; \ diff --git a/src/host/srvinit.cpp b/src/host/srvinit.cpp index 284f4483820..3fda2556ca8 100644 --- a/src/host/srvinit.cpp +++ b/src/host/srvinit.cpp @@ -64,7 +64,7 @@ try // Check if this conhost is allowed to delegate its activities to another. // If so, look up the registered default console handler. - if (Globals.delegationPair.IsUndecided() && Microsoft::Console::Internal::DefaultApp::CheckDefaultAppPolicy()) + if (Globals.delegationPair.IsUndecided()) { Globals.delegationPair = DelegationConfig::s_GetDelegationPair(); @@ -82,7 +82,7 @@ try // If we looked up the registered defterm pair, and it was left as the default (missing or {0}), // AND velocity is enabled for DxD, then we switch the delegation pair to Terminal and // mark that we should check that class for the marker interface later. - if (Globals.delegationPair.IsDefault() && Microsoft::Console::Internal::DefaultApp::CheckShouldTerminalBeDefault()) + if (Globals.delegationPair.IsDefault()) { Globals.delegationPair = DelegationConfig::TerminalDelegationPair; Globals.defaultTerminalMarkerCheckRequired = true; @@ -428,11 +428,6 @@ HRESULT ConsoleCreateIoThread(_In_ HANDLE Server, [[maybe_unused]] PCONSOLE_API_MSG connectMessage) try { - // Create a telemetry instance here - this singleton is responsible for - // setting up the g_hConhostV2EventTraceProvider, which is otherwise not - // initialized in the defterm handoff at this point. - (void)Telemetry::Instance(); - #if !TIL_FEATURE_RECEIVEINCOMINGHANDOFF_ENABLED TraceLoggingWrite(g_hConhostV2EventTraceProvider, "SrvInit_ReceiveHandoff_Disabled", @@ -865,8 +860,6 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand, [[nodiscard]] NTSTATUS ConsoleAllocateConsole(PCONSOLE_API_CONNECTINFO p) { // AllocConsole is outside our codebase, but we should be able to mostly track the call here. - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::AllocConsole); - auto& g = ServiceLocator::LocateGlobals(); auto& gci = g.getConsoleInformation(); @@ -1050,7 +1043,7 @@ DWORD WINAPI ConsoleIoThread(LPVOID lpParameter) // This will not return. Terminate immediately when disconnected. ServiceLocator::RundownAndExit(STATUS_SUCCESS); } - RIPMSG1(RIP_WARNING, "DeviceIoControl failed with Result 0x%x", hr); + LOG_HR_MSG(hr, "DeviceIoControl failed"); ReplyMsg = nullptr; continue; } diff --git a/src/host/telemetry.cpp b/src/host/telemetry.cpp deleted file mode 100644 index 21cff15e815..00000000000 --- a/src/host/telemetry.cpp +++ /dev/null @@ -1,520 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include -#include "Shlwapi.h" -#include "telemetry.hpp" -#include - -#include "history.h" - -#include "../interactivity/inc/ServiceLocator.hpp" - -TRACELOGGING_DEFINE_PROVIDER(g_hConhostV2EventTraceProvider, - "Microsoft.Windows.Console.Host", - // {fe1ff234-1f09-50a8-d38d-c44fab43e818} - (0xfe1ff234, 0x1f09, 0x50a8, 0xd3, 0x8d, 0xc4, 0x4f, 0xab, 0x43, 0xe8, 0x18), - TraceLoggingOptionMicrosoftTelemetry()); -#pragma warning(push) -// Disable 4351 so we can initialize the arrays to 0 without a warning. -#pragma warning(disable : 4351) -Telemetry::Telemetry() : - _uiColorSelectionUsed(0), - _tStartedAt(0), - _wchProcessFileNames(), - // Start at position 1, since the first 2 bytes contain the number of strings. - _iProcessFileNamesNext(1), - _iProcessConnectedCurrently(SIZE_MAX), - _rgiProcessFileNameIndex(), - _rguiProcessFileNamesCount(), - _rgiAlphabeticalIndex(), - _rguiTimesApiUsed(), - _rguiTimesApiUsedAnsi(), - _uiNumberProcessFileNames(0), - _fBashUsed(false), - _fKeyboardTextEditingUsed(false), - _fKeyboardTextSelectionUsed(false), - _fUserInteractiveForTelemetry(false), - _fCtrlPgUpPgDnUsed(false), - _uiCtrlShiftCProcUsed(0), - _uiCtrlShiftCRawUsed(0), - _uiCtrlShiftVProcUsed(0), - _uiCtrlShiftVRawUsed(0), - _uiQuickEditCopyProcUsed(0), - _uiQuickEditCopyRawUsed(0), - _uiQuickEditPasteProcUsed(0), - _uiQuickEditPasteRawUsed(0) -{ - time(&_tStartedAt); - TraceLoggingRegister(g_hConhostV2EventTraceProvider); - TraceLoggingWriteStart(_activity, "ActivityStart"); - // initialize wil tracelogging - wil::SetResultLoggingCallback(&Tracing::TraceFailure); -} -#pragma warning(pop) - -Telemetry::~Telemetry() -{ - TraceLoggingWriteStop(_activity, "ActivityStop"); - TraceLoggingUnregister(g_hConhostV2EventTraceProvider); -} - -void Telemetry::SetUserInteractive() -{ - _fUserInteractiveForTelemetry = true; -} - -void Telemetry::SetCtrlPgUpPgDnUsed() -{ - _fCtrlPgUpPgDnUsed = true; - SetUserInteractive(); -} - -void Telemetry::LogCtrlShiftCProcUsed() -{ - _uiCtrlShiftCProcUsed++; - SetUserInteractive(); -} - -void Telemetry::LogCtrlShiftCRawUsed() -{ - _uiCtrlShiftCRawUsed++; - SetUserInteractive(); -} - -void Telemetry::LogCtrlShiftVProcUsed() -{ - _uiCtrlShiftVProcUsed++; - SetUserInteractive(); -} - -void Telemetry::LogCtrlShiftVRawUsed() -{ - _uiCtrlShiftVRawUsed++; - SetUserInteractive(); -} - -void Telemetry::LogQuickEditCopyProcUsed() -{ - _uiQuickEditCopyProcUsed++; - SetUserInteractive(); -} - -void Telemetry::LogQuickEditCopyRawUsed() -{ - _uiQuickEditCopyRawUsed++; - SetUserInteractive(); -} - -void Telemetry::LogQuickEditPasteProcUsed() -{ - _uiQuickEditPasteProcUsed++; - SetUserInteractive(); -} - -void Telemetry::LogQuickEditPasteRawUsed() -{ - _uiQuickEditPasteRawUsed++; - SetUserInteractive(); -} - -// Log usage of the Color Selection option. -void Telemetry::LogColorSelectionUsed() -{ - _uiColorSelectionUsed++; - SetUserInteractive(); -} - -void Telemetry::SetWindowSizeChanged() -{ - SetUserInteractive(); -} - -void Telemetry::SetContextMenuUsed() -{ - SetUserInteractive(); -} - -void Telemetry::SetKeyboardTextSelectionUsed() -{ - _fKeyboardTextSelectionUsed = true; - SetUserInteractive(); -} - -void Telemetry::SetKeyboardTextEditingUsed() -{ - _fKeyboardTextEditingUsed = true; - SetUserInteractive(); -} - -// Log an API call was used. -void Telemetry::LogApiCall(const ApiCall api, const BOOLEAN fUnicode) -{ - // Initially we thought about passing over a string (ex. "XYZ") and use a dictionary data type to hold the counts. - // However we would have to search through the dictionary every time we called this method, so we decided - // to use an array which has very quick access times. - // The downside is we have to create an enum type, and then convert them to strings when we finally - // send out the telemetry, but the upside is we should have very good performance. - if (fUnicode) - { - _rguiTimesApiUsed[api]++; - } - else - { - _rguiTimesApiUsedAnsi[api]++; - } -} - -// Log an API call was used. -void Telemetry::LogApiCall(const ApiCall api) -{ - _rguiTimesApiUsed[api]++; -} - -// Tries to find the process name amongst our previous process names by doing a binary search. -// The main difference between this and the standard bsearch library call, is that if this -// can't find the string, it returns the position the new string should be inserted at. This saves -// us from having an additional search through the array, and improves performance. -bool Telemetry::FindProcessName(const WCHAR* pszProcessName, _Out_ size_t* iPosition) const -{ - auto iMin = 0; - auto iMid = 0; - auto iMax = _uiNumberProcessFileNames - 1; - auto result = 0; - - while (iMin <= iMax) - { - iMid = (iMax + iMin) / 2; - // Use a case-insensitive comparison. We do support running Linux binaries now, but we haven't seen them connect - // as processes, and even if they did, we don't care about the difference in running emacs vs. Emacs. - result = _wcsnicmp(pszProcessName, _wchProcessFileNames + _rgiProcessFileNameIndex[_rgiAlphabeticalIndex[iMid]], MAX_PATH); - if (result < 0) - { - iMax = iMid - 1; - } - else if (result > 0) - { - iMin = iMid + 1; - } - else - { - // Found the string. - *iPosition = iMid; - return true; - } - } - - // Let them know which position to insert the string at. - *iPosition = (result > 0) ? iMid + 1 : iMid; - return false; -} - -// Log a process name and number of times it has connected to the console in preparation to send through telemetry. -// We were considering sending out a log of telemetry when each process connects, but then the telemetry can get -// complicated and spammy, especially since command line utilities like help.exe and where.exe are considered processes. -// Don't send telemetry for every time a process connects, as this will help reduce the load on our servers. -// Just save the name and count, and send the telemetry before the console exits. -void Telemetry::LogProcessConnected(const HANDLE hProcess) -{ - // This is a bit of processing, so don't do it for the 95% of machines that aren't being sampled. - if (TraceLoggingProviderEnabled(g_hConhostV2EventTraceProvider, 0, MICROSOFT_KEYWORD_MEASURES)) - { - // Don't initialize wszFilePathAndName, QueryFullProcessImageName does that for us. Use QueryFullProcessImageName instead of - // GetProcessImageFileName because we need the path to begin with a drive letter and not a device name. - WCHAR wszFilePathAndName[MAX_PATH]; - DWORD dwSize = ARRAYSIZE(wszFilePathAndName); - if (QueryFullProcessImageName(hProcess, 0, wszFilePathAndName, &dwSize)) - { - // Stripping out the path also helps with PII issues in case they launched the program - // from a path containing their username. - auto pwszFileName = PathFindFileName(wszFilePathAndName); - - size_t iFileName; - if (FindProcessName(pwszFileName, &iFileName)) - { - // We already logged this process name, so just increment the count. - _iProcessConnectedCurrently = _rgiAlphabeticalIndex[iFileName]; - _rguiProcessFileNamesCount[_iProcessConnectedCurrently]++; - } - else if ((_uiNumberProcessFileNames < ARRAYSIZE(_rguiProcessFileNamesCount)) && - (_iProcessFileNamesNext < ARRAYSIZE(_wchProcessFileNames) - 10)) - { - // Check if the MS released bash was used. MS bash is installed under windows\system32, and it's possible somebody else - // could be installing their bash into that directory, but not likely. If the user first runs a non-MS bash, - // and then runs MS bash, we won't detect the MS bash as running, but it's an acceptable compromise. - if (!_fBashUsed && !_wcsnicmp(c_pwszBashExeName, pwszFileName, MAX_PATH)) - { - // We could have gotten the system directory once when this class starts, but we'd have to hold the memory for it - // plus we're not sure we'd ever need it, so just get it when we know we're running bash.exe. - WCHAR wszSystemDirectory[MAX_PATH] = L""; - if (GetSystemDirectory(wszSystemDirectory, ARRAYSIZE(wszSystemDirectory))) - { - _fBashUsed = (PathIsSameRoot(wszFilePathAndName, wszSystemDirectory) == TRUE); - } - } - - // In order to send out a dynamic array of strings through telemetry, we have to pack the strings into a single WCHAR array. - // There currently aren't any helper functions for this, and we have to pack it manually. - // To understand the format of the single string, consult the documentation in the traceloggingprovider.h file. - if (SUCCEEDED(StringCchCopyW(_wchProcessFileNames + _iProcessFileNamesNext, ARRAYSIZE(_wchProcessFileNames) - _iProcessFileNamesNext - 1, pwszFileName))) - { - // As each FileName comes in, it's appended to the end. However to improve searching speed, we have an array of indexes - // that is alphabetically sorted. We could call qsort, but that would be a waste in performance since we're just adding one string - // at a time and we always keep the array sorted, so just shift everything over one. - for (size_t n = _uiNumberProcessFileNames; n > iFileName; n--) - { - _rgiAlphabeticalIndex[n] = _rgiAlphabeticalIndex[n - 1]; - } - - // Now point to the string, and set the count to 1. - _rgiAlphabeticalIndex[iFileName] = _uiNumberProcessFileNames; - _rgiProcessFileNameIndex[_uiNumberProcessFileNames] = _iProcessFileNamesNext; - _rguiProcessFileNamesCount[_uiNumberProcessFileNames] = 1; - _iProcessFileNamesNext += wcslen(pwszFileName) + 1; - _iProcessConnectedCurrently = _uiNumberProcessFileNames++; - - // Packed arrays start with a UINT16 value indicating the number of elements in the array. - auto pbFileNames = reinterpret_cast(_wchProcessFileNames); - pbFileNames[0] = (BYTE)_uiNumberProcessFileNames; - pbFileNames[1] = (BYTE)(_uiNumberProcessFileNames >> 8); - } - } - } - } -} - -// This Function sends final Trace log before session closes. -// We're primarily sending this telemetry once at the end, and only when the user interacted with the console -// so we don't overwhelm our servers by sending a constant stream of telemetry while the console is being used. -void Telemetry::WriteFinalTraceLog() -{ - const auto& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto& renderSettings = gci.GetRenderSettings(); - // This is a bit of processing, so don't do it for the 95% of machines that aren't being sampled. - if (TraceLoggingProviderEnabled(g_hConhostV2EventTraceProvider, 0, MICROSOFT_KEYWORD_MEASURES)) - { - if (_fUserInteractiveForTelemetry) - { - // Send this back using "measures" since we want a good sampling of our entire userbase. - time_t tEndedAt; - time(&tEndedAt); - // clang-format off -#pragma prefast(suppress: __WARNING_NONCONST_LOCAL, "Activity can't be const, since it's set to a random value on startup.") - // clang-format on - TraceLoggingWriteTagged(_activity, - "SessionEnding", - TraceLoggingBool(_fBashUsed, "BashUsed"), - TraceLoggingBool(_fCtrlPgUpPgDnUsed, "CtrlPgUpPgDnUsed"), - TraceLoggingBool(_fKeyboardTextEditingUsed, "KeyboardTextEditingUsed"), - TraceLoggingBool(_fKeyboardTextSelectionUsed, "KeyboardTextSelectionUsed"), - TraceLoggingUInt32(_uiCtrlShiftCProcUsed, "CtrlShiftCProcUsed"), - TraceLoggingUInt32(_uiCtrlShiftCRawUsed, "CtrlShiftCRawUsed"), - TraceLoggingUInt32(_uiCtrlShiftVProcUsed, "CtrlShiftVProcUsed"), - TraceLoggingUInt32(_uiCtrlShiftVRawUsed, "CtrlShiftVRawUsed"), - TraceLoggingUInt32(_uiQuickEditCopyProcUsed, "QuickEditCopyProcUsed"), - TraceLoggingUInt32(_uiQuickEditCopyRawUsed, "QuickEditCopyRawUsed"), - TraceLoggingUInt32(_uiQuickEditPasteProcUsed, "QuickEditPasteProcUsed"), - TraceLoggingUInt32(_uiQuickEditPasteRawUsed, "QuickEditPasteRawUsed"), - TraceLoggingBool(gci.GetLinkTitle().length() == 0, "LaunchedFromShortcut"), - // Normally we would send out a single array containing the name and count, - // but that's difficult to do with our telemetry system, so send out two separate arrays. - // Casting to UINT should be fine, since our array size is only 2K. - TraceLoggingPackedField(_wchProcessFileNames, static_cast(sizeof(WCHAR) * _iProcessFileNamesNext), TlgInUNICODESTRING | TlgInVcount, "ProcessesConnected"), - TraceLoggingUInt32Array(_rguiProcessFileNamesCount, _uiNumberProcessFileNames, "ProcessesConnectedCount"), - // Send back both starting and ending times separately instead just usage time (ending - starting). - // This can help us determine if they were using multiple consoles at the same time. - TraceLoggingInt32(static_cast(_tStartedAt), "StartedUsingAtSeconds"), - TraceLoggingInt32(static_cast(tEndedAt), "EndedUsingAtSeconds"), - TraceLoggingUInt32(_uiColorSelectionUsed, "ColorSelectionUsed"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - - // Always send this back. We could only send this back when they click "OK" in the settings dialog, but sending it - // back every time should give us a good idea of their current, final settings, and not just only when they change a setting. - // clang-format off -#pragma prefast(suppress: __WARNING_NONCONST_LOCAL, "Activity can't be const, since it's set to a random value on startup.") - // clang-format on - TraceLoggingWriteTagged(_activity, - "Settings", - TraceLoggingBool(gci.GetAutoPosition(), "AutoPosition"), - TraceLoggingBool(gci.GetHistoryNoDup(), "HistoryNoDuplicates"), - TraceLoggingBool(gci.GetInsertMode(), "InsertMode"), - TraceLoggingBool(gci.GetLineSelection(), "LineSelection"), - TraceLoggingBool(gci.GetQuickEdit(), "QuickEdit"), - TraceLoggingValue(gci.GetWindowAlpha(), "WindowAlpha"), - TraceLoggingBool(gci.GetWrapText(), "WrapText"), - TraceLoggingUInt32Array((UINT32 const*)renderSettings.GetColorTable().data(), 16, "ColorTable"), - TraceLoggingValue(gci.CP, "CodePageInput"), - TraceLoggingValue(gci.OutputCP, "CodePageOutput"), - TraceLoggingValue(gci.GetFontSize().width, "FontSizeX"), - TraceLoggingValue(gci.GetFontSize().height, "FontSizeY"), - TraceLoggingValue(gci.GetHotKey(), "HotKey"), - TraceLoggingValue(gci.GetScreenBufferSize().width, "ScreenBufferSizeX"), - TraceLoggingValue(gci.GetScreenBufferSize().height, "ScreenBufferSizeY"), - TraceLoggingValue(gci.GetStartupFlags(), "StartupFlags"), - TraceLoggingValue(gci.GetDefaultVirtTermLevel(), "VirtualTerminalLevel"), - TraceLoggingValue(gci.GetWindowSize().width, "WindowSizeX"), - TraceLoggingValue(gci.GetWindowSize().height, "WindowSizeY"), - TraceLoggingValue(gci.GetWindowOrigin().width, "WindowOriginX"), - TraceLoggingValue(gci.GetWindowOrigin().height, "WindowOriginY"), - TraceLoggingValue(gci.GetFaceName(), "FontName"), - TraceLoggingBool(gci.IsAltF4CloseAllowed(), "AllowAltF4Close"), - TraceLoggingBool(gci.GetCtrlKeyShortcutsDisabled(), "ControlKeyShortcutsDisabled"), - TraceLoggingBool(gci.GetEnableColorSelection(), "EnabledColorSelection"), - TraceLoggingBool(gci.GetFilterOnPaste(), "FilterOnPaste"), - TraceLoggingBool(gci.GetTrimLeadingZeros(), "TrimLeadingZeros"), - TraceLoggingValue(gci.GetLaunchFaceName().data(), "LaunchFontName"), - TraceLoggingValue(CommandHistory::s_CountOfHistories(), "CommandHistoriesNumber"), - TraceLoggingValue(gci.GetCodePage(), "CodePage"), - TraceLoggingValue(gci.GetCursorSize(), "CursorSize"), - TraceLoggingValue(gci.GetFontFamily(), "FontFamily"), - TraceLoggingValue(gci.GetFontWeight(), "FontWeight"), - TraceLoggingValue(gci.GetHistoryBufferSize(), "HistoryBufferSize"), - TraceLoggingValue(gci.GetNumberOfHistoryBuffers(), "HistoryBuffersNumber"), - TraceLoggingValue(gci.GetScrollScale(), "ScrollScale"), - TraceLoggingValue(gci.GetFillAttribute(), "FillAttribute"), - TraceLoggingValue(gci.GetPopupFillAttribute(), "PopupFillAttribute"), - TraceLoggingValue(gci.GetShowWindow(), "ShowWindow"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - static_assert(sizeof(UINT32) == sizeof(renderSettings.GetColorTable()[0]), "gci.Get16ColorTable()"); - - // I could use the TraceLoggingUIntArray, but then we would have to know the order of the enums on the backend. - // So just log each enum count separately with its string representation which makes it more human readable. - // clang-format off -#pragma prefast(suppress: __WARNING_NONCONST_LOCAL, "Activity can't be const, since it's set to a random value on startup.") - // clang-format on - TraceLoggingWriteTagged(_activity, - "ApiUsed", - TraceLoggingUInt32(_rguiTimesApiUsed[AddConsoleAlias], "AddConsoleAlias"), - TraceLoggingUInt32(_rguiTimesApiUsed[AllocConsole], "AllocConsole"), - TraceLoggingUInt32(_rguiTimesApiUsed[AttachConsole], "AttachConsole"), - TraceLoggingUInt32(_rguiTimesApiUsed[CreateConsoleScreenBuffer], "CreateConsoleScreenBuffer"), - TraceLoggingUInt32(_rguiTimesApiUsed[GenerateConsoleCtrlEvent], "GenerateConsoleCtrlEvent"), - TraceLoggingUInt32(_rguiTimesApiUsed[FillConsoleOutputAttribute], "FillConsoleOutputAttribute"), - TraceLoggingUInt32(_rguiTimesApiUsed[FillConsoleOutputCharacter], "FillConsoleOutputCharacter"), - TraceLoggingUInt32(_rguiTimesApiUsed[FlushConsoleInputBuffer], "FlushConsoleInputBuffer"), - TraceLoggingUInt32(_rguiTimesApiUsed[FreeConsole], "FreeConsole"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleAlias], "GetConsoleAlias"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleAliases], "GetConsoleAliases"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleAliasExesLength], "GetConsoleAliasExesLength"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleAliasesLength], "GetConsoleAliasesLength"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleAliasExes], "GetConsoleAliasExes"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleCP], "GetConsoleCP"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleCursorInfo], "GetConsoleCursorInfo"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleDisplayMode], "GetConsoleDisplayMode"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleFontSize], "GetConsoleFontSize"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleHistoryInfo], "GetConsoleHistoryInfo"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleLangId], "GetConsoleLangId"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleMode], "GetConsoleMode"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleOriginalTitle], "GetConsoleOriginalTitle"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleOutputCP], "GetConsoleOutputCP"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleProcessList], "GetConsoleProcessList"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleScreenBufferInfoEx], "GetConsoleScreenBufferInfoEx"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleSelectionInfo], "GetConsoleSelectionInfo"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleTitle], "GetConsoleTitle"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetConsoleWindow], "GetConsoleWindow"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetCurrentConsoleFontEx], "GetCurrentConsoleFontEx"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetLargestConsoleWindowSize], "GetLargestConsoleWindowSize"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetNumberOfConsoleInputEvents], "GetNumberOfConsoleInputEvents"), - TraceLoggingUInt32(_rguiTimesApiUsed[GetNumberOfConsoleMouseButtons], "GetNumberOfConsoleMouseButtons"), - TraceLoggingUInt32(_rguiTimesApiUsed[PeekConsoleInput], "PeekConsoleInput"), - TraceLoggingUInt32(_rguiTimesApiUsed[ReadConsole], "ReadConsole"), - TraceLoggingUInt32(_rguiTimesApiUsed[ReadConsoleInput], "ReadConsoleInput"), - TraceLoggingUInt32(_rguiTimesApiUsed[ReadConsoleOutput], "ReadConsoleOutput"), - TraceLoggingUInt32(_rguiTimesApiUsed[ReadConsoleOutputAttribute], "ReadConsoleOutputAttribute"), - TraceLoggingUInt32(_rguiTimesApiUsed[ReadConsoleOutputCharacter], "ReadConsoleOutputCharacter"), - TraceLoggingUInt32(_rguiTimesApiUsed[ScrollConsoleScreenBuffer], "ScrollConsoleScreenBuffer"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleActiveScreenBuffer], "SetConsoleActiveScreenBuffer"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleCP], "SetConsoleCP"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleCursorInfo], "SetConsoleCursorInfo"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleCursorPosition], "SetConsoleCursorPosition"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleDisplayMode], "SetConsoleDisplayMode"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleHistoryInfo], "SetConsoleHistoryInfo"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleMode], "SetConsoleMode"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleOutputCP], "SetConsoleOutputCP"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleScreenBufferInfoEx], "SetConsoleScreenBufferInfoEx"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleScreenBufferSize], "SetConsoleScreenBufferSize"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleTextAttribute], "SetConsoleTextAttribute"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleTitle], "SetConsoleTitle"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetConsoleWindowInfo], "SetConsoleWindowInfo"), - TraceLoggingUInt32(_rguiTimesApiUsed[SetCurrentConsoleFontEx], "SetCurrentConsoleFontEx"), - TraceLoggingUInt32(_rguiTimesApiUsed[WriteConsole], "WriteConsole"), - TraceLoggingUInt32(_rguiTimesApiUsed[WriteConsoleInput], "WriteConsoleInput"), - TraceLoggingUInt32(_rguiTimesApiUsed[WriteConsoleOutput], "WriteConsoleOutput"), - TraceLoggingUInt32(_rguiTimesApiUsed[WriteConsoleOutputAttribute], "WriteConsoleOutputAttribute"), - TraceLoggingUInt32(_rguiTimesApiUsed[WriteConsoleOutputCharacter], "WriteConsoleOutputCharacter"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - - for (auto n = 0; n < ARRAYSIZE(_rguiTimesApiUsedAnsi); n++) - { - if (_rguiTimesApiUsedAnsi[n]) - { - // Ansi specific API's are used less, so check if we have anything to send back. - // Also breaking it up into a separate TraceLoggingWriteTagged fixes a compilation warning that - // the heap is too small. - // clang-format off -#pragma prefast(suppress: __WARNING_NONCONST_LOCAL, "Activity can't be const, since it's set to a random value on startup.") - // clang-format on - TraceLoggingWriteTagged(_activity, - "ApiAnsiUsed", - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[AddConsoleAlias], "AddConsoleAlias"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[FillConsoleOutputCharacter], "FillConsoleOutputCharacter"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleAlias], "GetConsoleAlias"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleAliases], "GetConsoleAliases"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleAliasesLength], "GetConsoleAliasesLength"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleAliasExes], "GetConsoleAliasExes"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleAliasExesLength], "GetConsoleAliasExesLength"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleOriginalTitle], "GetConsoleOriginalTitle"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[GetConsoleTitle], "GetConsoleTitle"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[PeekConsoleInput], "PeekConsoleInput"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[ReadConsole], "ReadConsole"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[ReadConsoleInput], "ReadConsoleInput"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[ReadConsoleOutput], "ReadConsoleOutput"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[ReadConsoleOutputCharacter], "ReadConsoleOutputCharacter"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[SetConsoleTitle], "SetConsoleTitle"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[WriteConsole], "WriteConsole"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[WriteConsoleInput], "WriteConsoleInput"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[WriteConsoleOutput], "WriteConsoleOutput"), - TraceLoggingUInt32(_rguiTimesApiUsedAnsi[WriteConsoleOutputCharacter], "WriteConsoleOutputCharacter"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - break; - } - } - } - } -} - -// These are legacy error messages with limited value, so don't send them back as telemetry. -void Telemetry::LogRipMessage(_In_z_ const char* pszMessage, ...) const -{ - // Code needed for passing variable parameters to the vsprintf function. - va_list args; - va_start(args, pszMessage); - char szMessageEvaluated[200] = ""; - auto cCharsWritten = vsprintf_s(szMessageEvaluated, ARRAYSIZE(szMessageEvaluated), pszMessage, args); - va_end(args); - -#if DBG - OutputDebugStringA(szMessageEvaluated); -#endif - - if (cCharsWritten > 0) - { - // clang-format off -#pragma prefast(suppress: __WARNING_NONCONST_LOCAL, "Activity can't be const, since it's set to a random value on startup.") - // clang-format on - TraceLoggingWriteTagged(_activity, - "RipMessage", - TraceLoggingString(szMessageEvaluated, "Message")); - } -} - -bool Telemetry::IsUserInteractive() -{ - return _fUserInteractiveForTelemetry; -} diff --git a/src/host/telemetry.hpp b/src/host/telemetry.hpp deleted file mode 100644 index 4cb2560a3b5..00000000000 --- a/src/host/telemetry.hpp +++ /dev/null @@ -1,186 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- telemetry.hpp - -Abstract: -- This module is used for recording all telemetry feedback from the console - -Author(s): -- Evan Wirt (EvanWi) 09-Jul-2014 -- Kourosh Mehrain (KMehrain) 09-Jul-2014 -- Stephen Somuah (StSomuah) 09-Jul-2014 -- Anup Manandhar (AnupM) 09-Jul-2014 ---*/ -#pragma once - -#include - -class Telemetry -{ -public: - // Implement this as a singleton class. - static Telemetry& Instance() - { - static Telemetry s_Instance; - return s_Instance; - } - - void SetUserInteractive(); - void SetWindowSizeChanged(); - void SetContextMenuUsed(); - void SetKeyboardTextSelectionUsed(); - void SetKeyboardTextEditingUsed(); - void SetCtrlPgUpPgDnUsed(); - void LogCtrlShiftCProcUsed(); - void LogCtrlShiftCRawUsed(); - void LogCtrlShiftVProcUsed(); - void LogCtrlShiftVRawUsed(); - void LogQuickEditCopyProcUsed(); - void LogQuickEditCopyRawUsed(); - void LogQuickEditPasteProcUsed(); - void LogQuickEditPasteRawUsed(); - void LogColorSelectionUsed(); - - void LogProcessConnected(const HANDLE hProcess); - void WriteFinalTraceLog(); - - void LogRipMessage(_In_z_ const char* pszMessage, ...) const; - - bool IsUserInteractive(); - - // Names are from the external API call names. Note that some names can be different - // than the internal API calls. - // Don't worry about the following APIs, because they are external to our conhost codebase and hard to track through - // telemetry: GetStdHandle, SetConsoleCtrlHandler, SetStdHandle - // We can't differentiate between these apis, so just log the "-Ex" versions: GetConsoleScreenBufferInfo / GetConsoleScreenBufferInfoEx, - // GetCurrentConsoleFontEx / GetCurrentConsoleFont - enum ApiCall - { - AddConsoleAlias = 0, - AllocConsole, - AttachConsole, - CreateConsoleScreenBuffer, - FillConsoleOutputAttribute, - FillConsoleOutputCharacter, - FlushConsoleInputBuffer, - FreeConsole, - GenerateConsoleCtrlEvent, - GetConsoleAlias, - GetConsoleAliases, - GetConsoleAliasesLength, - GetConsoleAliasExes, - GetConsoleAliasExesLength, - GetConsoleCP, - GetConsoleCursorInfo, - GetConsoleDisplayMode, - GetConsoleFontSize, - GetConsoleHistoryInfo, - GetConsoleMode, - GetConsoleLangId, - GetConsoleOriginalTitle, - GetConsoleOutputCP, - GetConsoleProcessList, - GetConsoleScreenBufferInfoEx, - GetConsoleSelectionInfo, - GetConsoleTitle, - GetConsoleWindow, - GetCurrentConsoleFontEx, - GetLargestConsoleWindowSize, - GetNumberOfConsoleInputEvents, - GetNumberOfConsoleMouseButtons, - PeekConsoleInput, - ReadConsole, - ReadConsoleInput, - ReadConsoleOutput, - ReadConsoleOutputAttribute, - ReadConsoleOutputCharacter, - ScrollConsoleScreenBuffer, - SetConsoleActiveScreenBuffer, - SetConsoleCP, - SetConsoleCursorInfo, - SetConsoleCursorPosition, - SetConsoleDisplayMode, - SetConsoleHistoryInfo, - SetConsoleMode, - SetConsoleOutputCP, - SetConsoleScreenBufferInfoEx, - SetConsoleScreenBufferSize, - SetConsoleTextAttribute, - SetConsoleTitle, - SetConsoleWindowInfo, - SetCurrentConsoleFontEx, - WriteConsole, - WriteConsoleInput, - WriteConsoleOutput, - WriteConsoleOutputAttribute, - WriteConsoleOutputCharacter, - // Only use this last enum as a count of the number of api enums. - NUMBER_OF_APIS - }; - void LogApiCall(const ApiCall api); - void LogApiCall(const ApiCall api, const BOOLEAN fUnicode); - -private: - // Used to prevent multiple instances - Telemetry(); - ~Telemetry(); - Telemetry(const Telemetry&); - void operator=(const Telemetry&); - - bool FindProcessName(const WCHAR* pszProcessName, _Out_ size_t* iPosition) const; - - static const int c_iMaxProcessesConnected = 100; - - TraceLoggingActivity _activity; - - unsigned int _uiColorSelectionUsed; - time_t _tStartedAt; - WCHAR const* const c_pwszBashExeName = L"bash.exe"; - - // The current recommendation is to keep telemetry events 4KB or less, so let's keep our array at less than 2KB (1000 * 2 bytes). - WCHAR _wchProcessFileNames[1000]; - // Index into our specially packed string, where to insert the next string. - size_t _iProcessFileNamesNext; - // Index for the currently connected process. - size_t _iProcessConnectedCurrently; - // An array of indexes into the _wchProcessFileNames array, which point to the individual process names. - size_t _rgiProcessFileNameIndex[c_iMaxProcessesConnected]; - // Number of times each process has connected to the console. - unsigned int _rguiProcessFileNamesCount[c_iMaxProcessesConnected]; - // To speed up searching the Process Names, create an alphabetically sorted index. - size_t _rgiAlphabeticalIndex[c_iMaxProcessesConnected]; - unsigned int _rguiTimesApiUsed[NUMBER_OF_APIS]; - // Most of this array will be empty, and is only used if an API has an ansi specific variant. - unsigned int _rguiTimesApiUsedAnsi[NUMBER_OF_APIS]; - // Total number of file names we've added. - UINT16 _uiNumberProcessFileNames; - - bool _fBashUsed; - bool _fKeyboardTextEditingUsed; - bool _fKeyboardTextSelectionUsed; - bool _fUserInteractiveForTelemetry; - bool _fCtrlPgUpPgDnUsed; - - // Linux copy and paste keyboard shortcut telemetry - unsigned int _uiCtrlShiftCProcUsed; - unsigned int _uiCtrlShiftCRawUsed; - unsigned int _uiCtrlShiftVProcUsed; - unsigned int _uiCtrlShiftVRawUsed; - - // Quick edit copy and paste usage telemetry - unsigned int _uiQuickEditCopyProcUsed; - unsigned int _uiQuickEditCopyRawUsed; - unsigned int _uiQuickEditPasteProcUsed; - unsigned int _uiQuickEditPasteRawUsed; -}; - -// Log the RIPMSG through telemetry, and also through a normal OutputDebugStringW call. -// These are drop-in substitutes for the RIPMSG0-4 macros from /windows/Core/ntcon2/conhost/consrv.h -#define RIPMSG0(flags, msg) Telemetry::Instance().LogRipMessage(msg); -#define RIPMSG1(flags, msg, a) Telemetry::Instance().LogRipMessage(msg, a); -#define RIPMSG2(flags, msg, a, b) Telemetry::Instance().LogRipMessage(msg, a, b); -#define RIPMSG3(flags, msg, a, b, c) Telemetry::Instance().LogRipMessage(msg, a, b, c); -#define RIPMSG4(flags, msg, a, b, c, d) Telemetry::Instance().LogRipMessage(msg, a, b, c, d); diff --git a/src/host/tracing.cpp b/src/host/tracing.cpp index 998410cf247..c3ae744f41f 100644 --- a/src/host/tracing.cpp +++ b/src/host/tracing.cpp @@ -6,6 +6,12 @@ #include "../types/UiaTextRangeBase.hpp" #include "../types/ScreenInfoUiaProviderBase.h" +TRACELOGGING_DEFINE_PROVIDER(g_hConhostV2EventTraceProvider, + "Microsoft.Windows.Console.Host", + // {fe1ff234-1f09-50a8-d38d-c44fab43e818} + (0xfe1ff234, 0x1f09, 0x50a8, 0xd3, 0x8d, 0xc4, 0x4f, 0xab, 0x43, 0xe8, 0x18), + TraceLoggingOptionMicrosoftTelemetry()); + using namespace Microsoft::Console::Types; // NOTE: See `til.h` for which keyword flags are reserved @@ -194,15 +200,12 @@ void Tracing::s_TraceConsoleAttachDetach(_In_ ConsoleProcessHandle* const pConso { if (TraceLoggingProviderEnabled(g_hConhostV2EventTraceProvider, 0, TraceKeywords::ConsoleAttachDetach)) { - auto bIsUserInteractive = Telemetry::Instance().IsUserInteractive(); - TraceLoggingWrite( g_hConhostV2EventTraceProvider, "ConsoleAttachDetach", TraceLoggingPid(pConsoleProcessHandle->dwProcessId, "AttachedProcessId"), TraceLoggingFileTime(pConsoleProcessHandle->GetProcessCreationTime(), "AttachedProcessCreationTime"), TraceLoggingBool(bIsAttach, "IsAttach"), - TraceLoggingBool(bIsUserInteractive, "IsUserInteractive"), TraceLoggingKeyword(TIL_KEYWORD_TRACE), TraceLoggingKeyword(TraceKeywords::ConsoleAttachDetach)); } diff --git a/src/host/ut_host/ClipboardTests.cpp b/src/host/ut_host/ClipboardTests.cpp index 2f9e03c88bb..30b45179095 100644 --- a/src/host/ut_host/ClipboardTests.cpp +++ b/src/host/ut_host/ClipboardTests.cpp @@ -67,7 +67,16 @@ class ClipboardTests const UINT cRectsSelected = 4; - std::vector SetupRetrieveFromBuffers(bool fLineSelection, std::vector& selection) + std::pair GetBufferSize() + { + const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + const auto& screenInfo = gci.GetActiveOutputBuffer(); + const auto& buffer = screenInfo.GetTextBuffer(); + const auto bufferBounds = buffer.GetSize(); + return { bufferBounds.Width(), bufferBounds.Height() }; + } + + std::wstring SetupRetrieveFromBuffer(bool fLineSelection) { const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); // NOTE: This test requires innate knowledge of how the common buffer text is emitted in order to test all cases @@ -75,82 +84,86 @@ class ClipboardTests // set up and try to retrieve the first 4 rows from the buffer const auto& screenInfo = gci.GetActiveOutputBuffer(); - - selection.clear(); - selection.emplace_back(til::inclusive_rect{ 0, 0, 8, 0 }); - selection.emplace_back(til::inclusive_rect{ 0, 1, 14, 1 }); - selection.emplace_back(til::inclusive_rect{ 0, 2, 14, 2 }); - selection.emplace_back(til::inclusive_rect{ 0, 3, 8, 3 }); - const auto& buffer = screenInfo.GetTextBuffer(); - return buffer.GetText(true, fLineSelection, selection).text; + + constexpr til::point_span selection = { { 0, 0 }, { 14, 3 } }; + const auto req = TextBuffer::CopyRequest::FromConfig(buffer, selection.start, selection.end, false, !fLineSelection, false); + return buffer.GetPlainText(req); } -#pragma prefast(push) -#pragma prefast(disable : 26006, "Specifically trying to check unterminated strings in this test.") - TEST_METHOD(TestRetrieveFromBuffer) + TEST_METHOD(TestRetrieveBlockSelectionFromBuffer) { // NOTE: This test requires innate knowledge of how the common buffer text is emitted in order to test all cases // Please see CommonState.hpp for information on the buffer state per row, the row contents, etc. - std::vector selection; - const auto text = SetupRetrieveFromBuffers(false, selection); - - // verify trailing bytes were trimmed - // there are 2 double-byte characters in our sample string (see CommonState.hpp for sample) - // the width is right - left - VERIFY_ARE_EQUAL((til::CoordType)wcslen(text[0].data()), selection[0].right - selection[0].left + 1); - - // since we're not in line selection, the line should be \r\n terminated - auto tempPtr = text[0].data(); - tempPtr += text[0].size(); - tempPtr -= 2; - VERIFY_ARE_EQUAL(String(tempPtr), String(L"\r\n")); - - // since we're not in line selection, spaces should be trimmed from the end - tempPtr = text[0].data(); - tempPtr += selection[0].right - selection[0].left - 2; - tempPtr++; - VERIFY_IS_NULL(wcsrchr(tempPtr, L' ')); - - // final line of selection should not contain CR/LF - tempPtr = text[3].data(); - tempPtr += text[3].size(); - tempPtr -= 2; - VERIFY_ARE_NOT_EQUAL(String(tempPtr), String(L"\r\n")); + const auto text = SetupRetrieveFromBuffer(false); + + std::wstring expectedText; + + // Block selection: + // - Add line breaks on wrapped and non-wrapped rows. + // - No trimming of trailing whitespace because trimming in Block + // selection is disabled. + + // All rows: + // First 15 columns selected -> 7 characters (9 columns) + 6 spaces + // Add CR/LF (except the last row) + + // row 0 + expectedText += L"AB\u304bC\u304dDE "; + expectedText += L"\r\n"; + + // row 1 + expectedText += L"AB\u304bC\u304dDE "; + expectedText += L"\r\n"; + + // row 2 + expectedText += L"AB\u304bC\u304dDE "; + expectedText += L"\r\n"; + + // row 3 + // last row -> no CR/LF + expectedText += L"AB\u304bC\u304dDE "; + + VERIFY_ARE_EQUAL(expectedText, text); } -#pragma prefast(pop) TEST_METHOD(TestRetrieveLineSelectionFromBuffer) { // NOTE: This test requires innate knowledge of how the common buffer text is emitted in order to test all cases // Please see CommonState.hpp for information on the buffer state per row, the row contents, etc. - - std::vector selection; - const auto text = SetupRetrieveFromBuffers(true, selection); - - // row 2, no wrap - // no wrap row before the end should have CR/LF - auto tempPtr = text[2].data(); - tempPtr += text[2].size(); - tempPtr -= 2; - VERIFY_ARE_EQUAL(String(tempPtr), String(L"\r\n")); - - // no wrap row should trim spaces at the end - tempPtr = text[2].data(); - VERIFY_IS_NULL(wcsrchr(tempPtr, L' ')); - - // row 1, wrap - // wrap row before the end should *not* have CR/LF - tempPtr = text[1].data(); - tempPtr += text[1].size(); - tempPtr -= 2; - VERIFY_ARE_NOT_EQUAL(String(tempPtr), String(L"\r\n")); - - // wrap row should have spaces at the end - tempPtr = text[1].data(); - auto ptr = wcsrchr(tempPtr, L' '); - VERIFY_IS_NOT_NULL(ptr); + const auto text = SetupRetrieveFromBuffer(true); + + std::wstring expectedText; + + // Line Selection: + // - Add line breaks on non-wrapped rows. + // - Trim trailing whitespace on non-wrapped rows. + + // row 0 + // no wrap -> trim trailing whitespace, add CR/LF + // All columns selected -> 7 characters, trimmed trailing spaces + expectedText += L"AB\u304bC\u304dDE"; + expectedText += L"\r\n"; + + // row 1 + // wrap -> no trimming of trailing whitespace, no CR/LF + // All columns selected -> 7 characters (9 columns) + (bufferWidth - 9) spaces + const auto [bufferWidth, bufferHeight] = GetBufferSize(); + expectedText += L"AB\u304bC\u304dDE" + std::wstring(bufferWidth - 9, L' '); + + // row 2 + // no wrap -> trim trailing whitespace, add CR/LF + // All columns selected -> 7 characters (9 columns), trimmed trailing spaces + expectedText += L"AB\u304bC\u304dDE"; + expectedText += L"\r\n"; + + // row 3 + // wrap -> no trimming of trailing whitespace, no CR/LF + // First 15 columns selected -> 7 characters (9 columns) + 6 spaces + expectedText += L"AB\u304bC\u304dDE "; + + VERIFY_ARE_EQUAL(expectedText, text); } TEST_METHOD(CanConvertText) diff --git a/src/host/ut_host/Host.UnitTests.vcxproj b/src/host/ut_host/Host.UnitTests.vcxproj index f995dea0a44..3592ec1489f 100644 --- a/src/host/ut_host/Host.UnitTests.vcxproj +++ b/src/host/ut_host/Host.UnitTests.vcxproj @@ -28,7 +28,6 @@ - diff --git a/src/host/ut_host/Host.UnitTests.vcxproj.filters b/src/host/ut_host/Host.UnitTests.vcxproj.filters index 4366c712fb9..9e61fb41067 100644 --- a/src/host/ut_host/Host.UnitTests.vcxproj.filters +++ b/src/host/ut_host/Host.UnitTests.vcxproj.filters @@ -39,9 +39,6 @@ Source Files - - Source Files - Source Files diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index 985fcadf343..98a6f09b2e5 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -604,6 +604,16 @@ void ScreenBufferTests::TestResetClearTabStops() stateMachine.ProcessString(resetToInitialState); expectedStops = { 8, 16, 24, 32, 40, 48, 56, 64, 72 }; VERIFY_ARE_EQUAL(expectedStops, _GetTabStops(screenInfo)); + + Log::Comment(L"DECST8C with 5 parameter resets tabs to defaults."); + stateMachine.ProcessString(clearTabStops); + stateMachine.ProcessString(L"\033[?5W"); + VERIFY_ARE_EQUAL(expectedStops, _GetTabStops(screenInfo)); + + Log::Comment(L"DECST8C with omitted parameter resets tabs to defaults."); + stateMachine.ProcessString(clearTabStops); + stateMachine.ProcessString(L"\033[?W"); + VERIFY_ARE_EQUAL(expectedStops, _GetTabStops(screenInfo)); } void ScreenBufferTests::TestAddTabStop() @@ -4505,6 +4515,7 @@ void ScreenBufferTests::EraseScrollbackTests() auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer(); auto& stateMachine = si.GetStateMachine(); const auto& cursor = si.GetTextBuffer().GetCursor(); + const auto initialAttributes = si.GetAttributes(); WI_SetFlag(si.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING); const auto bufferWidth = si.GetBufferSize().Width(); @@ -4561,7 +4572,7 @@ void ScreenBufferTests::EraseScrollbackTests() } Log::Comment(L"The rest of the buffer should be cleared with default attributes."); - VERIFY_IS_TRUE(_ValidateLinesContain(viewportLine, bufferHeight, L' ', TextAttribute{})); + VERIFY_IS_TRUE(_ValidateLinesContain(viewportLine, bufferHeight, L' ', initialAttributes)); } void ScreenBufferTests::EraseTests() diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp index d1c7395635f..648b054778b 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -157,7 +157,7 @@ class TextBufferTests TEST_METHOD(GetGlyphBoundaries); TEST_METHOD(GetTextRects); - TEST_METHOD(GetText); + TEST_METHOD(GetPlainText); TEST_METHOD(HyperlinkTrim); TEST_METHOD(NoHyperlinkTrim); @@ -2087,41 +2087,42 @@ void TextBufferTests::TestRowReplaceText() void TextBufferTests::TestAppendRTFText() { { - std::ostringstream contentStream; + std::string contentStream; const auto ascii = L"This is some Ascii \\ {}"; TextBuffer::_AppendRTFText(contentStream, ascii); - VERIFY_ARE_EQUAL("This is some Ascii \\\\ \\{\\}", contentStream.str()); + VERIFY_ARE_EQUAL("This is some Ascii \\\\ \\{\\}", contentStream); } { - std::ostringstream contentStream; + std::string contentStream; // "Low code units: á é í ó ú ⮁ ⮂" in UTF-16 const auto lowCodeUnits = L"Low code units: \x00E1 \x00E9 \x00ED \x00F3 \x00FA \x2B81 \x2B82"; TextBuffer::_AppendRTFText(contentStream, lowCodeUnits); - VERIFY_ARE_EQUAL("Low code units: \\u225? \\u233? \\u237? \\u243? \\u250? \\u11137? \\u11138?", contentStream.str()); + VERIFY_ARE_EQUAL("Low code units: \\u225? \\u233? \\u237? \\u243? \\u250? \\u11137? \\u11138?", contentStream); } { - std::ostringstream contentStream; + std::string contentStream; // "High code units: ꞵ ꞷ" in UTF-16 const auto highCodeUnits = L"High code units: \xA7B5 \xA7B7"; TextBuffer::_AppendRTFText(contentStream, highCodeUnits); - VERIFY_ARE_EQUAL("High code units: \\u-22603? \\u-22601?", contentStream.str()); + VERIFY_ARE_EQUAL("High code units: \\u-22603? \\u-22601?", contentStream); } { - std::ostringstream contentStream; + std::string contentStream; // "Surrogates: 🍦 👾 👀" in UTF-16 const auto surrogates = L"Surrogates: \xD83C\xDF66 \xD83D\xDC7E \xD83D\xDC40"; TextBuffer::_AppendRTFText(contentStream, surrogates); - VERIFY_ARE_EQUAL("Surrogates: \\u-10180?\\u-8346? \\u-10179?\\u-9090? \\u-10179?\\u-9152?", contentStream.str()); + VERIFY_ARE_EQUAL("Surrogates: \\u-10180?\\u-8346? \\u-10179?\\u-9090? \\u-10179?\\u-9152?", contentStream); } } void TextBufferTests::WriteLinesToBuffer(const std::vector& text, TextBuffer& buffer) { const auto bufferSize = buffer.GetSize(); - + int rowsWrapped{}; for (size_t row = 0; row < text.size(); ++row) { auto line = text[row]; + if (!line.empty()) { // TODO GH#780: writing up to (but not past) the end of the line @@ -2133,7 +2134,12 @@ void TextBufferTests::WriteLinesToBuffer(const std::vector& text, } OutputCellIterator iter{ line }; - buffer.Write(iter, { 0, gsl::narrow(row) }, wrap); + buffer.Write(iter, { 0, gsl::narrow(row + rowsWrapped) }, wrap); + //prevent bug that overwrites wrapped rows + if (line.size() > static_cast(bufferSize.RightExclusive())) + { + rowsWrapped += static_cast(line.size()) / bufferSize.RightExclusive(); + } } } } @@ -2245,6 +2251,88 @@ void TextBufferTests::GetWordBoundaries() const auto expected = accessibilityMode ? test.expected.accessibilityModeEnabled : test.expected.accessibilityModeDisabled; VERIFY_ARE_EQUAL(expected, result); } + + _buffer->Reset(); + _buffer->ResizeTraditional({ 10, 5 }); + const std::vector secondText = { L"this wordiswrapped", + L"spaces wrapped reachEOB" }; + //Buffer looks like: + // this wordi + // swrapped + // spaces + // wrappe + // d reachEOB + WriteLinesToBuffer(secondText, *_buffer); + testData = { + { { 0, 0 }, { { 0, 0 }, { 0, 0 } } }, + { { 1, 0 }, { { 0, 0 }, { 0, 0 } } }, + { { 4, 0 }, { { 4, 0 }, { 0, 0 } } }, + { { 5, 0 }, { { 5, 0 }, { 5, 0 } } }, + { { 7, 0 }, { { 5, 0 }, { 5, 0 } } }, + + { { 4, 1 }, { { 5, 0 }, { 5, 0 } } }, + { { 7, 1 }, { { 5, 0 }, { 5, 0 } } }, + { { 9, 1 }, { { 8, 1 }, { 5, 0 } } }, + + { { 0, 2 }, { { 0, 2 }, { 0, 2 } } }, + { { 7, 2 }, { { 6, 2 }, { 0, 2 } } }, + + { { 1, 3 }, { { 0, 3 }, { 0, 2 } } }, + { { 4, 3 }, { { 4, 3 }, { 4, 3 } } }, + { { 8, 3 }, { { 4, 3 }, { 4, 3 } } }, + + { { 0, 4 }, { { 4, 3 }, { 4, 3 } } }, + { { 1, 4 }, { { 1, 4 }, { 4, 3 } } }, + { { 9, 4 }, { { 2, 4 }, { 2, 4 } } }, + }; + for (const auto& test : testData) + { + Log::Comment(NoThrowString().Format(L"Testing til::point (%hd, %hd)", test.startPos.x, test.startPos.y)); + const auto result = _buffer->GetWordStart(test.startPos, delimiters, accessibilityMode); + const auto expected = accessibilityMode ? test.expected.accessibilityModeEnabled : test.expected.accessibilityModeDisabled; + VERIFY_ARE_EQUAL(expected, result); + } + + //GetWordEnd for Wrapping Text + //Buffer looks like: + // this wordi + // swrapped + // spaces + // wrappe + // d reachEOB + testData = { + // tests for first line of text + { { 0, 0 }, { { 3, 0 }, { 5, 0 } } }, + { { 1, 0 }, { { 3, 0 }, { 5, 0 } } }, + { { 4, 0 }, { { 4, 0 }, { 5, 0 } } }, + { { 5, 0 }, { { 7, 1 }, { 0, 2 } } }, + { { 7, 0 }, { { 7, 1 }, { 0, 2 } } }, + + { { 4, 1 }, { { 7, 1 }, { 0, 2 } } }, + { { 7, 1 }, { { 7, 1 }, { 0, 2 } } }, + { { 9, 1 }, { { 9, 1 }, { 0, 2 } } }, + + { { 0, 2 }, { { 5, 2 }, { 4, 3 } } }, + { { 7, 2 }, { { 9, 2 }, { 4, 3 } } }, + + { { 1, 3 }, { { 3, 3 }, { 4, 3 } } }, + { { 4, 3 }, { { 0, 4 }, { 2, 4 } } }, + { { 8, 3 }, { { 0, 4 }, { 2, 4 } } }, + + { { 0, 4 }, { { 0, 4 }, { 2, 4 } } }, + { { 1, 4 }, { { 1, 4 }, { 2, 4 } } }, + { { 4, 4 }, { { 9, 4 }, { 0, 5 } } }, + { { 9, 4 }, { { 9, 4 }, { 0, 5 } } }, + }; + // clang-format on + + for (const auto& test : testData) + { + Log::Comment(NoThrowString().Format(L"TestEnd til::point (%hd, %hd)", test.startPos.x, test.startPos.y)); + auto result = _buffer->GetWordEnd(test.startPos, delimiters, accessibilityMode); + const auto expected = accessibilityMode ? test.expected.accessibilityModeEnabled : test.expected.accessibilityModeDisabled; + VERIFY_ARE_EQUAL(expected, result); + } } void TextBufferTests::MoveByWord() @@ -2447,9 +2535,9 @@ void TextBufferTests::GetTextRects() } } -void TextBufferTests::GetText() +void TextBufferTests::GetPlainText() { - // GetText() is used by... + // GetPlainText() is used by... // - Copying text to the clipboard regularly // - Copying text to the clipboard, with shift held (collapse to one line) // - Extracting text from a UiaTextRange @@ -2485,14 +2573,10 @@ void TextBufferTests::GetText() WriteLinesToBuffer(bufferText, *_buffer); // simulate a selection from origin to {4,4} - const auto textRects = _buffer->GetTextRects({ 0, 0 }, { 4, 4 }, blockSelection, false); + constexpr til::point_span selection = { { 0, 0 }, { 4, 4 } }; - std::wstring result = L""; - const auto textData = _buffer->GetText(includeCRLF, trimTrailingWhitespace, textRects).text; - for (auto& text : textData) - { - result += text; - } + const auto req = TextBuffer::CopyRequest{ *_buffer, selection.start, selection.end, blockSelection, includeCRLF, trimTrailingWhitespace, false }; + const auto result = _buffer->GetPlainText(req); std::wstring expectedText = L""; if (includeCRLF) @@ -2571,9 +2655,7 @@ void TextBufferTests::GetText() // Setup: Write lines of text to the buffer const std::vector bufferText = { L"1234567", - L"", - L" 345", - L"123 ", + L" 345123 ", L"" }; WriteLinesToBuffer(bufferText, *_buffer); // buffer should look like this: @@ -2586,16 +2668,11 @@ void TextBufferTests::GetText() // |_____| // simulate a selection from origin to {4,5} - const auto textRects = _buffer->GetTextRects({ 0, 0 }, { 4, 5 }, blockSelection, false); - - std::wstring result = L""; + constexpr til::point_span selection = { { 0, 0 }, { 4, 5 } }; const auto formatWrappedRows = blockSelection; - const auto textData = _buffer->GetText(includeCRLF, trimTrailingWhitespace, textRects, nullptr, formatWrappedRows).text; - for (auto& text : textData) - { - result += text; - } + const auto req = TextBuffer::CopyRequest{ *_buffer, selection.start, selection.end, blockSelection, includeCRLF, trimTrailingWhitespace, formatWrappedRows }; + const auto result = _buffer->GetPlainText(req); std::wstring expectedText = L""; if (formatWrappedRows) @@ -2653,7 +2730,7 @@ void TextBufferTests::GetText() Log::Comment(L"Standard Copy to Clipboard"); expectedText += L"12345"; expectedText += L"67\r\n"; - expectedText += L" 345\r\n"; + expectedText += L" 345"; expectedText += L"123 \r\n"; } else @@ -2661,7 +2738,7 @@ void TextBufferTests::GetText() Log::Comment(L"UI Automation"); expectedText += L"12345"; expectedText += L"67 \r\n"; - expectedText += L" 345\r\n"; + expectedText += L" 345"; expectedText += L"123 "; expectedText += L" \r\n"; expectedText += L" "; diff --git a/src/host/ut_host/Utf8ToWideCharParserTests.cpp b/src/host/ut_host/Utf8ToWideCharParserTests.cpp deleted file mode 100644 index 041e0bf39c2..00000000000 --- a/src/host/ut_host/Utf8ToWideCharParserTests.cpp +++ /dev/null @@ -1,405 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "WexTestClass.h" -#include "../../inc/consoletaeftemplates.hpp" - -#include "utf8ToWideCharParser.hpp" - -#define IsBitSet WI_IsFlagSet - -using namespace WEX::Common; -using namespace WEX::Logging; -using namespace WEX::TestExecution; -using namespace std; - -class Utf8ToWideCharParserTests -{ - static const unsigned int utf8CodePage = 65001; - static const unsigned int USACodePage = 1252; - - TEST_CLASS(Utf8ToWideCharParserTests); - - TEST_METHOD(ConvertsAsciiTest) - { - Log::Comment(L"Testing that ASCII chars are correctly converted to wide chars"); - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - // ascii "hello" - const unsigned char hello[5] = { 0x48, 0x65, 0x6c, 0x6c, 0x6f }; - const unsigned char wideHello[10] = { 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00 }; - unsigned int count = 5; - unsigned int consumed = 0; - unsigned int generated = 0; - unique_ptr output{ nullptr }; - - VERIFY_SUCCEEDED(parser.Parse(hello, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(consumed, (unsigned int)5); - VERIFY_ARE_EQUAL(generated, (unsigned int)5); - VERIFY_ARE_NOT_EQUAL(output.get(), nullptr); - - auto pReturnedBytes = reinterpret_cast(output.get()); - for (auto i = 0; i < ARRAYSIZE(wideHello); ++i) - { - VERIFY_ARE_EQUAL(wideHello[i], pReturnedBytes[i]); - } - } - - TEST_METHOD(ConvertSimpleUtf8Test) - { - Log::Comment(L"Testing that a simple UTF8 sequence can be converted"); - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - // U+3059, U+3057 (hiragana sushi) - const unsigned char sushi[6] = { 0xe3, 0x81, 0x99, 0xe3, 0x81, 0x97 }; - const unsigned char wideSushi[4] = { 0x59, 0x30, 0x57, 0x30 }; - unsigned int count = 6; - unsigned int consumed = 0; - unsigned int generated = 0; - unique_ptr output{ nullptr }; - - VERIFY_SUCCEEDED(parser.Parse(sushi, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(consumed, (unsigned int)6); - VERIFY_ARE_EQUAL(generated, (unsigned int)2); - VERIFY_ARE_NOT_EQUAL(output.get(), nullptr); - - auto pReturnedBytes = reinterpret_cast(output.get()); - for (auto i = 0; i < ARRAYSIZE(wideSushi); ++i) - { - VERIFY_ARE_EQUAL(wideSushi[i], pReturnedBytes[i]); - } - } - - TEST_METHOD(WaitsForAdditionalInputAfterPartialSequenceTest) - { - Log::Comment(L"Testing that nothing is returned when parsing a partial sequence until the sequence is complete"); - // U+3057 (hiragana shi) - unsigned char shi[3] = { 0xe3, 0x81, 0x97 }; - unsigned char wideShi[2] = { 0x57, 0x30 }; - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - unsigned int count = 1; - unsigned int consumed = 0; - unsigned int generated = 0; - unique_ptr output{ nullptr }; - - for (auto i = 0; i < 2; ++i) - { - VERIFY_SUCCEEDED(parser.Parse(shi + i, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(consumed, (unsigned int)1); - VERIFY_ARE_EQUAL(generated, (unsigned int)0); - VERIFY_ARE_EQUAL(output.get(), nullptr); - count = 1; - } - - VERIFY_SUCCEEDED(parser.Parse(shi + 2, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(consumed, (unsigned int)1); - VERIFY_ARE_EQUAL(generated, (unsigned int)1); - VERIFY_ARE_NOT_EQUAL(output.get(), nullptr); - - auto pReturnedBytes = reinterpret_cast(output.get()); - for (auto i = 0; i < ARRAYSIZE(wideShi); ++i) - { - VERIFY_ARE_EQUAL(wideShi[i], pReturnedBytes[i]); - } - } - - TEST_METHOD(ReturnsInitialPartOfSequenceThatEndsWithPartialTest) - { - Log::Comment(L"Testing that a valid portion of a sequence is returned when it ends with a partial sequence"); - // U+3059, U+3057 (hiragana sushi) - const unsigned char sushi[6] = { 0xe3, 0x81, 0x99, 0xe3, 0x81, 0x97 }; - const unsigned char wideSushi[4] = { 0x59, 0x30, 0x57, 0x30 }; - unsigned int count = 4; - unsigned int consumed = 0; - unsigned int generated = 0; - unique_ptr output{ nullptr }; - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - - VERIFY_SUCCEEDED(parser.Parse(sushi, count, consumed, output, generated)); - // check that we got the first wide char back - VERIFY_ARE_EQUAL(consumed, (unsigned int)4); - VERIFY_ARE_EQUAL(generated, (unsigned int)1); - VERIFY_ARE_NOT_EQUAL(output.get(), nullptr); - - auto pReturnedBytes = reinterpret_cast(output.get()); - for (auto i = 0; i < 2; ++i) - { - VERIFY_ARE_EQUAL(wideSushi[i], pReturnedBytes[i]); - } - - // add byte 2 of 3 to parser - count = 1; - consumed = 0; - generated = 0; - output.reset(nullptr); - VERIFY_SUCCEEDED(parser.Parse(sushi + 4, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(consumed, (unsigned int)1); - VERIFY_ARE_EQUAL(generated, (unsigned int)0); - VERIFY_ARE_EQUAL(output.get(), nullptr); - - // add last byte - count = 1; - consumed = 0; - generated = 0; - output.reset(nullptr); - VERIFY_SUCCEEDED(parser.Parse(sushi + 5, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(consumed, (unsigned int)1); - VERIFY_ARE_EQUAL(generated, (unsigned int)1); - VERIFY_ARE_NOT_EQUAL(output.get(), nullptr); - - pReturnedBytes = reinterpret_cast(output.get()); - for (auto i = 0; i < 2; ++i) - { - VERIFY_ARE_EQUAL(wideSushi[i + 2], pReturnedBytes[i]); - } - } - - TEST_METHOD(MergesMultiplePartialSequencesTest) - { - Log::Comment(L"Testing that partial sequences sent individually will be merged together"); - - // clang-format off - // (hiragana doomo arigatoo) - const unsigned char doomoArigatoo[24] = { - 0xe3, 0x81, 0xa9, // U+3069 - 0xe3, 0x81, 0x86, // U+3046 - 0xe3, 0x82, 0x82, // U+3082 - 0xe3, 0x81, 0x82, // U+3042 - 0xe3, 0x82, 0x8a, // U+308A - 0xe3, 0x81, 0x8c, // U+304C - 0xe3, 0x81, 0xa8, // U+3068 - 0xe3, 0x81, 0x86 // U+3046 - }; - const unsigned char wideDoomoArigatoo[16] = { - 0x69, 0x30, - 0x46, 0x30, - 0x82, 0x30, - 0x42, 0x30, - 0x8a, 0x30, - 0x4c, 0x30, - 0x68, 0x30, - 0x46, 0x30 - }; - // clang-format on - - // send first 4 bytes - unsigned int count = 4; - unsigned int consumed = 0; - unsigned int generated = 0; - unique_ptr output{ nullptr }; - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - - VERIFY_SUCCEEDED(parser.Parse(doomoArigatoo, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(consumed, (unsigned int)4); - VERIFY_ARE_EQUAL(generated, (unsigned int)1); - VERIFY_ARE_NOT_EQUAL(output.get(), nullptr); - - auto pReturnedBytes = reinterpret_cast(output.get()); - for (auto i = 0; i < 2; ++i) - { - VERIFY_ARE_EQUAL(wideDoomoArigatoo[i], pReturnedBytes[i]); - } - - // send next 16 bytes - count = 16; - consumed = 0; - generated = 0; - output.reset(nullptr); - VERIFY_SUCCEEDED(parser.Parse(doomoArigatoo + 4, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(consumed, (unsigned int)16); - VERIFY_ARE_EQUAL(generated, (unsigned int)5); - VERIFY_ARE_NOT_EQUAL(output.get(), nullptr); - - pReturnedBytes = reinterpret_cast(output.get()); - for (auto i = 0; i < 10; ++i) - { - VERIFY_ARE_EQUAL(wideDoomoArigatoo[i + 2], pReturnedBytes[i]); - } - - // send last 4 bytes - count = 4; - consumed = 0; - generated = 0; - output.reset(nullptr); - VERIFY_SUCCEEDED(parser.Parse(doomoArigatoo + 20, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(consumed, (unsigned int)4); - VERIFY_ARE_EQUAL(generated, (unsigned int)2); - VERIFY_ARE_NOT_EQUAL(output.get(), nullptr); - - pReturnedBytes = reinterpret_cast(output.get()); - for (auto i = 0; i < 4; ++i) - { - VERIFY_ARE_EQUAL(wideDoomoArigatoo[i + 12], pReturnedBytes[i]); - } - } - - TEST_METHOD(RemovesInvalidSequencesTest) - { - Log::Comment(L"Testing that invalid sequences are removed and don't stop the parsing of the rest"); - - // clang-format off - // hiragana sushi with junk between japanese characters - const unsigned char sushi[9] = { - 0xe3, 0x81, 0x99, // U+3059 - 0x80, 0x81, 0x82, // junk continuation bytes - 0xe3, 0x81, 0x97 // U+3057 - }; - // clang-format on - - const unsigned char wideSushi[4] = { 0x59, 0x30, 0x57, 0x30 }; - unsigned int count = 9; - unsigned int consumed = 0; - unsigned int generated = 0; - unique_ptr output{ nullptr }; - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - - VERIFY_SUCCEEDED(parser.Parse(sushi, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(consumed, (unsigned int)9); - VERIFY_ARE_EQUAL(generated, (unsigned int)2); - VERIFY_ARE_NOT_EQUAL(output.get(), nullptr); - - auto pReturnedBytes = reinterpret_cast(output.get()); - for (auto i = 0; i < ARRAYSIZE(wideSushi); ++i) - { - VERIFY_ARE_EQUAL(wideSushi[i], pReturnedBytes[i]); - } - } - - TEST_METHOD(NonMinimalFormTest) - { - Log::Comment(L"Testing that non-minimal forms of a character are tolerated don't stop the rest"); - - // clang-format off - - // Test data - const unsigned char data[] = { - 0x60, 0x12, 0x08, 0x7f, // single byte points - 0xc0, 0x80, // U+0000 as a 2-byte sequence (non-minimal) - 0x41, 0x48, 0x06, 0x55, // more single byte points - 0xe0, 0x80, 0x80, // U+0000 as a 3-byte sequence (non-minimal) - 0x18, 0x77, 0x40, 0x31, // more single byte points - 0xf0, 0x80, 0x80, 0x80, // U+0000 as a 4-byte sequence (non-minimal) - 0x59, 0x1f, 0x68, 0x20 // more single byte points - }; - - // Expected conversion - const wchar_t wideData[] = { - 0x0060, 0x0012, 0x0008, 0x007f, - 0xfffd, 0xfffd, // The number of replacements per invalid sequence is not intended to be load-bearing - 0x0041, 0x0048, 0x0006, 0x0055, - 0xfffd, 0xfffd, // It is just representative of what it looked like when fixing this for GH#3380 - 0x0018, 0x0077, 0x0040, 0x0031, - 0xfffd, 0xfffd, 0xfffd, // Change if necessary when completing GH#3378 - 0x0059, 0x001f, 0x0068, 0x0020 - }; - - // clang-format on - - const auto count = gsl::narrow_cast(ARRAYSIZE(data)); - const auto wideCount = gsl::narrow_cast(ARRAYSIZE(wideData)); - unsigned int consumed = 0; - unsigned int generated = 0; - unique_ptr output{ nullptr }; - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - - VERIFY_SUCCEEDED(parser.Parse(data, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(count, consumed); - VERIFY_ARE_EQUAL(wideCount, generated); - VERIFY_IS_NOT_NULL(output.get()); - - const auto expected = WEX::Common::String(wideData, wideCount); - const auto actual = WEX::Common::String(output.get(), generated); - VERIFY_ARE_EQUAL(expected, actual); - } - - TEST_METHOD(PartialBytesAreDroppedOnCodePageChangeTest) - { - Log::Comment(L"Testing that a saved partial sequence is cleared when the codepage changes"); - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - // 2 bytes of a 4 byte sequence - const unsigned int inputSize = 2; - const unsigned char partialSequence[inputSize] = { 0xF0, 0x80 }; - auto count = inputSize; - unsigned int consumed = 0; - unsigned int generated = 0; - unique_ptr output{ nullptr }; - VERIFY_SUCCEEDED(parser.Parse(partialSequence, count, consumed, output, generated)); - VERIFY_ARE_EQUAL(parser._currentState, Utf8ToWideCharParser::_State::BeginPartialParse); - VERIFY_ARE_EQUAL(parser._bytesStored, inputSize); - // set the codepage to the same one it currently is, ensure - // that nothing changes - parser.SetCodePage(utf8CodePage); - VERIFY_ARE_EQUAL(parser._currentState, Utf8ToWideCharParser::_State::BeginPartialParse); - VERIFY_ARE_EQUAL(parser._bytesStored, inputSize); - // change to a different codepage, ensure parser is reset - parser.SetCodePage(USACodePage); - VERIFY_ARE_EQUAL(parser._currentState, Utf8ToWideCharParser::_State::Ready); - VERIFY_ARE_EQUAL(parser._bytesStored, (unsigned int)0); - } - - TEST_METHOD(_IsLeadByteTest) - { - Log::Comment(L"Testing that _IsLeadByte properly differentiates correct from incorrect sequences"); - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - VERIFY_IS_TRUE(parser._IsLeadByte(0xC0)); // 2 byte sequence - VERIFY_IS_TRUE(parser._IsLeadByte(0xE0)); // 3 byte sequence - VERIFY_IS_TRUE(parser._IsLeadByte(0xF0)); // 4 byte sequence - VERIFY_IS_FALSE(parser._IsLeadByte(0x00)); // ASCII char NUL - VERIFY_IS_FALSE(parser._IsLeadByte(0x80)); // continuation byte - VERIFY_IS_FALSE(parser._IsLeadByte(0x83)); // continuation byte - VERIFY_IS_FALSE(parser._IsLeadByte(0x7E)); // ASCII char '~' - VERIFY_IS_FALSE(parser._IsLeadByte(0x21)); // ASCII char '!' - VERIFY_IS_FALSE(parser._IsLeadByte(0xF8)); // invalid 5 byte sequence - VERIFY_IS_FALSE(parser._IsLeadByte(0xFC)); // invalid 6 byte sequence - VERIFY_IS_FALSE(parser._IsLeadByte(0xFE)); // invalid 7 byte sequence - VERIFY_IS_FALSE(parser._IsLeadByte(0xFF)); // all 1's - } - - TEST_METHOD(_IsContinuationByteTest) - { - Log::Comment(L"Testing that _IsContinuationByte properly differentiates correct from incorrect sequences"); - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - for (BYTE i = 0x00; i < 0xFF; ++i) - { - if (IsBitSet(i, 0x80) && !IsBitSet(i, 0x40)) - { - VERIFY_IS_TRUE(parser._IsContinuationByte(i), NoThrowString().Format(L"Byte is 0x%02x", i)); - } - else - { - VERIFY_IS_FALSE(parser._IsContinuationByte(i), NoThrowString().Format(L"Byte is 0x%02x", i)); - } - } - VERIFY_IS_FALSE(parser._IsContinuationByte(0xFF)); - } - - TEST_METHOD(_IsAsciiByteTest) - { - Log::Comment(L"Testing that _IsAsciiByte properly differentiates correct from incorrect sequences"); - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - for (BYTE i = 0x00; i < 0x80; ++i) - { - VERIFY_IS_TRUE(parser._IsAsciiByte(i), NoThrowString().Format(L"Byte is 0x%02x", i)); - } - for (BYTE i = 0xFF; i > 0x7F; --i) - { - VERIFY_IS_FALSE(parser._IsAsciiByte(i), NoThrowString().Format(L"Byte is 0x%02x", i)); - } - } - - TEST_METHOD(_Utf8SequenceSizeTest) - { - Log::Comment(L"Testing that _Utf8SequenceSize correctly counts the number of MSB 1's"); - auto parser = Utf8ToWideCharParser{ utf8CodePage }; - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0x00), (unsigned int)0); - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0x80), (unsigned int)1); - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0xC2), (unsigned int)2); - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0xE3), (unsigned int)3); - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0xF0), (unsigned int)4); - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0xF3), (unsigned int)4); - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0xF8), (unsigned int)5); - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0xFC), (unsigned int)6); - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0xFD), (unsigned int)6); - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0xFE), (unsigned int)7); - VERIFY_ARE_EQUAL(parser._Utf8SequenceSize(0xFF), (unsigned int)8); - } -}; diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index 902f711771d..7f4796df60d 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -282,6 +282,11 @@ class MockRenderData : public IRenderData return std::vector{}; } + std::vector GetSearchSelectionRects() noexcept override + { + return std::vector{}; + } + void LockConsole() noexcept override { } @@ -363,6 +368,10 @@ class MockRenderData : public IRenderData { } + void SelectSearchRegions(std::vector /*source*/) override + { + } + const til::point GetSelectionAnchor() const noexcept { return {}; diff --git a/src/host/ut_host/sources b/src/host/ut_host/sources index 9b47dded30d..1312b3e63fa 100644 --- a/src/host/ut_host/sources +++ b/src/host/ut_host/sources @@ -27,7 +27,6 @@ SOURCES = \ TextBufferTests.cpp \ ClipboardTests.cpp \ SelectionTests.cpp \ - Utf8ToWideCharParserTests.cpp \ OutputCellIteratorTests.cpp \ InitTests.cpp \ TitleTests.cpp \ diff --git a/src/host/utf8ToWideCharParser.cpp b/src/host/utf8ToWideCharParser.cpp deleted file mode 100644 index 911ddf267cc..00000000000 --- a/src/host/utf8ToWideCharParser.cpp +++ /dev/null @@ -1,520 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "utf8ToWideCharParser.hpp" -#include - -#ifndef WIL_ENABLE_EXCEPTIONS -#error WIL exception helpers must be enabled -#endif - -#define IsBitSet WI_IsFlagSet - -const byte NonAsciiBytePrefix = 0x80; - -const byte ContinuationByteMask = 0xC0; -const byte ContinuationBytePrefix = 0x80; - -const byte MostSignificantBitMask = 0x80; - -// Routine Description: -// - Constructs an instance of the parser. -// Arguments: -// - codePage - Starting code page to interpret input with. -// Return Value: -// - A new instance of the parser. -Utf8ToWideCharParser::Utf8ToWideCharParser(const unsigned int codePage) : - _currentCodePage{ codePage }, - _bytesStored{ 0 }, - _currentState{ _State::Ready }, - _convertedWideChars{ nullptr } -{ - std::fill_n(_utf8CodePointPieces, _UTF8_BYTE_SEQUENCE_MAX, 0ui8); -} - -// Routine Description: -// - Set the code page that input sequences will correspond to. Clears -// any saved partial multi-byte sequences if the code page changes -// from the code page the partial sequence is associated with. -// Arguments: -// - codePage - the code page to set to. -// Return Value: -// - -void Utf8ToWideCharParser::SetCodePage(const unsigned int codePage) -{ - if (_currentCodePage != codePage) - { - _currentCodePage = codePage; - // we can't be making any assumptions about the partial - // sequence we were storing now that the codepage has changed - _bytesStored = 0; - _currentState = _State::Ready; - } -} - -// Routine Description: -// - Parses the input multi-byte sequence. -// Arguments: -// - pBytes - The byte sequence to parse. -// - cchBuffer - The amount of bytes in pBytes. This will contain the -// number of wide chars contained by converted after this function is -// run, or 0 if an error occurs (or if pBytes is 0). -// - converted - a valid unique_ptr to store the parsed wide chars -// in. On error this will contain nullptr instead of an array. -// Return Value: -// - -[[nodiscard]] HRESULT Utf8ToWideCharParser::Parse(_In_reads_(cchBuffer) const byte* const pBytes, - _In_ const unsigned int cchBuffer, - _Out_ unsigned int& cchConsumed, - _Inout_ std::unique_ptr& converted, - _Out_ unsigned int& cchConverted) -{ - cchConsumed = 0; - cchConverted = 0; - - // we can't parse anything if we weren't given any data to parse - if (cchBuffer == 0) - { - return S_OK; - } - // we shouldn't be parsing if the current codepage isn't UTF8 - if (_currentCodePage != CP_UTF8) - { - _currentState = _State::Error; - } - auto hr = S_OK; - try - { - auto loop = true; - unsigned int wideCharCount = 0; - _convertedWideChars.reset(nullptr); - while (loop) - { - switch (_currentState) - { - case _State::Ready: - wideCharCount = _ParseFullRange(pBytes, cchBuffer); - break; - case _State::BeginPartialParse: - wideCharCount = _InvolvedParse(pBytes, cchBuffer); - break; - case _State::Error: - hr = E_FAIL; - _Reset(); - wideCharCount = 0; - loop = false; - break; - case _State::Finished: - _currentState = _State::Ready; - cchConsumed = cchBuffer; - loop = false; - break; - case _State::AwaitingMoreBytes: - _currentState = _State::BeginPartialParse; - cchConsumed = cchBuffer; - loop = false; - break; - default: - _currentState = _State::Error; - break; - } - } - converted.swap(_convertedWideChars); - cchConverted = wideCharCount; - } - catch (...) - { - _Reset(); - hr = wil::ResultFromCaughtException(); - } - return hr; -} - -// Routine Description: -// - Determines if ch is a UTF8 lead byte. See _Utf8SequenceSize() for a -// description of how a lead byte is specified. -// Arguments: -// - ch - The byte to test. -// Return Value: -// - True if ch is a lead byte, false otherwise. -bool Utf8ToWideCharParser::_IsLeadByte(_In_ byte ch) -{ - auto sequenceSize = _Utf8SequenceSize(ch); - return !_IsContinuationByte(ch) && - !_IsAsciiByte(ch) && - sequenceSize > 1 && - sequenceSize <= _UTF8_BYTE_SEQUENCE_MAX; -} - -// Routine Description: -// - Determines if ch is a UTF8 continuation byte. A continuation byte -// takes the form 10xx xxxx, so we need to check that the two most -// significant bits are a 1 followed by a 0. -// Arguments: -// - ch - The byte to test -// Return Value: -// - True if ch is a continuation byte, false otherwise. -bool Utf8ToWideCharParser::_IsContinuationByte(_In_ byte ch) -{ - return (ch & ContinuationByteMask) == ContinuationBytePrefix; -} - -// Routine Description: -// - Determines if ch is an ASCII compatible UTF8 byte. A byte is -// ASCII compatible if the most significant bit is a 0. -// Arguments: -// - ch - The byte to test. -// Return Value: -// - True if ch is an ASCII compatible byte, false otherwise. -bool Utf8ToWideCharParser::_IsAsciiByte(_In_ byte ch) -{ - return !IsBitSet(ch, NonAsciiBytePrefix); -} - -// Routine Description: -// - Determines if the sequence starting at pLeadByte is a valid UTF8 -// multi-byte sequence. Note that a single ASCII byte does not count -// as a valid MULTI-byte sequence. -// Arguments: -// - pLeadByte - The start of a possible sequence. -// - cb - The amount of remaining chars in the array that -// pLeadByte points to. -// Return Value: -// - true if the sequence starting at pLeadByte is a multi-byte -// sequence and uses all of the remaining chars, false otherwise. -bool Utf8ToWideCharParser::_IsValidMultiByteSequence(_In_reads_(cb) const byte* const pLeadByte, const unsigned int cb) -{ - if (!_IsLeadByte(*pLeadByte)) - { - return false; - } - const auto sequenceSize = _Utf8SequenceSize(*pLeadByte); - if (sequenceSize > cb) - { - return false; - } - // i starts at 1 so that we skip the lead byte - for (unsigned int i = 1; i < sequenceSize; ++i) - { - const auto ch = *(pLeadByte + i); - if (!_IsContinuationByte(ch)) - { - return false; - } - } - return true; -} - -// Routine Description: -// - Checks if the sequence starting at pLeadByte is a portion of a -// single valid multi-byte sequence. A new sequence must not be -// started within the range provided in order for it to be considered -// a valid partial sequence. -// Arguments: -// - pLeadByte - The start of the possible partial sequence. -// - cb - The amount of remaining chars in the array that -// pLeadByte points to. -// Return Value: -// - true if the sequence is a single partial multi-byte sequence, -// false otherwise. -bool Utf8ToWideCharParser::_IsPartialMultiByteSequence(_In_reads_(cb) const byte* const pLeadByte, const unsigned int cb) -{ - if (!_IsLeadByte(*pLeadByte)) - { - return false; - } - const auto sequenceSize = _Utf8SequenceSize(*pLeadByte); - if (sequenceSize <= cb) - { - return false; - } - // i starts at 1 so that we skip the lead byte - for (unsigned int i = 1; i < cb; ++i) - { - const auto ch = *(pLeadByte + i); - if (!_IsContinuationByte(ch)) - { - return false; - } - } - return true; -} - -// Routine Description: -// - Determines the number of bytes in the UTF8 multi-byte sequence. -// Does not perform any verification that ch is a valid lead byte. A -// lead byte indicates how many bytes are in a sequence by repeating a -// 1 for each byte in the sequence, starting with the most significant -// bit, then a 0 directly after. Ex: -// - 110x xxxx = a two byte sequence -// - 1110 xxxx = a three byte sequence -// -// Note that a byte that has a pattern 10xx xxxx is a continuation -// byte and will be reported as a sequence of one by this function. -// -// A sequence is currently a maximum of four bytes but this function -// will just count the number of consecutive 1 bits (starting with the -// most significant bit) so if the byte is malformed (ex. 1111 110x) a -// number larger than the maximum utf8 byte sequence may be -// returned. It is the responsibility of the calling function to check -// this (and the continuation byte scenario) because we don't do any -// verification here. -// Arguments: -// - ch - the lead byte of a UTF8 multi-byte sequence. -// Return Value: -// - The number of bytes (including the lead byte) that ch indicates -// are in the sequence. -unsigned int Utf8ToWideCharParser::_Utf8SequenceSize(_In_ byte ch) -{ - unsigned int msbOnes = 0; - while (IsBitSet(ch, MostSignificantBitMask)) - { - ++msbOnes; - ch <<= 1; - } - return msbOnes; -} - -// Routine Description: -// - Attempts to parse pInputChars by themselves in wide chars, -// without using any saved partial byte sequences. On success, -// _convertedWideChars will contain the converted wide char sequence -// and _currentState will be set to _State::Finished. On failure, -// _currentState will be set to either _State::Error or -// _State::BeginPartialParse. -// Arguments: -// - pInputChars - The byte sequence to convert to wide chars. -// - cb - The amount of bytes in pInputChars. -// Return Value: -// - The amount of wide chars that are stored in _convertedWideChars, -// or 0 if pInputChars cannot be successfully converted. -unsigned int Utf8ToWideCharParser::_ParseFullRange(_In_reads_(cb) const byte* const pInputChars, const unsigned int cb) -{ - auto bufferSize = MultiByteToWideChar(_currentCodePage, - MB_ERR_INVALID_CHARS, - reinterpret_cast(pInputChars), - cb, - nullptr, - 0); - if (bufferSize == 0) - { - auto err = GetLastError(); - LOG_WIN32(err); - if (err == ERROR_NO_UNICODE_TRANSLATION) - { - _currentState = _State::BeginPartialParse; - } - else - { - _currentState = _State::Error; - } - } - else - { - _convertedWideChars = std::make_unique(bufferSize); - bufferSize = MultiByteToWideChar(_currentCodePage, - 0, - reinterpret_cast(pInputChars), - cb, - _convertedWideChars.get(), - bufferSize); - if (bufferSize == 0) - { - LOG_LAST_ERROR(); - _currentState = _State::Error; - } - else - { - _currentState = _State::Finished; - } - } - return bufferSize; -} - -// Routine Description: -// - Attempts to parse pInputChars in a more complex manner, taking -// into account any previously saved partial byte sequences while -// removing any invalid byte sequences. Will also save a partial byte -// sequence from the end of the sequence if necessary. If the sequence -// can be successfully parsed, _currentState will be set to -// _State::Finished. If more bytes are necessary to form a wide char, -// then _currentState will be set to -// _State::AwaitingMoreBytes. Otherwise, _currentState will be set to -// _State::Error. -// Arguments: -// - pInputChars - The byte sequence to convert to wide chars. -// - cb - The amount of bytes in pInputChars. -// Return Value: -// - The amount of wide chars that are stored in _convertedWideChars, -// or 0 if pInputChars cannot be successfully converted or if the -// parser requires additional bytes before returning a valid wide -// char. -unsigned int Utf8ToWideCharParser::_InvolvedParse(_In_reads_(cb) const byte* const pInputChars, const unsigned int cb) -{ - // Do safe math to add up the count and error if it won't fit. - unsigned int count; - const auto hr = UIntAdd(cb, _bytesStored, &count); - if (FAILED(hr)) - { - LOG_HR(hr); - _currentState = _State::Error; - return 0; - } - - // Allocate space and copy. - auto combinedInputBytes = std::make_unique(count); - std::copy(_utf8CodePointPieces, _utf8CodePointPieces + _bytesStored, combinedInputBytes.get()); - std::copy(pInputChars, pInputChars + cb, combinedInputBytes.get() + _bytesStored); - _bytesStored = 0; - auto validSequence = _RemoveInvalidSequences(combinedInputBytes.get(), count); - // the input may have only been a partial sequence so we need to - // check that there are actually any bytes that we can convert - // right now - if (validSequence.second == 0 && _bytesStored > 0) - { - _currentState = _State::AwaitingMoreBytes; - return 0; - } - - // By this point, all obviously invalid sequences have been removed. - // But non-minimal forms of sequences might still exist. - // MB2WC will fail non-minimal forms with MB_ERR_INVALID_CHARS at this point. - // So we call with flags = 0 such that non-minimal forms get the U+FFFD - // replacement character treatment. - // This issue and related concerns are fully captured in future work item GH#3378 - // for future cleanup and reconciliation. - // The original issue introducing this was GH#3320. - auto bufferSize = MultiByteToWideChar(_currentCodePage, - 0, - reinterpret_cast(validSequence.first.get()), - validSequence.second, - nullptr, - 0); - if (bufferSize == 0) - { - LOG_LAST_ERROR(); - _currentState = _State::Error; - } - else - { - _convertedWideChars = std::make_unique(bufferSize); - bufferSize = MultiByteToWideChar(_currentCodePage, - 0, - reinterpret_cast(validSequence.first.get()), - validSequence.second, - _convertedWideChars.get(), - bufferSize); - if (bufferSize == 0) - { - LOG_LAST_ERROR(); - _currentState = _State::Error; - } - else if (_bytesStored > 0) - { - _currentState = _State::AwaitingMoreBytes; - } - else - { - _currentState = _State::Finished; - } - } - return bufferSize; -} - -// Routine Description: -// - Reads pInputChars byte by byte, removing any invalid UTF8 -// multi-byte sequences. -// Arguments: -// - pInputChars - The byte sequence to fix. -// - cb - The amount of bytes in pInputChars. -// Return Value: -// - A std::pair containing the corrected byte sequence and the number -// of bytes in the sequence. -std::pair, unsigned int> Utf8ToWideCharParser::_RemoveInvalidSequences(_In_reads_(cb) const byte* const pInputChars, const unsigned int cb) -{ - auto validSequence = std::make_unique(cb); - unsigned int validSequenceLocation = 0; // index into validSequence - unsigned int currentByteInput = 0; // index into pInputChars - while (currentByteInput < cb) - { - if (_IsAsciiByte(pInputChars[currentByteInput])) - { - validSequence[validSequenceLocation] = pInputChars[currentByteInput]; - ++validSequenceLocation; - ++currentByteInput; - } - else if (_IsContinuationByte(pInputChars[currentByteInput])) - { - while (currentByteInput < cb && _IsContinuationByte(pInputChars[currentByteInput])) - { - ++currentByteInput; - } - } - else if (_IsLeadByte(pInputChars[currentByteInput])) - { - if (_IsValidMultiByteSequence(&pInputChars[currentByteInput], cb - currentByteInput)) - { - const auto sequenceSize = _Utf8SequenceSize(pInputChars[currentByteInput]); - // min is to guard against static analysis possible buffer overflow - const auto limit = std::min(sequenceSize, cb - currentByteInput); - for (unsigned int i = 0; i < limit; ++i) - { - validSequence[validSequenceLocation] = pInputChars[currentByteInput]; - ++validSequenceLocation; - ++currentByteInput; - } - } - else if (_IsPartialMultiByteSequence(&pInputChars[currentByteInput], cb - currentByteInput)) - { - _StorePartialSequence(&pInputChars[currentByteInput], cb - currentByteInput); - break; - } - else - { - ++currentByteInput; - while (currentByteInput < cb && _IsContinuationByte(pInputChars[currentByteInput])) - { - ++currentByteInput; - } - } - } - else - { - // invalid byte, skip it. - ++currentByteInput; - } - } - return std::make_pair, unsigned int>(std::move(validSequence), std::move(validSequenceLocation)); -} - -// Routine Description: -// - Stores a partial byte sequence for later use. Will overwrite any -// previously saved sequence. Will only store bytes up to the limit -// Utf8ToWideCharParser::_UTF8_BYTE_SEQUENCE_MAX. -// Arguments: -// - pLeadByte - The beginning of the sequence to save. -// - cb - The amount of bytes to save. -// Return Value: -// - -void Utf8ToWideCharParser::_StorePartialSequence(_In_reads_(cb) const byte* const pLeadByte, const unsigned int cb) -{ - const auto maxLength = std::min(cb, _UTF8_BYTE_SEQUENCE_MAX); - std::copy(pLeadByte, pLeadByte + maxLength, _utf8CodePointPieces); - _bytesStored = maxLength; -} - -// Routine Description: -// - Resets the state of the parser to that of a newly initialized -// instance. _currentCodePage is not affected. -// Arguments: -// - -// Return Value: -// - -void Utf8ToWideCharParser::_Reset() -{ - _currentState = _State::Ready; - _bytesStored = 0; - _convertedWideChars.reset(nullptr); -} diff --git a/src/host/utf8ToWideCharParser.hpp b/src/host/utf8ToWideCharParser.hpp deleted file mode 100644 index 500637437b8..00000000000 --- a/src/host/utf8ToWideCharParser.hpp +++ /dev/null @@ -1,64 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- utf8ToWideCharParser.hpp - -Abstract: -- This transforms a multi-byte character sequence into wide chars -- It will attempt to work around invalid byte sequences -- Partial byte sequences are supported - -Author(s): -- Austin Diviness (AustDi) 16-August-2016 ---*/ - -#pragma once - -class Utf8ToWideCharParser final -{ -public: - Utf8ToWideCharParser(const unsigned int codePage); - void SetCodePage(const unsigned int codePage); - [[nodiscard]] HRESULT Parse(_In_reads_(cchBuffer) const byte* const pBytes, - _In_ const unsigned int cchBuffer, - _Out_ unsigned int& cchConsumed, - _Inout_ std::unique_ptr& converted, - _Out_ unsigned int& cchConverted); - -private: - enum class _State - { - Ready, // ready for input, no partially parsed code points - Error, // error in parsing given bytes - BeginPartialParse, // not a clean byte sequence, needs involved parsing - AwaitingMoreBytes, // have a partial sequence saved, waiting for the rest of it - Finished // ready to return a wide char sequence - }; - - bool _IsLeadByte(_In_ byte ch); - bool _IsContinuationByte(_In_ byte ch); - bool _IsAsciiByte(_In_ byte ch); - bool _IsValidMultiByteSequence(_In_reads_(cb) const byte* const pLeadByte, const unsigned int cb); - bool _IsPartialMultiByteSequence(_In_reads_(cb) const byte* const pLeadByte, const unsigned int cb); - unsigned int _Utf8SequenceSize(_In_ byte ch); - unsigned int _ParseFullRange(_In_reads_(cb) const byte* const _InputChars, const unsigned int cb); - unsigned int _InvolvedParse(_In_reads_(cb) const byte* const pInputChars, const unsigned int cb); - std::pair, unsigned int> _RemoveInvalidSequences(_In_reads_(cb) const byte* const pInputChars, - const unsigned int cb); - void _StorePartialSequence(_In_reads_(cb) const byte* const pLeadByte, const unsigned int cb); - void _Reset(); - - static const unsigned int _UTF8_BYTE_SEQUENCE_MAX = 4; - - byte _utf8CodePointPieces[_UTF8_BYTE_SEQUENCE_MAX]; - unsigned int _bytesStored; // bytes stored in utf8CodePointPieces - unsigned int _currentCodePage; - std::unique_ptr _convertedWideChars; - _State _currentState; - -#ifdef UNIT_TESTING - friend class Utf8ToWideCharParserTests; -#endif -}; diff --git a/src/inc/HostAndPropsheetIncludes.h b/src/inc/HostAndPropsheetIncludes.h index 2a8d588b309..d0480421dfc 100644 --- a/src/inc/HostAndPropsheetIncludes.h +++ b/src/inc/HostAndPropsheetIncludes.h @@ -14,7 +14,9 @@ #include #undef WIN32_NO_STATUS +#ifndef NO_WINTERNL_INBOX_BUILD #include +#endif #pragma warning(push) #pragma warning(disable:4430) // Must disable 4430 "default int" warning for C++ because ntstatus.h is inflexible SDK definition. diff --git a/src/inc/LibraryIncludes.h b/src/inc/LibraryIncludes.h index 7a7ec3a6a53..511e4d4d95e 100644 --- a/src/inc/LibraryIncludes.h +++ b/src/inc/LibraryIncludes.h @@ -18,7 +18,9 @@ // Block minwindef.h min/max macros to prevent conflict #define NOMINMAX // Exclude rarely-used stuff from Windows headers +#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN +#endif #include #include @@ -65,6 +67,7 @@ // GSL // Block GSL Multi Span include because it both has C++17 deprecated iterators // and uses the C-namespaced "max" which conflicts with Windows definitions. +#include #include #include diff --git a/src/inc/conint.h b/src/inc/conint.h index 2a3c8d0fd54..5430c5c1de9 100644 --- a/src/inc/conint.h +++ b/src/inc/conint.h @@ -35,19 +35,8 @@ namespace Microsoft::Console::Internal } - namespace EdpPolicy - { - void AuditClipboard(const std::wstring_view destinationName) noexcept; - } - namespace Theming { [[nodiscard]] HRESULT TrySetDarkMode(HWND hwnd) noexcept; } - - namespace DefaultApp - { - [[nodiscard]] bool CheckDefaultAppPolicy() noexcept; - [[nodiscard]] bool CheckShouldTerminalBeDefault() noexcept; - } } diff --git a/src/inc/test/CommonState.hpp b/src/inc/test/CommonState.hpp index 1803acb0ae8..b6e8ad153be 100644 --- a/src/inc/test/CommonState.hpp +++ b/src/inc/test/CommonState.hpp @@ -259,9 +259,10 @@ class CommonState void FillRow(ROW* pRow, bool wrapForced) { // fill a row - // 9 characters, 6 spaces. 15 total - // か = \x304b - // き = \x304d + // - Each row is populated with L"AB\u304bC\u304dDE " + // - 7 characters, 6 spaces. 13 total + // - The characters take up first 9 columns. (The wide glyphs take up 2 columns each) + // - か = \x304b, き = \x304d uint16_t column = 0; for (const auto& ch : std::wstring_view{ L"AB\u304bC\u304dDE " }) diff --git a/src/interactivity/inc/IConsoleWindow.hpp b/src/interactivity/inc/IConsoleWindow.hpp index 232957ffb6c..fba098ea78d 100644 --- a/src/interactivity/inc/IConsoleWindow.hpp +++ b/src/interactivity/inc/IConsoleWindow.hpp @@ -25,13 +25,6 @@ namespace Microsoft::Console::Types public: virtual ~IConsoleWindow() = default; - virtual BOOL EnableBothScrollBars() = 0; - virtual int UpdateScrollBar(_In_ bool isVertical, - _In_ bool isAltBuffer, - _In_ UINT pageSize, - _In_ int maxSize, - _In_ int viewportPosition) = 0; - virtual bool IsInFullscreen() const = 0; virtual void SetIsFullscreen(const bool fFullscreenEnabled) = 0; diff --git a/src/interactivity/onecore/BgfxEngine.cpp b/src/interactivity/onecore/BgfxEngine.cpp index ce6b903ffd1..a730ae8219e 100644 --- a/src/interactivity/onecore/BgfxEngine.cpp +++ b/src/interactivity/onecore/BgfxEngine.cpp @@ -147,9 +147,10 @@ CATCH_RETURN() CATCH_RETURN() } -[[nodiscard]] HRESULT BgfxEngine::PaintBufferGridLines(GridLineSet const /*lines*/, - COLORREF const /*color*/, - size_t const /*cchLine*/, +[[nodiscard]] HRESULT BgfxEngine::PaintBufferGridLines(const GridLineSet /*lines*/, + const COLORREF /*gridlineColor*/, + const COLORREF /*underlineColor*/, + const size_t /*cchLine*/, const til::point /*coordTarget*/) noexcept { return S_OK; @@ -160,6 +161,11 @@ CATCH_RETURN() return S_OK; } +[[nodiscard]] HRESULT BgfxEngine::PaintSelections(const std::vector& /*rects*/) noexcept +{ + return S_OK; +} + [[nodiscard]] HRESULT BgfxEngine::PaintCursor(const CursorOptions& options) noexcept try { diff --git a/src/interactivity/onecore/BgfxEngine.hpp b/src/interactivity/onecore/BgfxEngine.hpp index 31fa49c8522..c787baba392 100644 --- a/src/interactivity/onecore/BgfxEngine.hpp +++ b/src/interactivity/onecore/BgfxEngine.hpp @@ -51,8 +51,9 @@ namespace Microsoft::Console::Render const til::point coord, const bool trimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, til::point const coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; + [[nodiscard]] HRESULT PaintSelections(const std::vector& rects) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; diff --git a/src/interactivity/onecore/ConsoleWindow.cpp b/src/interactivity/onecore/ConsoleWindow.cpp index f6b7fac3c89..8d8fb9dcdc4 100644 --- a/src/interactivity/onecore/ConsoleWindow.cpp +++ b/src/interactivity/onecore/ConsoleWindow.cpp @@ -11,20 +11,6 @@ using namespace Microsoft::Console::Interactivity::OneCore; using namespace Microsoft::Console::Types; -BOOL ConsoleWindow::EnableBothScrollBars() noexcept -{ - return FALSE; -} - -int ConsoleWindow::UpdateScrollBar(bool /*isVertical*/, - bool /*isAltBuffer*/, - UINT /*pageSize*/, - int /*maxSize*/, - int /*viewportPosition*/) noexcept -{ - return 0; -} - bool ConsoleWindow::IsInFullscreen() const noexcept { return true; diff --git a/src/interactivity/onecore/ConsoleWindow.hpp b/src/interactivity/onecore/ConsoleWindow.hpp index 4ab28d8626d..530aafd13cb 100644 --- a/src/interactivity/onecore/ConsoleWindow.hpp +++ b/src/interactivity/onecore/ConsoleWindow.hpp @@ -24,9 +24,6 @@ namespace Microsoft::Console::Interactivity::OneCore { public: // Inherited via IConsoleWindow - BOOL EnableBothScrollBars() noexcept override; - int UpdateScrollBar(bool isVertical, bool isAltBuffer, UINT pageSize, int maxSize, int viewportPosition) noexcept override; - bool IsInFullscreen() const noexcept override; void SetIsFullscreen(const bool fFullscreenEnabled) noexcept override; void ChangeViewport(const til::inclusive_rect& NewWindow) override; diff --git a/src/interactivity/onecore/precomp.h b/src/interactivity/onecore/precomp.h index 7a19e746439..6883aa0972d 100644 --- a/src/interactivity/onecore/precomp.h +++ b/src/interactivity/onecore/precomp.h @@ -10,13 +10,16 @@ #include #include #include +#define WIN32_NO_STATUS #include +#undef WIN32_NO_STATUS #include "wchar.h" // Extension presence detection #include #define _DDK_INCLUDED +#define NO_WINTERNL_INBOX_BUILD #include "../../host/precomp.h" #else diff --git a/src/interactivity/win32/Clipboard.cpp b/src/interactivity/win32/Clipboard.cpp index be97047404c..c4eb7edf356 100644 --- a/src/interactivity/win32/Clipboard.cpp +++ b/src/interactivity/win32/Clipboard.cpp @@ -52,35 +52,80 @@ contents and writing them to the console's input buffer --*/ void Clipboard::Paste() { - HANDLE ClipboardDataHandle; + const auto clipboard = _openClipboard(ServiceLocator::LocateConsoleWindow()->GetWindowHandle()); + if (!clipboard) + { + LOG_LAST_ERROR(); + return; + } + + // This handles most cases of pasting text as the OS converts most formats to CF_UNICODETEXT automatically. + if (const auto handle = GetClipboardData(CF_UNICODETEXT)) + { + const wil::unique_hglobal_locked lock{ handle }; + const auto str = static_cast(lock.get()); + if (!str) + { + return; + } - // Clear any selection or scrolling that may be active. - Selection::Instance().ClearSelection(); - Scrolling::s_ClearScroll(); + // As per: https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats + // CF_UNICODETEXT: [...] A null character signals the end of the data. + // --> Use wcsnlen() to determine the actual length. + // NOTE: Some applications don't add a trailing null character. This includes past conhost versions. + const auto maxLen = GlobalSize(handle) / sizeof(wchar_t); + StringPaste(str, wcsnlen(str, maxLen)); + } - // Get paste data from clipboard - if (!OpenClipboard(ServiceLocator::LocateConsoleWindow()->GetWindowHandle())) + // We get CF_HDROP when a user copied a file with Ctrl+C in Explorer and pastes that into the terminal (among others). + if (const auto handle = GetClipboardData(CF_HDROP)) { - return; + const wil::unique_hglobal_locked lock{ handle }; + const auto drop = static_cast(lock.get()); + if (!drop) + { + return; + } + + PasteDrop(drop); } +} - ClipboardDataHandle = GetClipboardData(CF_UNICODETEXT); - if (ClipboardDataHandle == nullptr) +void Clipboard::PasteDrop(HDROP drop) +{ + // NOTE: When asking DragQueryFileW for the required capacity it returns a length without trailing \0, + // but then expects a capacity that includes it. If you don't make space for a trailing \0 + // then it will silently (!) cut off the end of the string. A somewhat disappointing API design. + const auto expectedLength = DragQueryFileW(drop, 0, nullptr, 0); + if (expectedLength == 0) { - CloseClipboard(); return; } - auto pwstr = (PWCHAR)GlobalLock(ClipboardDataHandle); - StringPaste(pwstr, (ULONG)GlobalSize(ClipboardDataHandle) / sizeof(WCHAR)); - - // WIP auditing if user is enrolled - static auto DestinationName = _LoadString(ID_CONSOLE_WIP_DESTINATIONNAME); - Microsoft::Console::Internal::EdpPolicy::AuditClipboard(DestinationName); + // If the path contains spaces, we'll wrap it in quotes and so this allocates +2 characters ahead of time. + // We'll first make DragQueryFileW copy its contents in the middle and then check if that contains spaces. + // If it does, only then we'll add the quotes at the start and end. + // This is preferable over calling StringPaste 3x (an alternative, simpler approach), + // because the pasted content should be treated as a single atomic unit by the InputBuffer. + const auto buffer = std::make_unique_for_overwrite(expectedLength + 2); + auto str = buffer.get() + 1; + size_t len = expectedLength; + + const auto actualLength = DragQueryFileW(drop, 0, str, expectedLength + 1); + if (actualLength != expectedLength) + { + return; + } - GlobalUnlock(ClipboardDataHandle); + if (wmemchr(str, L' ', len)) + { + str = buffer.get(); + len += 2; + til::at(str, 0) = L'"'; + til::at(str, len - 1) = L'"'; + } - CloseClipboard(); + StringPaste(str, len); } Clipboard& Clipboard::Instance() @@ -108,6 +153,10 @@ void Clipboard::StringPaste(_In_reads_(cchData) const wchar_t* const pData, try { + // Clear any selection or scrolling that may be active. + Selection::Instance().ClearSelection(); + Scrolling::s_ClearScroll(); + const auto vtInputMode = gci.pInputBuffer->IsInVirtualTerminalInputMode(); const auto bracketedPasteMode = gci.GetBracketedPasteMode(); auto inEvents = TextToKeyEvents(pData, cchData, vtInputMode && bracketedPasteMode); @@ -123,6 +172,52 @@ void Clipboard::StringPaste(_In_reads_(cchData) const wchar_t* const pData, #pragma region Private Methods +wil::unique_close_clipboard_call Clipboard::_openClipboard(HWND hwnd) +{ + bool success = false; + + // OpenClipboard may fail to acquire the internal lock --> retry. + for (DWORD sleep = 10;; sleep *= 2) + { + if (OpenClipboard(hwnd)) + { + success = true; + break; + } + // 10 iterations + if (sleep > 10000) + { + break; + } + Sleep(sleep); + } + + return wil::unique_close_clipboard_call{ success }; +} + +void Clipboard::_copyToClipboard(const UINT format, const void* src, const size_t bytes) +{ + wil::unique_hglobal handle{ THROW_LAST_ERROR_IF_NULL(GlobalAlloc(GMEM_MOVEABLE, bytes)) }; + + const auto locked = GlobalLock(handle.get()); + memcpy(locked, src, bytes); + GlobalUnlock(handle.get()); + + THROW_LAST_ERROR_IF_NULL(SetClipboardData(format, handle.get())); + handle.release(); +} + +void Clipboard::_copyToClipboardRegisteredFormat(const wchar_t* format, const void* src, size_t bytes) +{ + const auto id = RegisterClipboardFormatW(format); + if (!id) + { + LOG_LAST_ERROR(); + return; + } + _copyToClipboard(id, src, bytes); +} + // Routine Description: // - converts a wchar_t* into a series of KeyEvents as if it was typed // from the keyboard @@ -214,6 +309,9 @@ InputEventQueue Clipboard::TextToKeyEvents(_In_reads_(cchData) const wchar_t* co // void Clipboard::StoreSelectionToClipboard(const bool copyFormatting) { + std::wstring text; + std::string htmlData, rtfData; + const auto& selection = Selection::Instance(); // See if there is a selection to get @@ -230,122 +328,52 @@ void Clipboard::StoreSelectionToClipboard(const bool copyFormatting) const auto& renderSettings = gci.GetRenderSettings(); const auto GetAttributeColors = [&](const auto& attr) { - return renderSettings.GetAttributeColors(attr); + const auto [fg, bg] = renderSettings.GetAttributeColors(attr); + const auto ul = renderSettings.GetAttributeUnderlineColor(attr); + return std::tuple{ fg, bg, ul }; }; - bool includeCRLF, trimTrailingWhitespace; + bool singleLine = false; if (WI_IsFlagSet(OneCoreSafeGetKeyState(VK_SHIFT), KEY_PRESSED)) { // When shift is held, put everything in one line - includeCRLF = trimTrailingWhitespace = false; - } - else - { - includeCRLF = trimTrailingWhitespace = true; + singleLine = true; } - const auto text = buffer.GetText(includeCRLF, - trimTrailingWhitespace, - selectionRects, - GetAttributeColors, - !selection.IsLineSelection()); + const auto& [selectionStart, selectionEnd] = selection.GetSelectionAnchors(); - CopyTextToSystemClipboard(text, copyFormatting); -} - -// Routine Description: -// - Copies the text given onto the global system clipboard. -// Arguments: -// - rows - Rows of text data to copy -// - fAlsoCopyFormatting - true if the color and formatting should also be copied, false otherwise -void Clipboard::CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, const bool fAlsoCopyFormatting) -{ - std::wstring finalString; + const auto req = TextBuffer::CopyRequest::FromConfig(buffer, selectionStart, selectionEnd, singleLine, !selection.IsLineSelection(), false); + text = buffer.GetPlainText(req); - // Concatenate strings into one giant string to put onto the clipboard. - for (const auto& str : rows.text) + if (copyFormatting) { - finalString += str; + const auto& fontData = gci.GetActiveOutputBuffer().GetCurrentFont(); + const auto& fontName = fontData.GetFaceName(); + const auto fontSizePt = fontData.GetUnscaledSize().height * 72 / ServiceLocator::LocateGlobals().dpi; + const auto bgColor = renderSettings.GetAttributeColors({}).second; + const auto isIntenseBold = renderSettings.GetRenderMode(::Microsoft::Console::Render::RenderSettings::Mode::IntenseIsBold); + + htmlData = buffer.GenHTML(req, fontSizePt, fontName, bgColor, isIntenseBold, GetAttributeColors); + rtfData = buffer.GenRTF(req, fontSizePt, fontName, bgColor, isIntenseBold, GetAttributeColors); } - // allocate the final clipboard data - const auto cchNeeded = finalString.size() + 1; - const auto cbNeeded = sizeof(wchar_t) * cchNeeded; - wil::unique_hglobal globalHandle(GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, cbNeeded)); - THROW_LAST_ERROR_IF_NULL(globalHandle.get()); - - auto pwszClipboard = (PWSTR)GlobalLock(globalHandle.get()); - THROW_LAST_ERROR_IF_NULL(pwszClipboard); - - // The pattern gets a bit strange here because there's no good wil built-in for global lock of this type. - // Try to copy then immediately unlock. Don't throw until after (so the hglobal won't be freed until we unlock). - const auto hr = StringCchCopyW(pwszClipboard, cchNeeded, finalString.data()); - GlobalUnlock(globalHandle.get()); - THROW_IF_FAILED(hr); - - // Set global data to clipboard - THROW_LAST_ERROR_IF(!OpenClipboard(ServiceLocator::LocateConsoleWindow()->GetWindowHandle())); - - { // Clipboard Scope - auto clipboardCloser = wil::scope_exit([]() { - THROW_LAST_ERROR_IF(!CloseClipboard()); - }); - - THROW_LAST_ERROR_IF(!EmptyClipboard()); - THROW_LAST_ERROR_IF_NULL(SetClipboardData(CF_UNICODETEXT, globalHandle.get())); - - if (fAlsoCopyFormatting) - { - const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto& fontData = gci.GetActiveOutputBuffer().GetCurrentFont(); - const auto iFontHeightPoints = fontData.GetUnscaledSize().height * 72 / ServiceLocator::LocateGlobals().dpi; - const auto bgColor = gci.GetRenderSettings().GetAttributeColors({}).second; - - auto HTMLToPlaceOnClip = TextBuffer::GenHTML(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor); - CopyToSystemClipboard(HTMLToPlaceOnClip, L"HTML Format"); - - auto RTFToPlaceOnClip = TextBuffer::GenRTF(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor); - CopyToSystemClipboard(RTFToPlaceOnClip, L"Rich Text Format"); - } + const auto clipboard = _openClipboard(ServiceLocator::LocateConsoleWindow()->GetWindowHandle()); + if (!clipboard) + { + LOG_LAST_ERROR(); + return; } - // only free if we failed. - // the memory has to remain allocated if we successfully placed it on the clipboard. - // Releasing the smart pointer will leave it allocated as we exit scope. - globalHandle.release(); -} + EmptyClipboard(); + // As per: https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats + // CF_UNICODETEXT: [...] A null character signals the end of the data. + // --> We add +1 to the length. This works because .c_str() is null-terminated. + _copyToClipboard(CF_UNICODETEXT, text.c_str(), (text.size() + 1) * sizeof(wchar_t)); -// Routine Description: -// - Copies the given string onto the global system clipboard in the specified format -// Arguments: -// - stringToCopy - The string to copy -// - lpszFormat - the name of the format -void Clipboard::CopyToSystemClipboard(std::string stringToCopy, LPCWSTR lpszFormat) -{ - const auto cbData = stringToCopy.size() + 1; // +1 for '\0' - if (cbData) + if (copyFormatting) { - wil::unique_hglobal globalHandleData(GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, cbData)); - THROW_LAST_ERROR_IF_NULL(globalHandleData.get()); - - auto pszClipboardHTML = (PSTR)GlobalLock(globalHandleData.get()); - THROW_LAST_ERROR_IF_NULL(pszClipboardHTML); - - // The pattern gets a bit strange here because there's no good wil built-in for global lock of this type. - // Try to copy then immediately unlock. Don't throw until after (so the hglobal won't be freed until we unlock). - const auto hr2 = StringCchCopyA(pszClipboardHTML, cbData, stringToCopy.data()); - GlobalUnlock(globalHandleData.get()); - THROW_IF_FAILED(hr2); - - const auto CF_FORMAT = RegisterClipboardFormatW(lpszFormat); - THROW_LAST_ERROR_IF(0 == CF_FORMAT); - - THROW_LAST_ERROR_IF_NULL(SetClipboardData(CF_FORMAT, globalHandleData.get())); - - // only free if we failed. - // the memory has to remain allocated if we successfully placed it on the clipboard. - // Releasing the smart pointer will leave it allocated as we exit scope. - globalHandleData.release(); + _copyToClipboardRegisteredFormat(L"HTML Format", htmlData.data(), htmlData.size()); + _copyToClipboardRegisteredFormat(L"Rich Text Format", rtfData.data(), rtfData.size()); } } diff --git a/src/interactivity/win32/clipboard.hpp b/src/interactivity/win32/clipboard.hpp index f6bab9254cf..09990ab5146 100644 --- a/src/interactivity/win32/clipboard.hpp +++ b/src/interactivity/win32/clipboard.hpp @@ -30,20 +30,21 @@ namespace Microsoft::Console::Interactivity::Win32 static Clipboard& Instance(); void Copy(_In_ const bool fAlsoCopyFormatting = false); - void StringPaste(_In_reads_(cchData) PCWCHAR pwchData, - const size_t cchData); void Paste(); + void PasteDrop(HDROP drop); private: + static wil::unique_close_clipboard_call _openClipboard(HWND hwnd); + static void _copyToClipboard(UINT format, const void* src, size_t bytes); + static void _copyToClipboardRegisteredFormat(const wchar_t* format, const void* src, size_t bytes); + + void StringPaste(_In_reads_(cchData) PCWCHAR pwchData, const size_t cchData); InputEventQueue TextToKeyEvents(_In_reads_(cchData) const wchar_t* const pData, const size_t cchData, const bool bracketedPaste = false); void StoreSelectionToClipboard(_In_ const bool fAlsoCopyFormatting); - void CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, _In_ const bool copyFormatting); - void CopyToSystemClipboard(std::string stringToPlaceOnClip, LPCWSTR lpszFormat); - bool FilterCharacterOnPaste(_Inout_ WCHAR* const pwch); #ifdef UNIT_TESTING diff --git a/src/interactivity/win32/consoleKeyInfo.cpp b/src/interactivity/win32/consoleKeyInfo.cpp index 5aa0f18a85a..87a6231c58e 100644 --- a/src/interactivity/win32/consoleKeyInfo.cpp +++ b/src/interactivity/win32/consoleKeyInfo.cpp @@ -42,7 +42,7 @@ void StoreKeyInfo(_In_ PMSG msg) } else { - RIPMSG0(RIP_WARNING, "ConsoleKeyInfo buffer is full"); + LOG_HR_MSG(E_FAIL, "ConsoleKeyInfo buffer is full"); } } diff --git a/src/interactivity/win32/menu.cpp b/src/interactivity/win32/menu.cpp index 7b59887daaf..44e803931ff 100644 --- a/src/interactivity/win32/menu.cpp +++ b/src/interactivity/win32/menu.cpp @@ -14,7 +14,6 @@ #include "../../host/misc.h" #include "../../host/server.h" #include "../../host/scrolling.hpp" -#include "../../host/telemetry.hpp" #include "../inc/ServiceLocator.hpp" diff --git a/src/interactivity/win32/resource.h b/src/interactivity/win32/resource.h index f453557218c..9b82b787ec0 100644 --- a/src/interactivity/win32/resource.h +++ b/src/interactivity/win32/resource.h @@ -25,7 +25,6 @@ Author(s): #define ID_CONSOLE_MSGMARKMODE 0x100C #define ID_CONSOLE_MSGSCROLLMODE 0x100D #define ID_CONSOLE_FMT_WINDOWTITLE 0x100E -#define ID_CONSOLE_WIP_DESTINATIONNAME 0x100F // Menu Item strings #define ID_CONSOLE_COPY 0xFFF0 diff --git a/src/interactivity/win32/ut_interactivity_win32/sources b/src/interactivity/win32/ut_interactivity_win32/sources index 4b988d16720..1f417647e44 100644 --- a/src/interactivity/win32/ut_interactivity_win32/sources +++ b/src/interactivity/win32/ut_interactivity_win32/sources @@ -51,7 +51,6 @@ TARGETLIBS = \ $(ONECOREUAP_EXTERNAL_SDK_LIB_PATH)\d3dcompiler.lib \ $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\api-ms-win-mm-playsound-l1.lib \ $(ONECORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-dwmapi-ext-l1.lib \ - $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-edputil-policy-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-create-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-draw-l1.lib \ @@ -110,12 +109,12 @@ DELAYLOAD = \ DXGI.dll; \ D3D11.dll; \ OLEAUT32.dll; \ + icu.dll; \ api-ms-win-mm-playsound-l1.dll; \ api-ms-win-shcore-scaling-l1.dll; \ api-ms-win-shell-dataobject-l1.dll; \ api-ms-win-shell-namespace-l1.dll; \ ext-ms-win-dwmapi-ext-l1.dll; \ - ext-ms-win-edputil-policy-l1.dll; \ ext-ms-win-gdi-dc-l1.dll; \ ext-ms-win-gdi-dc-create-l1.dll; \ ext-ms-win-gdi-draw-l1.dll; \ diff --git a/src/interactivity/win32/window.cpp b/src/interactivity/win32/window.cpp index 5b0dde18949..03eed072c13 100644 --- a/src/interactivity/win32/window.cpp +++ b/src/interactivity/win32/window.cpp @@ -20,7 +20,6 @@ #include "../../host/scrolling.hpp" #include "../../host/srvinit.h" #include "../../host/stream.h" -#include "../../host/telemetry.hpp" #include "../../host/tracing.hpp" #include "../../renderer/base/renderer.hpp" @@ -325,7 +324,7 @@ void Window::_UpdateSystemMetrics() const if (hWnd == nullptr) { const auto gle = GetLastError(); - RIPMSG1(RIP_WARNING, "CreateWindow failed with gle = 0x%x", gle); + LOG_WIN32_MSG(gle, "CreateWindow failed"); status = NTSTATUS_FROM_WIN32(gle); } @@ -438,7 +437,7 @@ void Window::_CloseWindow() const ShowWindow(hWnd, wShowWindow); auto& siAttached = GetScreenInfo(); - siAttached.InternalUpdateScrollBars(); + siAttached.UpdateScrollBars(); } return status; @@ -591,7 +590,7 @@ void Window::_UpdateWindowSize(const til::size sizeNew) if (WI_IsFlagClear(gci.Flags, CONSOLE_IS_ICONIC)) { - ScreenInfo.InternalUpdateScrollBars(); + ScreenInfo.UpdateScrollBars(); SetWindowPos(GetWindowHandle(), nullptr, @@ -621,7 +620,7 @@ void Window::_UpdateWindowSize(const til::size sizeNew) if (!IsInFullscreen() && !IsInMaximized()) { // Figure out how big to make the window, given the desired client area size. - siAttached.ResizingWindow++; + _resizingWindow++; // First get the buffer viewport size const auto WindowDimensions = siAttached.GetViewport().Dimensions(); @@ -691,7 +690,7 @@ void Window::_UpdateWindowSize(const til::size sizeNew) // If the change wasn't substantial, we may still need to update scrollbar positions. Note that PSReadLine // scrolls the window via Console.SetWindowPosition, which ultimately calls down to SetConsoleWindowInfo, // which ends up in this function. - siAttached.InternalUpdateScrollBars(); + siAttached.UpdateScrollBars(); } // MSFT: 12092729 @@ -716,7 +715,7 @@ void Window::_UpdateWindowSize(const til::size sizeNew) // an additional Buffer message with the same size again and do nothing special. ScreenBufferSizeChange(siAttached.GetActiveBuffer().GetBufferSize().Dimensions()); - siAttached.ResizingWindow--; + _resizingWindow--; } LOG_IF_FAILED(ConsoleImeResizeCompStrView()); @@ -738,8 +737,6 @@ void Window::VerticalScroll(const WORD wScrollCommand, const WORD wAbsoluteChang auto& ScreenInfo = GetScreenInfo(); // Log a telemetry event saying the user interacted with the Console - Telemetry::Instance().SetUserInteractive(); - const auto& viewport = ScreenInfo.GetViewport(); NewOrigin = viewport.Origin(); @@ -818,8 +815,6 @@ void Window::VerticalScroll(const WORD wScrollCommand, const WORD wAbsoluteChang void Window::HorizontalScroll(const WORD wScrollCommand, const WORD wAbsoluteChange) { // Log a telemetry event saying the user interacted with the Console - Telemetry::Instance().SetUserInteractive(); - auto& ScreenInfo = GetScreenInfo(); const auto sScreenBufferSizeX = ScreenInfo.GetBufferSize().Width(); const auto& viewport = ScreenInfo.GetViewport(); @@ -879,26 +874,29 @@ void Window::HorizontalScroll(const WORD wScrollCommand, const WORD wAbsoluteCha LOG_IF_FAILED(ScreenInfo.SetViewportOrigin(true, NewOrigin, false)); } -BOOL Window::EnableBothScrollBars() +void Window::UpdateScrollBars(const SCREEN_INFORMATION::ScrollBarState& state) { - return EnableScrollBar(_hWnd, SB_BOTH, ESB_ENABLE_BOTH); -} + // If this is the main buffer, make sure we enable both of the scroll bars. + // The alt buffer likely disabled the scroll bars, this is the only way to re-enable it. + if (!state.isAltBuffer) + { + EnableScrollBar(_hWnd, SB_BOTH, ESB_ENABLE_BOTH); + } -int Window::UpdateScrollBar(bool isVertical, - bool isAltBuffer, - UINT pageSize, - int maxSize, - int viewportPosition) -{ - SCROLLINFO si; - si.cbSize = sizeof(si); - si.fMask = isAltBuffer ? SIF_ALL | SIF_DISABLENOSCROLL : SIF_ALL; - si.nPage = pageSize; - si.nMin = 0; - si.nMax = maxSize; - si.nPos = viewportPosition; - - return SetScrollInfo(_hWnd, isVertical ? SB_VERT : SB_HORZ, &si, TRUE); + SCROLLINFO si{ + .cbSize = sizeof(SCROLLINFO), + .fMask = static_cast(state.isAltBuffer ? SIF_ALL | SIF_DISABLENOSCROLL : SIF_ALL), + }; + + si.nMax = state.maxSize.width; + si.nPage = state.viewport.width(); + si.nPos = state.viewport.left; + SetScrollInfo(_hWnd, SB_HORZ, &si, TRUE); + + si.nMax = state.maxSize.height; + si.nPage = state.viewport.height(); + si.nPos = state.viewport.top; + SetScrollInfo(_hWnd, SB_VERT, &si, TRUE); } // Routine Description: diff --git a/src/interactivity/win32/window.hpp b/src/interactivity/win32/window.hpp index 45db4e7fbe6..7550b18c9d8 100644 --- a/src/interactivity/win32/window.hpp +++ b/src/interactivity/win32/window.hpp @@ -65,12 +65,7 @@ namespace Microsoft::Console::Interactivity::Win32 void HorizontalScroll(const WORD wScrollCommand, const WORD wAbsoluteChange); - BOOL EnableBothScrollBars(); - int UpdateScrollBar(bool isVertical, - bool isAltBuffer, - UINT pageSize, - int maxSize, - int viewportPosition); + void UpdateScrollBars(const SCREEN_INFORMATION::ScrollBarState& state); void UpdateWindowSize(const til::size coordSizeInChars); void UpdateWindowPosition(_In_ const til::point ptNewPos) const; @@ -185,6 +180,7 @@ namespace Microsoft::Console::Interactivity::Win32 static void s_ReinitializeFontsForDPIChange(); + WORD _resizingWindow = 0; // > 0 if we should ignore WM_SIZE messages bool _fInDPIChange = false; static void s_ConvertWindowPosToWindowRect(const LPWINDOWPOS lpWindowPos, diff --git a/src/interactivity/win32/windowio.cpp b/src/interactivity/win32/windowio.cpp index 5866400a730..94d7f3b2a86 100644 --- a/src/interactivity/win32/windowio.cpp +++ b/src/interactivity/win32/windowio.cpp @@ -135,16 +135,6 @@ void HandleKeyEvent(const HWND hWnd, const BOOL bKeyDown = WI_IsFlagClear(lParam, KEY_TRANSITION_UP); const bool IsCharacterMessage = (Message == WM_CHAR || Message == WM_SYSCHAR || Message == WM_DEADCHAR || Message == WM_SYSDEADCHAR); - if (bKeyDown) - { - // Log a telemetry flag saying the user interacted with the Console - // Only log when the key is a down press. Otherwise we're getting many calls with - // Message = WM_CHAR, VirtualKeyCode = VK_TAB, with bKeyDown = false - // when nothing is happening, or the user has merely clicked on the title bar, and - // this can incorrectly mark the session as being interactive. - Telemetry::Instance().SetUserInteractive(); - } - // Make sure we retrieve the key info first, or we could chew up // unneeded space in the key info table if we bail out early. if (IsCharacterMessage) @@ -213,38 +203,6 @@ void HandleKeyEvent(const HWND hWnd, const INPUT_KEY_INFO inputKeyInfo(VirtualKeyCode, ControlKeyState); - // Capture telemetry on Ctrl+Shift+ C or V commands - if (IsInProcessedInputMode()) - { - // Capture telemetry data when a user presses ctrl+shift+c or v in processed mode - if (inputKeyInfo.IsShiftAndCtrlOnly()) - { - if (VirtualKeyCode == 'V') - { - Telemetry::Instance().LogCtrlShiftVProcUsed(); - } - else if (VirtualKeyCode == 'C') - { - Telemetry::Instance().LogCtrlShiftCProcUsed(); - } - } - } - else - { - // Capture telemetry data when a user presses ctrl+shift+c or v in raw mode - if (inputKeyInfo.IsShiftAndCtrlOnly()) - { - if (VirtualKeyCode == 'V') - { - Telemetry::Instance().LogCtrlShiftVRawUsed(); - } - else if (VirtualKeyCode == 'C') - { - Telemetry::Instance().LogCtrlShiftCRawUsed(); - } - } - } - // If this is a key up message, should we ignore it? We do this so that if a process reads a line from the input // buffer, the key up event won't get put in the buffer after the read completes. if (gci.Flags & CONSOLE_IGNORE_NEXT_KEYUP) @@ -265,7 +223,6 @@ void HandleKeyEvent(const HWND hWnd, { case 'V': // the user is attempting to paste from the clipboard - Telemetry::Instance().SetKeyboardTextEditingUsed(); Clipboard::Instance().Paste(); return; } @@ -278,8 +235,6 @@ void HandleKeyEvent(const HWND hWnd, switch (VirtualKeyCode) { case 'A': - // Set Text Selection using keyboard to true for telemetry - Telemetry::Instance().SetKeyboardTextSelectionUsed(); // the user is asking to select all pSelection->SelectAll(); return; @@ -293,8 +248,6 @@ void HandleKeyEvent(const HWND hWnd, Selection::Instance().InitializeMarkSelection(); return; case 'V': - // the user is attempting to paste from the clipboard - Telemetry::Instance().SetKeyboardTextEditingUsed(); Clipboard::Instance().Paste(); return; case VK_HOME: @@ -307,10 +260,6 @@ void HandleKeyEvent(const HWND hWnd, return; } break; - case VK_PRIOR: - case VK_NEXT: - Telemetry::Instance().SetCtrlPgUpPgDnUsed(); - break; } } @@ -469,9 +418,6 @@ BOOL HandleSysKeyEvent(const HWND hWnd, const UINT Message, const WPARAM wParam, VirtualKeyCode = LOWORD(wParam); } - // Log a telemetry flag saying the user interacted with the Console - Telemetry::Instance().SetUserInteractive(); - // check for ctrl-esc const auto bCtrlDown = OneCoreSafeGetKeyState(VK_CONTROL) & KEY_PRESSED; @@ -568,12 +514,6 @@ BOOL HandleMouseEvent(const SCREEN_INFORMATION& ScreenInfo, const LPARAM lParam) { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (Message != WM_MOUSEMOVE) - { - // Log a telemetry flag saying the user interacted with the Console - Telemetry::Instance().SetUserInteractive(); - } - const auto pSelection = &Selection::Instance(); if (!(gci.Flags & CONSOLE_HAS_FOCUS) && !pSelection->IsMouseButtonDown()) @@ -804,31 +744,12 @@ BOOL HandleMouseEvent(const SCREEN_INFORMATION& ScreenInfo, { if (pSelection->IsInSelectingState()) { - // Capture data on when quick edit copy is used in proc or raw mode - if (IsInProcessedInputMode()) - { - Telemetry::Instance().LogQuickEditCopyProcUsed(); - } - else - { - Telemetry::Instance().LogQuickEditCopyRawUsed(); - } // If the ALT key is held, also select HTML as well as plain text. const auto fAlsoCopyFormatting = WI_IsFlagSet(OneCoreSafeGetKeyState(VK_MENU), KEY_PRESSED); Clipboard::Instance().Copy(fAlsoCopyFormatting); } else if (gci.Flags & CONSOLE_QUICK_EDIT_MODE) { - // Capture data on when quick edit paste is used in proc or raw mode - if (IsInProcessedInputMode()) - { - Telemetry::Instance().LogQuickEditPasteProcUsed(); - } - else - { - Telemetry::Instance().LogQuickEditPasteRawUsed(); - } - Clipboard::Instance().Paste(); } gci.Flags |= CONSOLE_IGNORE_NEXT_MOUSE_INPUT; @@ -911,7 +832,7 @@ BOOL HandleMouseEvent(const SCREEN_INFORMATION& ScreenInfo, EventFlags = MOUSE_HWHEELED; break; default: - RIPMSG1(RIP_ERROR, "Invalid message 0x%x", Message); + LOG_HR_MSG(E_INVALIDARG, "Invalid message 0x%x", Message); ButtonFlags = 0; EventFlags = 0; break; @@ -968,7 +889,7 @@ NTSTATUS InitWindowsSubsystem(_Out_ HHOOK* phhook) if (FAILED_NTSTATUS(Status)) { - RIPMSG2(RIP_WARNING, "CreateWindowsWindow failed with status 0x%x, gle = 0x%x", Status, GetLastError()); + LOG_NTSTATUS_MSG(Status, "CreateWindowsWindow failed"); return Status; } diff --git a/src/interactivity/win32/windowproc.cpp b/src/interactivity/win32/windowproc.cpp index 376e93fe031..3e2225e182f 100644 --- a/src/interactivity/win32/windowproc.cpp +++ b/src/interactivity/win32/windowproc.cpp @@ -163,12 +163,6 @@ using namespace Microsoft::Console::Types; case WM_SIZING: { - // Signal that the user changed the window size, so we can return the value later for telemetry. By only - // sending the data back if the size has changed, helps reduce the amount of telemetry being sent back. - // WM_SIZING doesn't fire if they resize the window using Win-UpArrow, so we'll miss that scenario. We could - // listen to the WM_SIZE message instead, but they can fire when the window is being restored from being - // minimized, and not only when they resize the window. - Telemetry::Instance().SetWindowSizeChanged(); goto CallDefWin; break; } @@ -322,11 +316,6 @@ using namespace Microsoft::Console::Types; case WM_CLOSE: { - // Write the final trace log during the WM_CLOSE message while the console process is still fully alive. - // This gives us time to query the process for information. We shouldn't really miss any useful - // telemetry between now and when the process terminates. - Telemetry::Instance().WriteFinalTraceLog(); - _CloseWindow(); break; } @@ -471,7 +460,6 @@ using namespace Microsoft::Console::Types; case WM_CONTEXTMENU: { - Telemetry::Instance().SetContextMenuUsed(); if (DefWindowProcW(hWnd, WM_NCHITTEST, 0, lParam) == HTCLIENT) { auto hHeirMenu = Menu::s_GetHeirMenuHandle(); @@ -681,7 +669,16 @@ using namespace Microsoft::Console::Types; case CM_UPDATE_SCROLL_BARS: { - ScreenInfo.InternalUpdateScrollBars(); + const auto state = ScreenInfo.FetchScrollBarState(); + + // EnableScrollbar() and especially SetScrollInfo() are prohibitively expensive functions nowadays. + // Unlocking early here improves throughput of good old `type` in cmd.exe by ~10x. + UnlockConsole(); + Unlock = FALSE; + + _resizingWindow++; + UpdateScrollBars(state); + _resizingWindow--; break; } @@ -792,7 +789,7 @@ void Window::_HandleWindowPosChanged(const LPARAM lParam) // CONSOLE_IS_ICONIC bit appropriately. doing so in the WM_SIZE handler is incorrect because the WM_SIZE // comes after the WM_ERASEBKGND during SetWindowPos() processing, and the WM_ERASEBKGND needs to know if // the console window is iconic or not. - if (!ScreenInfo.ResizingWindow && (lpWindowPos->cx || lpWindowPos->cy) && !IsIconic(hWnd)) + if (!_resizingWindow && (lpWindowPos->cx || lpWindowPos->cy) && !IsIconic(hWnd)) { // calculate the dimensions for the newly proposed window rectangle til::rect rcNew; @@ -860,30 +857,7 @@ void Window::_HandleWindowPosChanged(const LPARAM lParam) // - void Window::_HandleDrop(const WPARAM wParam) const { - WCHAR szPath[MAX_PATH]; - BOOL fAddQuotes; - - if (DragQueryFile((HDROP)wParam, 0, szPath, ARRAYSIZE(szPath)) != 0) - { - // Log a telemetry flag saying the user interacted with the Console - // Only log when DragQueryFile succeeds, because if we don't when the console starts up, we're seeing - // _HandleDrop get called multiple times (and DragQueryFile fail), - // which can incorrectly mark this console session as interactive. - Telemetry::Instance().SetUserInteractive(); - - fAddQuotes = (wcschr(szPath, L' ') != nullptr); - if (fAddQuotes) - { - Clipboard::Instance().StringPaste(L"\"", 1); - } - - Clipboard::Instance().StringPaste(szPath, wcslen(szPath)); - - if (fAddQuotes) - { - Clipboard::Instance().StringPaste(L"\"", 1); - } - } + Clipboard::Instance().PasteDrop((HDROP)wParam); } [[nodiscard]] LRESULT Window::_HandleGetObject(const HWND hwnd, const WPARAM wParam, const LPARAM lParam) diff --git a/src/internal/stubs.cpp b/src/internal/stubs.cpp index f0cf811b017..f92f4096969 100644 --- a/src/internal/stubs.cpp +++ b/src/internal/stubs.cpp @@ -21,26 +21,7 @@ using namespace Microsoft::Console::Internal; return S_OK; } -void EdpPolicy::AuditClipboard(const std::wstring_view /*destinationName*/) noexcept -{ -} - [[nodiscard]] HRESULT Theming::TrySetDarkMode(HWND /*hwnd*/) noexcept { return S_FALSE; } - -[[nodiscard]] bool DefaultApp::CheckDefaultAppPolicy() noexcept -{ - // True so propsheet will show configuration options but be sure that - // the open one won't attempt handoff from double click of OpenConsole.exe - return true; -} - -[[nodiscard]] bool DefaultApp::CheckShouldTerminalBeDefault() noexcept -{ - // False since setting Terminal as the default app is an OS feature and probably - // should not be done in the open source conhost. We can always decide to turn it - // on in the future though. - return false; -} diff --git a/src/project.inc b/src/project.inc index b34fb10f2e3..0b7115efcf0 100644 --- a/src/project.inc +++ b/src/project.inc @@ -35,7 +35,7 @@ USE_NATIVE_EH = 1 USE_STD_CPP20 = 1 MSC_WARNING_LEVEL = /W4 /WX -USER_C_FLAGS = $(USER_C_FLAGS) /fp:contract /utf-8 +USER_C_FLAGS = $(USER_C_FLAGS) /Zc:preprocessor /fp:contract /utf-8 # ------------------------------------- # Common Console Includes and Libraries diff --git a/src/propsheet/OptionsPage.cpp b/src/propsheet/OptionsPage.cpp index a7991565f27..eee80476fe9 100644 --- a/src/propsheet/OptionsPage.cpp +++ b/src/propsheet/OptionsPage.cpp @@ -174,6 +174,23 @@ INT_PTR WINAPI SettingsDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lPara // Initialize the global handle to this dialog g_hOptionsDlg = hDlg; + { + // Do the check for conhostv1 early, so that we can propagate the new ForceV2 state to everyone. + wil::unique_hmodule conhostV1{ LoadLibraryExW(L"conhostv1.dll", nullptr, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_SEARCH_SYSTEM32) }; + HWND hwndItemToShow, hwndItemToHide; + hwndItemToShow = GetDlgItem(hDlg, IDD_HELP_LEGACY_LINK); + hwndItemToHide = GetDlgItem(hDlg, IDD_HELP_LEGACY_LINK_MISSING); + if (!conhostV1) + { + g_fForceV2 = true; + EnableWindow(GetDlgItem(hDlg, IDD_FORCEV2), FALSE); + std::swap(hwndItemToShow, hwndItemToHide); + } + + ShowWindow(hwndItemToShow, SW_SHOW); + ShowWindow(hwndItemToHide, SW_HIDE); + } + CheckDlgButton(hDlg, IDD_HISTORY_NODUP, gpStateInfo->HistoryNoDup); CheckDlgButton(hDlg, IDD_QUICKEDIT, gpStateInfo->QuickEdit); CheckDlgButton(hDlg, IDD_INSERT, gpStateInfo->InsertMode); @@ -250,7 +267,7 @@ INT_PTR WINAPI SettingsDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lPara case WM_NOTIFY: { - if (lParam && (wParam == IDD_HELP_SYSLINK || wParam == IDD_HELP_LEGACY_LINK)) + if (lParam && (wParam == IDD_HELP_SYSLINK || wParam == IDD_HELP_LEGACY_LINK || wParam == IDD_HELP_LEGACY_LINK_MISSING)) { // handle hyperlink click or keyboard activation switch (((LPNMHDR)lParam)->code) diff --git a/src/propsheet/console.cpp b/src/propsheet/console.cpp index 68b146c2e11..d6058440590 100644 --- a/src/propsheet/console.cpp +++ b/src/propsheet/console.cpp @@ -96,10 +96,7 @@ void SaveConsoleSettingsIfNeeded(const HWND hwnd) gpStateInfo->FaceName[0] = TEXT('\0'); } - if (Microsoft::Console::Internal::DefaultApp::CheckDefaultAppPolicy()) - { - LOG_IF_FAILED(DelegationConfig::s_SetDefaultByPackage(g_selectedPackage)); - } + LOG_IF_FAILED(DelegationConfig::s_SetDefaultByPackage(g_selectedPackage)); if (gpStateInfo->LinkTitle != nullptr) { @@ -552,14 +549,7 @@ BOOL PopulatePropSheetPageArray(_Out_writes_(cPsps) PROPSHEETPAGE* pPsp, const s { pTerminalPage->dwSize = sizeof(PROPSHEETPAGE); pTerminalPage->hInstance = ghInstance; - if (Microsoft::Console::Internal::DefaultApp::CheckDefaultAppPolicy()) - { - pTerminalPage->pszTemplate = MAKEINTRESOURCE(DID_TERMINAL_WITH_DEFTERM); - } - else - { - pTerminalPage->pszTemplate = MAKEINTRESOURCE(DID_TERMINAL); - } + pTerminalPage->pszTemplate = MAKEINTRESOURCE(DID_TERMINAL_WITH_DEFTERM); pTerminalPage->pfnDlgProc = TerminalDlgProc; pTerminalPage->lParam = TERMINAL_PAGE_INDEX; pTerminalPage->dwFlags = PSP_DEFAULT; @@ -629,10 +619,7 @@ INT_PTR ConsolePropertySheet(__in HWND hWnd, __in PCONSOLE_STATE_INFO pStateInfo // Find the available default console/terminal packages // - if (Microsoft::Console::Internal::DefaultApp::CheckDefaultAppPolicy()) - { - LOG_IF_FAILED(DelegationConfig::s_GetAvailablePackages(g_availablePackages, g_selectedPackage)); - } + LOG_IF_FAILED(DelegationConfig::s_GetAvailablePackages(g_availablePackages, g_selectedPackage)); // // Get the current page number diff --git a/src/propsheet/console.rc b/src/propsheet/console.rc index 18f3077d490..368144c9fd8 100644 --- a/src/propsheet/console.rc +++ b/src/propsheet/console.rc @@ -85,6 +85,9 @@ BEGIN CONTROL "&Use legacy console (requires relaunch, affects all consoles)", IDD_FORCEV2, "Button", BS_AUTOCHECKBOX | WS_GROUP | WS_TABSTOP, 10, 199, 200, 10 + CONTROL "The legacy console is not installed. Learn more.", + IDD_HELP_LEGACY_LINK_MISSING, "SysLink", WS_TABSTOP, 21, 211, 179, 10 + CONTROL "Learn more about legacy console mode", IDD_HELP_LEGACY_LINK, "SysLink", WS_TABSTOP, 21, 211, 179, 10 @@ -148,6 +151,9 @@ BEGIN CONTROL "&Use legacy console (requires relaunch, affects all consoles)", IDD_FORCEV2, "Button", BS_AUTOCHECKBOX | WS_GROUP | WS_TABSTOP, 10, 199, 200, 10 + CONTROL "The legacy console is not installed. Learn more.", + IDD_HELP_LEGACY_LINK_MISSING, "SysLink", WS_TABSTOP, 21, 211, 179, 10 + CONTROL "Learn more about legacy console mode", IDD_HELP_LEGACY_LINK, "SysLink", WS_TABSTOP, 21, 211, 179, 10 diff --git a/src/propsheet/dialogs.h b/src/propsheet/dialogs.h index 9200f9090ef..41b5ec8fe6c 100644 --- a/src/propsheet/dialogs.h +++ b/src/propsheet/dialogs.h @@ -129,6 +129,7 @@ Revision History: #define IDD_OPACITY_VALUE 514 #define IDD_INTERCEPT_COPY_PASTE 515 #define IDD_HELP_LEGACY_LINK 516 +#define IDD_HELP_LEGACY_LINK_MISSING 517 #define DID_TERMINAL 600 diff --git a/src/propsheet/sources b/src/propsheet/sources index 648dd1d1c12..033d3a5396d 100644 --- a/src/propsheet/sources +++ b/src/propsheet/sources @@ -84,7 +84,6 @@ TARGETLIBS = \ $(ONECORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\onecoreuap_internal.lib \ $(ONECOREUAP_INTERNAL_SDK_LIB_PATH)\onecoreuapuuid.lib \ $(ONECORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-dwmapi-ext-l1.lib \ - $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-edputil-policy-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-create-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-draw-l1.lib \ @@ -109,7 +108,6 @@ TARGETLIBS = \ DELAYLOAD = \ ext-ms-win-dwmapi-ext-l1.dll; \ - ext-ms-win-edputil-policy-l1.dll; \ ext-ms-win-uxtheme-themes-l1.dll; \ ext-ms-win-shell32-shellfolders-l1.dll; \ ext-ms-win-gdi-dc-l1.dll; \ diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp index 4459c97a8e5..0e2f7941688 100644 --- a/src/renderer/atlas/AtlasEngine.api.cpp +++ b/src/renderer/atlas/AtlasEngine.api.cpp @@ -455,30 +455,34 @@ void AtlasEngine::SetWarningCallback(std::function pfn) noexcept [[nodiscard]] HRESULT AtlasEngine::UpdateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map& features, const std::unordered_map& axes) noexcept { - static constexpr std::array fallbackFaceNames{ static_cast(nullptr), L"Consolas", L"Lucida Console", L"Courier New" }; - auto it = fallbackFaceNames.begin(); - const auto end = fallbackFaceNames.end(); + try + { + _updateFont(fontInfoDesired.GetFaceName().c_str(), fontInfoDesired, fontInfo, features, axes); + return S_OK; + } + CATCH_LOG(); - for (;;) + if constexpr (Feature_NearbyFontLoading::IsEnabled()) { try { - _updateFont(*it, fontInfoDesired, fontInfo, features, axes); + // _resolveFontMetrics() checks `_api.s->font->fontCollection` for a pre-existing font collection, + // before falling back to using the system font collection. This way we can inject our custom one. See GH#9375. + // Doing it this way is a bit hacky, but it does have the benefit that we can cache a font collection + // instance across font changes, like when zooming the font size rapidly using the scroll wheel. + _api.s.write()->font.write()->fontCollection = FontCache::GetCached(); + _updateFont(fontInfoDesired.GetFaceName().c_str(), fontInfoDesired, fontInfo, features, axes); return S_OK; } - catch (...) - { - ++it; - if (it == end) - { - RETURN_CAUGHT_EXCEPTION(); - } - else - { - LOG_CAUGHT_EXCEPTION(); - } - } + CATCH_LOG(); } + + try + { + _updateFont(nullptr, fontInfoDesired, fontInfo, features, axes); + return S_OK; + } + CATCH_RETURN(); } void AtlasEngine::UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept @@ -490,20 +494,20 @@ void AtlasEngine::UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept void AtlasEngine::_resolveTransparencySettings() noexcept { + // An opaque background allows us to use true "independent" flips. See AtlasEngine::_createSwapChain(). + // We can't enable them if custom shaders are specified, because it's unknown, whether they support opaque inputs. + const bool useAlpha = _api.enableTransparentBackground || !_api.s->misc->customPixelShaderPath.empty(); // If the user asks for ClearType, but also for a transparent background // (which our ClearType shader doesn't simultaneously support) // then we need to sneakily force the renderer to grayscale AA. - const auto antialiasingMode = _api.enableTransparentBackground && _api.antialiasingMode == AntialiasingMode::ClearType ? AntialiasingMode::Grayscale : _api.antialiasingMode; - const bool enableTransparentBackground = _api.enableTransparentBackground || !_api.s->misc->customPixelShaderPath.empty() || _api.s->misc->useRetroTerminalEffect; + const auto antialiasingMode = useAlpha && _api.antialiasingMode == AntialiasingMode::ClearType ? AntialiasingMode::Grayscale : _api.antialiasingMode; - if (antialiasingMode != _api.s->font->antialiasingMode || enableTransparentBackground != _api.s->target->enableTransparentBackground) + if (antialiasingMode != _api.s->font->antialiasingMode || useAlpha != _api.s->target->useAlpha) { const auto s = _api.s.write(); s->font.write()->antialiasingMode = antialiasingMode; - // An opaque background allows us to use true "independent" flips. See AtlasEngine::_createSwapChain(). - // We can't enable them if custom shaders are specified, because it's unknown, whether they support opaque inputs. - s->target.write()->enableTransparentBackground = enableTransparentBackground; - _api.backgroundOpaqueMixin = enableTransparentBackground ? 0x00000000 : 0xff000000; + s->target.write()->useAlpha = useAlpha; + _api.backgroundOpaqueMixin = useAlpha ? 0x00000000 : 0xff000000; } } @@ -598,11 +602,7 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo if (!requestedFaceName) { - requestedFaceName = fontInfoDesired.GetFaceName().c_str(); - if (!requestedFaceName) - { - requestedFaceName = L"Consolas"; - } + requestedFaceName = L"Consolas"; } if (!requestedSize.height) { @@ -614,22 +614,19 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo requestedWeight = DWRITE_FONT_WEIGHT_NORMAL; } - wil::com_ptr fontCollection; - THROW_IF_FAILED(_p.dwriteFactory->GetSystemFontCollection(fontCollection.addressof(), FALSE)); + // UpdateFont() (and its NearbyFontLoading feature path specifically) sets `_api.s->font->fontCollection` + // to a custom font collection that includes .ttf files that are bundled with our app package. See GH#9375. + // Doing it this way is a bit hacky, but it does have the benefit that we can cache a font collection + // instance across font changes, like when zooming the font size rapidly using the scroll wheel. + auto fontCollection = _api.s->font->fontCollection; + if (!fontCollection) + { + THROW_IF_FAILED(_p.dwriteFactory->GetSystemFontCollection(fontCollection.addressof(), FALSE)); + } u32 index = 0; BOOL exists = false; THROW_IF_FAILED(fontCollection->FindFamilyName(requestedFaceName, &index, &exists)); - - if constexpr (Feature_NearbyFontLoading::IsEnabled()) - { - if (!exists) - { - fontCollection = FontCache::GetCached(); - THROW_IF_FAILED(fontCollection->FindFamilyName(requestedFaceName, &index, &exists)); - } - } - THROW_HR_IF(DWRITE_E_NOFONT, !exists); wil::com_ptr fontFamily; diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index e46bb250835..93821394ce1 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -375,7 +375,7 @@ try } CATCH_RETURN() -[[nodiscard]] HRESULT AtlasEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const til::point coordTarget) noexcept +[[nodiscard]] HRESULT AtlasEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept try { const auto shift = gsl::narrow_cast(_api.lineRendition != LineRendition::SingleWidth); @@ -383,8 +383,9 @@ try const auto y = gsl::narrow_cast(clamp(coordTarget.y, 0, _p.s->viewportCellCount.y)); const auto from = gsl::narrow_cast(clamp(x << shift, 0, _p.s->viewportCellCount.x - 1)); const auto to = gsl::narrow_cast(clamp((x + cchLine) << shift, from, _p.s->viewportCellCount.x)); - const auto fg = gsl::narrow_cast(color) | 0xff000000; - _p.rows[y]->gridLineRanges.emplace_back(lines, fg, from, to); + const auto glColor = gsl::narrow_cast(gridlineColor) | 0xff000000; + const auto ulColor = gsl::narrow_cast(underlineColor) | 0xff000000; + _p.rows[y]->gridLineRanges.emplace_back(lines, glColor, ulColor, from, to); return S_OK; } CATCH_RETURN() @@ -413,6 +414,40 @@ try } CATCH_RETURN() +[[nodiscard]] HRESULT AtlasEngine::PaintSelections(const std::vector& rects) noexcept +try +{ + if (rects.empty()) + { + return S_OK; + } + + for (const auto& rect : rects) + { + const auto y = gsl::narrow_cast(clamp(rect.top, 0, _p.s->viewportCellCount.y)); + const auto from = gsl::narrow_cast(clamp(rect.left, 0, _p.s->viewportCellCount.x - 1)); + const auto to = gsl::narrow_cast(clamp(rect.right, from, _p.s->viewportCellCount.x)); + + if (rect.bottom <= 0 || rect.top >= _p.s->viewportCellCount.y) + { + continue; + } + + const auto bg = &_p.backgroundBitmap[_p.colorBitmapRowStride * y]; + const auto fg = &_p.foregroundBitmap[_p.colorBitmapRowStride * y]; + std::fill(bg + from, bg + to, 0xff3296ff); + std::fill(fg + from, fg + to, 0xff000000); + } + + for (int i = 0; i < 2; ++i) + { + _p.colorBitmapGenerations[i].bump(); + } + + return S_OK; +} +CATCH_RETURN() + [[nodiscard]] HRESULT AtlasEngine::PaintCursor(const CursorOptions& options) noexcept try { diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h index b135a8989f8..8d6d76795c0 100644 --- a/src/renderer/atlas/AtlasEngine.h +++ b/src/renderer/atlas/AtlasEngine.h @@ -43,8 +43,9 @@ namespace Microsoft::Console::Render::Atlas [[nodiscard]] HRESULT PrepareLineTransform(LineRendition lineRendition, til::CoordType targetRow, til::CoordType viewportLeft) noexcept override; [[nodiscard]] HRESULT PaintBackground() noexcept override; [[nodiscard]] HRESULT PaintBufferLine(std::span clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, til::point coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; + [[nodiscard]] HRESULT PaintSelections(const std::vector& rects) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; [[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const RenderSettings& renderSettings, gsl::not_null pData, bool usingSoftFont, bool isSettingDefaultBrushes) noexcept override; [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept override; diff --git a/src/renderer/atlas/AtlasEngine.r.cpp b/src/renderer/atlas/AtlasEngine.r.cpp index e7070b708e6..92649553245 100644 --- a/src/renderer/atlas/AtlasEngine.r.cpp +++ b/src/renderer/atlas/AtlasEngine.r.cpp @@ -217,7 +217,7 @@ void AtlasEngine::_recreateBackend() if (hr == DXGI_ERROR_SDK_COMPONENT_MISSING) { // This might happen if you don't have "Graphics debugger and GPU - // profiler for DirectX" installed in VS. We shouln't just explode if + // profiler for DirectX" installed in VS. We shouldn't just explode if // you don't though - instead, disable debugging and try again. WI_ClearFlag(deviceFlags, D3D11_CREATE_DEVICE_DEBUG); @@ -329,7 +329,7 @@ void AtlasEngine::_createSwapChain() .SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, // If our background is opaque we can enable "independent" flips by setting DXGI_ALPHA_MODE_IGNORE. // As our swap chain won't have to compose with DWM anymore it reduces the display latency dramatically. - .AlphaMode = _p.s->target->enableTransparentBackground ? DXGI_ALPHA_MODE_PREMULTIPLIED : DXGI_ALPHA_MODE_IGNORE, + .AlphaMode = _p.s->target->useAlpha ? DXGI_ALPHA_MODE_PREMULTIPLIED : DXGI_ALPHA_MODE_IGNORE, .Flags = swapChainFlags, }; @@ -360,6 +360,8 @@ void AtlasEngine::_createSwapChain() _p.swapChain.targetSize = _p.s->targetSize; _p.swapChain.waitForPresentation = true; + LOG_IF_FAILED(_p.swapChain.swapChain->SetMaximumFrameLatency(1)); + WaitUntilCanRender(); if (_p.swapChainChangedCallback) diff --git a/src/renderer/atlas/BackendD2D.cpp b/src/renderer/atlas/BackendD2D.cpp index 301738954aa..05f79c78c20 100644 --- a/src/renderer/atlas/BackendD2D.cpp +++ b/src/renderer/atlas/BackendD2D.cpp @@ -332,7 +332,7 @@ void BackendD2D::_drawTextResetLineRendition(const ShapedRow* row) const noexcep } } -// Returns the theoretical/design design size of the given `DWRITE_GLYPH_RUN`, relative the the given baseline origin. +// Returns the theoretical/design design size of the given `DWRITE_GLYPH_RUN`, relative the given baseline origin. // This algorithm replicates what DirectWrite does internally to provide `IDWriteTextLayout::GetMetrics`. f32r BackendD2D::_getGlyphRunDesignBounds(const DWRITE_GLYPH_RUN& glyphRun, f32 baselineX, f32 baselineY) { @@ -409,7 +409,7 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro D2D1_POINT_2F point0{ 0, static_cast(textCellCenter) }; D2D1_POINT_2F point1{ 0, static_cast(textCellCenter + cellSize.y) }; - const auto brush = _brushWithColor(r.color); + const auto brush = _brushWithColor(r.gridlineColor); const f32 w = pos.height; const f32 hw = w * 0.5f; @@ -421,11 +421,11 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro _renderTarget->DrawLine(point0, point1, brush, w, nullptr); } }; - const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ID2D1StrokeStyle* strokeStyle) { + const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ID2D1StrokeStyle* strokeStyle, const u32 color) { const auto from = r.from >> widthShift; const auto to = r.to >> widthShift; - const auto brush = _brushWithColor(r.color); + const auto brush = _brushWithColor(color); const f32 w = pos.height; const f32 centerY = textCellCenter + pos.position + w * 0.5f; const D2D1_POINT_2F point0{ static_cast(from * cellSize.x), centerY }; @@ -448,32 +448,32 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro } if (r.lines.test(GridLines::Top)) { - appendHorizontalLine(r, p.s->font->gridTop, nullptr); + appendHorizontalLine(r, p.s->font->gridTop, nullptr, r.gridlineColor); } if (r.lines.test(GridLines::Bottom)) { - appendHorizontalLine(r, p.s->font->gridBottom, nullptr); + appendHorizontalLine(r, p.s->font->gridBottom, nullptr, r.gridlineColor); + } + if (r.lines.test(GridLines::Strikethrough)) + { + appendHorizontalLine(r, p.s->font->strikethrough, nullptr, r.gridlineColor); } if (r.lines.test(GridLines::Underline)) { - appendHorizontalLine(r, p.s->font->underline, nullptr); + appendHorizontalLine(r, p.s->font->underline, nullptr, r.underlineColor); } - if (r.lines.test(GridLines::HyperlinkUnderline)) + else if (r.lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) { - appendHorizontalLine(r, p.s->font->underline, _dottedStrokeStyle.get()); + appendHorizontalLine(r, p.s->font->underline, _dottedStrokeStyle.get(), r.underlineColor); } - if (r.lines.test(GridLines::DoubleUnderline)) + else if (r.lines.test(GridLines::DoubleUnderline)) { for (const auto pos : p.s->font->doubleUnderline) { - appendHorizontalLine(r, pos, nullptr); + appendHorizontalLine(r, pos, nullptr, r.underlineColor); } } - if (r.lines.test(GridLines::Strikethrough)) - { - appendHorizontalLine(r, p.s->font->strikethrough, nullptr); - } } } diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 846567ca613..69d597ed0bd 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -90,7 +90,8 @@ BackendD3D::BackendD3D(const RenderingPayload& p) { static constexpr D3D11_INPUT_ELEMENT_DESC layout[]{ { "SV_Position", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, - { "shadingType", 0, DXGI_FORMAT_R32_UINT, 1, offsetof(QuadInstance, shadingType), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, + { "shadingType", 0, DXGI_FORMAT_R16_UINT, 1, offsetof(QuadInstance, shadingType), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, + { "renditionScale", 0, DXGI_FORMAT_R8G8_UINT, 1, offsetof(QuadInstance, renditionScale), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, { "position", 0, DXGI_FORMAT_R16G16_SINT, 1, offsetof(QuadInstance, position), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, { "size", 0, DXGI_FORMAT_R16G16_UINT, 1, offsetof(QuadInstance, size), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, { "texcoord", 0, DXGI_FORMAT_R16G16_UINT, 1, offsetof(QuadInstance, texcoord), D3D11_INPUT_PER_INSTANCE_DATA, 1 }, @@ -305,6 +306,35 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p) { const auto& font = *p.s->font; + // Curlyline is drawn with a desired height relative to the font size. The + // baseline of curlyline is at the middle of singly underline. When there's + // limited space to draw a curlyline, we apply a limit on the peak height. + { + // initialize curlyline peak height to a desired value. Clamp it to at + // least 1. + constexpr auto curlyLinePeakHeightEm = 0.075f; + _curlyLinePeakHeight = std::max(1.0f, std::roundf(curlyLinePeakHeightEm * font.fontSize)); + + // calc the limit we need to apply + const auto strokeHalfWidth = std::floor(font.underline.height / 2.0f); + const auto underlineMidY = font.underline.position + strokeHalfWidth; + const auto maxDrawableCurlyLinePeakHeight = font.cellSize.y - underlineMidY - font.underline.height; + + // if the limit is <= 0 (no height at all), stick with the desired height. + // This is how we force a curlyline even when there's no space, though it + // might be clipped at the bottom. + if (maxDrawableCurlyLinePeakHeight > 0.0f) + { + _curlyLinePeakHeight = std::min(_curlyLinePeakHeight, maxDrawableCurlyLinePeakHeight); + } + + const auto curlyUnderlinePos = underlineMidY - _curlyLinePeakHeight - font.underline.height; + const auto curlyUnderlineWidth = 2.0f * (_curlyLinePeakHeight + font.underline.height); + const auto curlyUnderlinePosU16 = gsl::narrow_cast(lrintf(curlyUnderlinePos)); + const auto curlyUnderlineWidthU16 = gsl::narrow_cast(lrintf(curlyUnderlineWidth)); + _curlyUnderline = { curlyUnderlinePosU16, curlyUnderlineWidthU16 }; + } + DWrite_GetRenderParams(p.dwriteFactory.get(), &_gamma, &_cleartypeEnhancedContrast, &_grayscaleEnhancedContrast, _textRenderingParams.put()); // Clearing the atlas requires BeginDraw(), which is expensive. Defer this until we need Direct2D anyways. _fontChangedResetGlyphAtlas = true; @@ -403,23 +433,24 @@ void BackendD3D::_recreateCustomShader(const RenderingPayload& p) /* ppCode */ blob.addressof(), /* ppErrorMsgs */ error.addressof()); - // Unless we can determine otherwise, assume this shader requires evaluation every frame - _requiresContinuousRedraw = true; - if (SUCCEEDED(hr)) { - THROW_IF_FAILED(p.device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, _customPixelShader.put())); + THROW_IF_FAILED(p.device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, _customPixelShader.addressof())); // Try to determine whether the shader uses the Time variable wil::com_ptr reflector; - if (SUCCEEDED_LOG(D3DReflect(blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(reflector.put())))) + if (SUCCEEDED_LOG(D3DReflect(blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(reflector.addressof())))) { + // Depending on the version of the d3dcompiler_*.dll, the next two functions either return nullptr + // on failure or an instance of CInvalidSRConstantBuffer or CInvalidSRVariable respectively, + // which cause GetDesc() to return E_FAIL. In other words, we have to assume that any failure in the + // next few lines indicates that the cbuffer is entirely unused (--> _requiresContinuousRedraw=false). if (ID3D11ShaderReflectionConstantBuffer* constantBufferReflector = reflector->GetConstantBufferByIndex(0)) // shader buffer { if (ID3D11ShaderReflectionVariable* variableReflector = constantBufferReflector->GetVariableByIndex(0)) // time { D3D11_SHADER_VARIABLE_DESC variableDescriptor; - if (SUCCEEDED_LOG(variableReflector->GetDesc(&variableDescriptor))) + if (SUCCEEDED(variableReflector->GetDesc(&variableDescriptor))) { // only if time is used _requiresContinuousRedraw = WI_IsFlagSet(variableDescriptor.uFlags, D3D_SVF_USED); @@ -427,12 +458,17 @@ void BackendD3D::_recreateCustomShader(const RenderingPayload& p) } } } + else + { + // Unless we can determine otherwise, assume this shader requires evaluation every frame + _requiresContinuousRedraw = true; + } } else { if (error) { - LOG_HR_MSG(hr, "%*hs", error->GetBufferSize(), error->GetBufferPointer()); + LOG_HR_MSG(hr, "%.*hs", static_cast(error->GetBufferSize()), static_cast(error->GetBufferPointer())); } else { @@ -447,8 +483,6 @@ void BackendD3D::_recreateCustomShader(const RenderingPayload& p) else if (p.s->misc->useRetroTerminalEffect) { THROW_IF_FAILED(p.device->CreatePixelShader(&custom_shader_ps[0], sizeof(custom_shader_ps), nullptr, _customPixelShader.put())); - // We know the built-in retro shader doesn't require continuous redraw. - _requiresContinuousRedraw = false; } if (_customPixelShader) @@ -539,6 +573,9 @@ void BackendD3D::_recreateConstBuffer(const RenderingPayload& p) const DWrite_GetGammaRatios(_gamma, data.gammaRatios); data.enhancedContrast = p.s->font->antialiasingMode == AntialiasingMode::ClearType ? _cleartypeEnhancedContrast : _grayscaleEnhancedContrast; data.underlineWidth = p.s->font->underline.height; + data.curlyLineWaveFreq = 2.0f * 3.14f / p.s->font->cellSize.x; + data.curlyLinePeakHeight = _curlyLinePeakHeight; + data.curlyLineCellOffset = p.s->font->underline.position + p.s->font->underline.height / 2.0f; p.deviceContext->UpdateSubresource(_psConstantBuffer.get(), 0, nullptr, &data, 0, 0); } } @@ -1020,7 +1057,7 @@ void BackendD3D::_drawText(RenderingPayload& p) goto drawGlyphRetry; } - if (glyphEntry.data.GetShadingType() != ShadingType::Default) + if (glyphEntry.data.shadingType != ShadingType::Default) { auto l = static_cast(lrintf((baselineX + row->glyphOffsets[x].advanceOffset) * scaleX)); auto t = static_cast(lrintf((baselineY - row->glyphOffsets[x].ascenderOffset) * scaleY)); @@ -1032,7 +1069,7 @@ void BackendD3D::_drawText(RenderingPayload& p) row->dirtyBottom = std::max(row->dirtyBottom, t + glyphEntry.data.size.y); _appendQuad() = { - .shadingType = glyphEntry.data.GetShadingType(), + .shadingType = glyphEntry.data.shadingType, .position = { static_cast(l), static_cast(t) }, .size = glyphEntry.data.size, .texcoord = glyphEntry.data.texcoord, @@ -1454,7 +1491,7 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryI const auto triggerRight = _ligatureOverhangTriggerRight * horizontalScale; const auto overlapSplit = rect.w >= p.s->font->cellSize.x && (bl <= triggerLeft || br >= triggerRight); - glyphEntry.data.shadingType = static_cast(isColorGlyph ? ShadingType::TextPassthrough : _textShadingType); + glyphEntry.data.shadingType = isColorGlyph ? ShadingType::TextPassthrough : _textShadingType; glyphEntry.data.overlapSplit = overlapSplit; glyphEntry.data.offset.x = bl; glyphEntry.data.offset.y = bt; @@ -1523,7 +1560,7 @@ bool BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const AtlasFontFa _drawSoftFontGlyphInBitmap(p, glyphEntry); _d2dRenderTarget->DrawBitmap(_softFontBitmap.get(), &dest, 1, interpolation, nullptr, nullptr); - glyphEntry.data.shadingType = static_cast(ShadingType::TextGrayscale); + glyphEntry.data.shadingType = ShadingType::TextGrayscale; glyphEntry.data.overlapSplit = 0; glyphEntry.data.offset.x = 0; glyphEntry.data.offset.y = -baseline; @@ -1627,11 +1664,11 @@ void BackendD3D::_splitDoubleHeightGlyph(const RenderingPayload& p, const AtlasF // double-height row. This effectively turns the other (unneeded) side into whitespace. if (!top.data.size.y) { - top.data.shadingType = static_cast(ShadingType::Default); + top.data.shadingType = ShadingType::Default; } if (!bottom.data.size.y) { - bottom.data.shadingType = static_cast(ShadingType::Default); + bottom.data.shadingType = ShadingType::Default; } } @@ -1643,8 +1680,6 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) const auto verticalShift = static_cast(row->lineRendition >= LineRendition::DoubleHeightTop); const auto cellSize = p.s->font->cellSize; - const auto dottedLineType = horizontalShift ? ShadingType::DottedLineWide : ShadingType::DottedLine; - const auto rowTop = static_cast(cellSize.y * y); const auto rowBottom = static_cast(rowTop + cellSize.y); @@ -1671,11 +1706,11 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) .shadingType = ShadingType::SolidLine, .position = { static_cast(posX), rowTop }, .size = { width, p.s->font->cellSize.y }, - .color = r.color, + .color = r.gridlineColor, }; } }; - const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ShadingType shadingType) { + const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ShadingType shadingType, const u32 color) { const auto offset = pos.position << verticalShift; const auto height = static_cast(pos.height << verticalShift); @@ -1691,9 +1726,10 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) { _appendQuad() = { .shadingType = shadingType, + .renditionScale = { static_cast(1 << horizontalShift), static_cast(1 << verticalShift) }, .position = { left, static_cast(rt) }, .size = { width, static_cast(rb - rt) }, - .color = r.color, + .color = color, }; } }; @@ -1713,32 +1749,40 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) } if (r.lines.test(GridLines::Top)) { - appendHorizontalLine(r, p.s->font->gridTop, ShadingType::SolidLine); + appendHorizontalLine(r, p.s->font->gridTop, ShadingType::SolidLine, r.gridlineColor); } if (r.lines.test(GridLines::Bottom)) { - appendHorizontalLine(r, p.s->font->gridBottom, ShadingType::SolidLine); + appendHorizontalLine(r, p.s->font->gridBottom, ShadingType::SolidLine, r.gridlineColor); + } + if (r.lines.test(GridLines::Strikethrough)) + { + appendHorizontalLine(r, p.s->font->strikethrough, ShadingType::SolidLine, r.gridlineColor); } if (r.lines.test(GridLines::Underline)) { - appendHorizontalLine(r, p.s->font->underline, ShadingType::SolidLine); + appendHorizontalLine(r, p.s->font->underline, ShadingType::SolidLine, r.underlineColor); + } + else if (r.lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) + { + appendHorizontalLine(r, p.s->font->underline, ShadingType::DottedLine, r.underlineColor); } - if (r.lines.test(GridLines::HyperlinkUnderline)) + else if (r.lines.test(GridLines::DashedUnderline)) { - appendHorizontalLine(r, p.s->font->underline, dottedLineType); + appendHorizontalLine(r, p.s->font->underline, ShadingType::DashedLine, r.underlineColor); } - if (r.lines.test(GridLines::DoubleUnderline)) + else if (r.lines.test(GridLines::CurlyUnderline)) + { + appendHorizontalLine(r, _curlyUnderline, ShadingType::CurlyLine, r.underlineColor); + } + else if (r.lines.test(GridLines::DoubleUnderline)) { for (const auto pos : p.s->font->doubleUnderline) { - appendHorizontalLine(r, pos, ShadingType::SolidLine); + appendHorizontalLine(r, pos, ShadingType::SolidLine, r.underlineColor); } } - if (r.lines.test(GridLines::Strikethrough)) - { - appendHorizontalLine(r, p.s->font->strikethrough, ShadingType::SolidLine); - } } } @@ -1886,7 +1930,7 @@ void BackendD3D::_drawCursorForeground() } } // We can also skip any instances (= any rows) at the beginning that are clearly not overlapping with - // the cursor. This reduces the the CPU cost of this function by roughly half (a few microseconds). + // the cursor. This reduces the CPU cost of this function by roughly half (a few microseconds). for (; instancesOffset < instancesCount; ++instancesOffset) { const auto& it = _instances[instancesOffset]; @@ -2038,6 +2082,8 @@ size_t BackendD3D::_drawCursorForegroundSlowPath(const CursorRect& c, size_t off auto& target = _instances[offset + i]; target.shadingType = it.shadingType; + target.renditionScale.x = it.renditionScale.x; + target.renditionScale.y = it.renditionScale.y; target.position.x = static_cast(cutout.left); target.position.y = static_cast(cutout.top); target.size.x = static_cast(cutout.right - cutout.left); @@ -2055,6 +2101,8 @@ size_t BackendD3D::_drawCursorForegroundSlowPath(const CursorRect& c, size_t off auto& target = cutoutCount ? _appendQuad() : _instances[offset]; target.shadingType = it.shadingType; + target.renditionScale.x = it.renditionScale.x; + target.renditionScale.y = it.renditionScale.y; target.position.x = static_cast(intersectionL); target.position.y = static_cast(intersectionT); target.size.x = static_cast(intersectionR - intersectionL); diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index 14f4ca2943e..4c248ed3ff3 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -42,6 +42,9 @@ namespace Microsoft::Console::Render::Atlas alignas(sizeof(f32x4)) f32 gammaRatios[4]{}; alignas(sizeof(f32)) f32 enhancedContrast = 0; alignas(sizeof(f32)) f32 underlineWidth = 0; + alignas(sizeof(f32)) f32 curlyLinePeakHeight = 0; + alignas(sizeof(f32)) f32 curlyLineWaveFreq = 0; + alignas(sizeof(f32)) f32 curlyLineCellOffset = 0; #pragma warning(suppress : 4324) // 'PSConstBuffer': structure was padded due to alignment specifier }; @@ -55,7 +58,7 @@ namespace Microsoft::Console::Render::Atlas #pragma warning(suppress : 4324) // 'CustomConstBuffer': structure was padded due to alignment specifier }; - enum class ShadingType : u32 + enum class ShadingType : u16 { Default = 0, Background = 0, @@ -66,12 +69,13 @@ namespace Microsoft::Console::Render::Atlas TextClearType = 2, TextPassthrough = 3, DottedLine = 4, - DottedLineWide = 5, + DashedLine = 5, + CurlyLine = 6, // All items starting here will be drawing as a solid RGBA color - SolidLine = 6, + SolidLine = 7, - Cursor = 7, - Selection = 8, + Cursor = 8, + Selection = 9, TextDrawingFirst = TextGrayscale, TextDrawingLast = SolidLine, @@ -86,7 +90,8 @@ namespace Microsoft::Console::Render::Atlas // impact on performance and power draw. If (when?) displays with >32k resolution make their // appearance in the future, this should be changed to f32x2. But if you do so, please change // all other occurrences of i16x2 positions/offsets throughout the class to keep it consistent. - alignas(u32) ShadingType shadingType; + alignas(u16) ShadingType shadingType; + alignas(u16) u8x2 renditionScale; alignas(u32) i16x2 position; alignas(u32) u16x2 size; alignas(u32) u16x2 texcoord; @@ -95,16 +100,11 @@ namespace Microsoft::Console::Render::Atlas struct alignas(u32) AtlasGlyphEntryData { - u16 shadingType; + ShadingType shadingType; u16 overlapSplit; i16x2 offset; u16x2 size; u16x2 texcoord; - - constexpr ShadingType GetShadingType() const noexcept - { - return static_cast(shadingType); - } }; // NOTE: Don't initialize any members in this struct. This ensures that no @@ -291,6 +291,9 @@ namespace Microsoft::Console::Render::Atlas // The bounding rect of _cursorRects in pixels. til::rect _cursorPosition; + f32 _curlyLinePeakHeight = 0.0f; + FontDecorationPosition _curlyUnderline; + bool _requiresContinuousRedraw = false; #if ATLAS_DEBUG_SHOW_DIRTY diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index bb46ff61547..014f5487118 100644 --- a/src/renderer/atlas/common.h +++ b/src/renderer/atlas/common.h @@ -125,6 +125,7 @@ namespace Microsoft::Console::Render::Atlas }; using u8 = uint8_t; + using u8x2 = vec2; using u16 = uint16_t; using u16x2 = vec2; @@ -313,7 +314,7 @@ namespace Microsoft::Console::Render::Atlas struct TargetSettings { HWND hwnd = nullptr; - bool enableTransparentBackground = false; + bool useAlpha = false; bool useSoftwareRendering = false; }; @@ -426,7 +427,8 @@ namespace Microsoft::Console::Render::Atlas struct GridLineRange { GridLineSet lines; - u32 color = 0; + u32 gridlineColor = 0; + u32 underlineColor = 0; u16 from = 0; u16 to = 0; }; @@ -470,7 +472,6 @@ namespace Microsoft::Console::Render::Atlas wil::com_ptr systemFontFallback; wil::com_ptr systemFontFallback1; // optional, might be nullptr wil::com_ptr textAnalyzer; - wil::com_ptr renderingParams; std::function warningCallback; std::function swapChainChangedCallback; diff --git a/src/renderer/atlas/shader_common.hlsl b/src/renderer/atlas/shader_common.hlsl index 957b17ac6ad..d712f081f28 100644 --- a/src/renderer/atlas/shader_common.hlsl +++ b/src/renderer/atlas/shader_common.hlsl @@ -7,13 +7,15 @@ #define SHADING_TYPE_TEXT_CLEARTYPE 2 #define SHADING_TYPE_TEXT_PASSTHROUGH 3 #define SHADING_TYPE_DOTTED_LINE 4 -#define SHADING_TYPE_DOTTED_LINE_WIDE 5 +#define SHADING_TYPE_DASHED_LINE 5 +#define SHADING_TYPE_CURLY_LINE 6 // clang-format on struct VSData { float2 vertex : SV_Position; uint shadingType : shadingType; + uint2 renditionScale : renditionScale; int2 position : position; uint2 size : size; uint2 texcoord : texcoord; @@ -25,6 +27,7 @@ struct PSData float4 position : SV_Position; float2 texcoord : texcoord; nointerpolation uint shadingType : shadingType; + nointerpolation uint2 renditionScale : renditionScale; nointerpolation float4 color : color; }; diff --git a/src/renderer/atlas/shader_ps.hlsl b/src/renderer/atlas/shader_ps.hlsl index 2cb92947ce2..e19ba955fe5 100644 --- a/src/renderer/atlas/shader_ps.hlsl +++ b/src/renderer/atlas/shader_ps.hlsl @@ -12,6 +12,9 @@ cbuffer ConstBuffer : register(b0) float4 gammaRatios; float enhancedContrast; float underlineWidth; + float curlyLinePeakHeight; + float curlyLineWaveFreq; + float curlyLineCellOffset; } Texture2D background : register(t0); @@ -73,18 +76,36 @@ Output main(PSData data) : SV_Target } case SHADING_TYPE_DOTTED_LINE: { - const bool on = frac(data.position.x / (2.0f * underlineWidth)) < 0.5f; + const bool on = frac(data.position.x / (2.0f * underlineWidth * data.renditionScale.x)) < 0.5f; color = on * premultiplyColor(data.color); weights = color.aaaa; break; } - case SHADING_TYPE_DOTTED_LINE_WIDE: + case SHADING_TYPE_DASHED_LINE: { - const bool on = frac(data.position.x / (4.0f * underlineWidth)) < 0.5f; + const bool on = frac(data.position.x / (backgroundCellSize.x * data.renditionScale.x)) < 0.5f; color = on * premultiplyColor(data.color); weights = color.aaaa; break; } + case SHADING_TYPE_CURLY_LINE: + { + uint cellRow = floor(data.position.y / backgroundCellSize.y); + // Use the previous cell when drawing 'Double Height' curly line. + cellRow -= data.renditionScale.y - 1; + const float cellTop = cellRow * backgroundCellSize.y; + const float centerY = cellTop + curlyLineCellOffset * data.renditionScale.y; + const float strokeWidthHalf = underlineWidth * data.renditionScale.y / 2.0f; + const float amp = curlyLinePeakHeight * data.renditionScale.y; + const float freq = curlyLineWaveFreq / data.renditionScale.x; + + const float s = sin(data.position.x * freq); + const float d = abs(centerY - (s * amp) - data.position.y); + const float a = 1 - saturate(d - strokeWidthHalf); + color = a * premultiplyColor(data.color); + weights = color.aaaa; + break; + } default: { color = premultiplyColor(data.color); diff --git a/src/renderer/atlas/shader_vs.hlsl b/src/renderer/atlas/shader_vs.hlsl index 49b9030b156..eb96fcf0e45 100644 --- a/src/renderer/atlas/shader_vs.hlsl +++ b/src/renderer/atlas/shader_vs.hlsl @@ -15,6 +15,7 @@ PSData main(VSData data) PSData output; output.color = data.color; output.shadingType = data.shadingType; + output.renditionScale = data.renditionScale; // positionScale is expected to be float2(2.0f / sizeInPixel.x, -2.0f / sizeInPixel.y). Together with the // addition below this will transform our "position" from pixel into normalized device coordinate (NDC) space. output.position.xy = (data.position + data.vertex.xy * data.size) * positionScale + float2(-1.0f, 1.0f); diff --git a/src/renderer/base/RenderSettings.cpp b/src/renderer/base/RenderSettings.cpp index 03c4795635a..f885957796a 100644 --- a/src/renderer/base/RenderSettings.cpp +++ b/src/renderer/base/RenderSettings.cpp @@ -212,6 +212,47 @@ std::pair RenderSettings::GetAttributeColorsWithAlpha(const return { fg, bg }; } +// Routine Description: +// - Calculates the RGB underline color of a given text attribute, using the +// current color table configuration and active render settings. +// - Returns the current foreground color when the underline color isn't set. +// Arguments: +// - attr - The TextAttribute to retrieve the underline color from. +// Return Value: +// - The color value of the attribute's underline. +COLORREF RenderSettings::GetAttributeUnderlineColor(const TextAttribute& attr) const noexcept +{ + const auto [fg, bg] = GetAttributeColors(attr); + const auto ulTextColor = attr.GetUnderlineColor(); + if (ulTextColor.IsDefault()) + { + return fg; + } + + const auto defaultUlIndex = GetColorAliasIndex(ColorAlias::DefaultForeground); + auto ul = ulTextColor.GetColor(_colorTable, defaultUlIndex, true); + if (attr.IsInvisible()) + { + ul = bg; + } + + // We intentionally aren't _only_ checking for attr.IsInvisible here, because we also want to + // catch the cases where the ul was intentionally set to be the same as the bg. In either case, + // don't adjust the underline color. + if constexpr (Feature_AdjustIndistinguishableText::IsEnabled()) + { + if ( + ul != bg && + (_renderMode.test(Mode::AlwaysDistinguishableColors) || + (_renderMode.test(Mode::IndexedDistinguishableColors) && ulTextColor.IsDefaultOrLegacy() && attr.GetBackground().IsDefaultOrLegacy()))) + { + ul = ColorFix::GetPerceivableColor(ul, bg, 0.5f * 0.5f); + } + } + + return ul; +} + // Routine Description: // - Increments the position in the blink cycle, toggling the blink rendition // state on every second call, potentially triggering a redraw of the given diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index 917c5ba05f9..c7c5c491815 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -217,7 +217,7 @@ void Renderer::TriggerSystemRedraw(const til::rect* const prcDirtyClient) // - void Renderer::TriggerRedraw(const Viewport& region) { - auto view = _viewport; + auto view = _pData->GetViewport(); auto srUpdateRegion = region.ToExclusive(); // If the dirty region has double width lines, we need to double the size of @@ -362,6 +362,7 @@ void Renderer::TriggerSelection() { // Get selection rectangles auto rects = _GetSelectionRects(); + auto searchSelections = _GetSearchSelectionRects(); // Make a viewport representing the coordinates that are currently presentable. const til::rect viewport{ _pData->GetViewport().Dimensions() }; @@ -374,11 +375,14 @@ void Renderer::TriggerSelection() FOREACH_ENGINE(pEngine) { + LOG_IF_FAILED(pEngine->InvalidateSelection(_previousSearchSelection)); LOG_IF_FAILED(pEngine->InvalidateSelection(_previousSelection)); + LOG_IF_FAILED(pEngine->InvalidateSelection(searchSelections)); LOG_IF_FAILED(pEngine->InvalidateSelection(rects)); } _previousSelection = std::move(rects); + _previousSearchSelection = std::move(searchSelections); NotifyPaintFrame(); } @@ -955,13 +959,21 @@ GridLineSet Renderer::s_GetGridlines(const TextAttribute& textAttribute) noexcep { case UnderlineStyle::NoUnderline: break; + case UnderlineStyle::SinglyUnderlined: + lines.set(GridLines::Underline); + break; case UnderlineStyle::DoublyUnderlined: lines.set(GridLines::DoubleUnderline); break; - case UnderlineStyle::SinglyUnderlined: case UnderlineStyle::CurlyUnderlined: + lines.set(GridLines::CurlyUnderline); + break; case UnderlineStyle::DottedUnderlined: + lines.set(GridLines::DottedUnderline); + break; case UnderlineStyle::DashedUnderlined: + lines.set(GridLines::DashedUnderline); + break; default: lines.set(GridLines::Underline); break; @@ -1002,10 +1014,11 @@ void Renderer::_PaintBufferOutputGridLineHelper(_In_ IRenderEngine* const pEngin // Return early if there are no lines to paint. if (lines.any()) { - // Get the current foreground color to render the lines. - const auto rgb = _renderSettings.GetAttributeColors(textAttribute).first; + // Get the current foreground and underline colors to render the lines. + const auto fg = _renderSettings.GetAttributeColors(textAttribute).first; + const auto underlineColor = _renderSettings.GetAttributeUnderlineColor(textAttribute); // Draw the lines - LOG_IF_FAILED(pEngine->PaintBufferGridLines(lines, rgb, cchLine, coordTarget)); + LOG_IF_FAILED(pEngine->PaintBufferGridLines(lines, fg, underlineColor, cchLine, coordTarget)); } } @@ -1197,9 +1210,20 @@ void Renderer::_PaintSelection(_In_ IRenderEngine* const pEngine) // Get selection rectangles const auto rectangles = _GetSelectionRects(); - for (const auto& rect : rectangles) + const auto searchRectangles = _GetSearchSelectionRects(); + + std::vector dirtySearchRectangles; + for (auto& dirtyRect : dirtyAreas) { - for (auto& dirtyRect : dirtyAreas) + for (const auto& sr : searchRectangles) + { + if (const auto rectCopy = sr & dirtyRect) + { + dirtySearchRectangles.emplace_back(rectCopy); + } + } + + for (const auto& rect : rectangles) { if (const auto rectCopy = rect & dirtyRect) { @@ -1207,6 +1231,11 @@ void Renderer::_PaintSelection(_In_ IRenderEngine* const pEngine) } } } + + if (!dirtySearchRectangles.empty()) + { + LOG_IF_FAILED(pEngine->PaintSelections(std::move(dirtySearchRectangles))); + } } CATCH_LOG(); } @@ -1272,6 +1301,28 @@ std::vector Renderer::_GetSelectionRects() const return result; } +std::vector Renderer::_GetSearchSelectionRects() const +{ + const auto& buffer = _pData->GetTextBuffer(); + auto rects = _pData->GetSearchSelectionRects(); + // Adjust rectangles to viewport + auto view = _pData->GetViewport(); + + std::vector result; + result.reserve(rects.size()); + + for (auto rect : rects) + { + // Convert buffer offsets to the equivalent range of screen cells + // expected by callers, taking line rendition into account. + const auto lineRendition = buffer.GetLineRendition(rect.Top()); + rect = Viewport::FromInclusive(BufferToScreenLine(rect.ToInclusive(), lineRendition)); + result.emplace_back(view.ConvertToOrigin(rect).ToExclusive()); + } + + return result; +} + // Method Description: // - Offsets all of the selection rectangles we might be holding onto // as the previously selected area. If the whole viewport scrolls, diff --git a/src/renderer/base/renderer.hpp b/src/renderer/base/renderer.hpp index ae3ed2cfda9..1cd61799a8f 100644 --- a/src/renderer/base/renderer.hpp +++ b/src/renderer/base/renderer.hpp @@ -110,6 +110,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT _UpdateDrawingBrushes(_In_ IRenderEngine* const pEngine, const TextAttribute attr, const bool usingSoftFont, const bool isSettingDefaultBrushes); [[nodiscard]] HRESULT _PerformScrolling(_In_ IRenderEngine* const pEngine); std::vector _GetSelectionRects() const; + std::vector _GetSearchSelectionRects() const; void _ScrollPreviousSelection(const til::point delta); [[nodiscard]] HRESULT _PaintTitle(IRenderEngine* const pEngine); bool _isInHoveredInterval(til::point coordTarget) const noexcept; @@ -127,6 +128,7 @@ namespace Microsoft::Console::Render Microsoft::Console::Types::Viewport _viewport; std::vector _clusterBuffer; std::vector _previousSelection; + std::vector _previousSearchSelection; std::function _pfnBackgroundColorChanged; std::function _pfnFrameColorChanged; std::function _pfnRendererEnteredErrorState; diff --git a/src/renderer/dx/DxFontInfo.cpp b/src/renderer/dx/DxFontInfo.cpp index 4945939fd86..b0f79cde39a 100644 --- a/src/renderer/dx/DxFontInfo.cpp +++ b/src/renderer/dx/DxFontInfo.cpp @@ -126,8 +126,12 @@ void DxFontInfo::SetFromEngine(const std::wstring_view familyName, try { face = _FindFontFace(localeName); + } + CATCH_LOG(); - if constexpr (Feature_NearbyFontLoading::IsEnabled()) + if constexpr (Feature_NearbyFontLoading::IsEnabled()) + { + try { if (!face) { @@ -135,37 +139,41 @@ void DxFontInfo::SetFromEngine(const std::wstring_view familyName, face = _FindFontFace(localeName); } } + CATCH_LOG(); + } - if (!face) + if (!face) + { + // If we missed, try looking a little more by trimming the last word off the requested family name a few times. + // Quite often, folks are specifying weights or something in the familyName and it causes failed resolution and + // an unexpected error dialog. We theoretically could detect the weight words and convert them, but this + // is the quick fix for the majority scenario. + // The long/full fix is backlogged to GH#9744 + // Also this doesn't count as a fallback because we don't want to annoy folks with the warning dialog over + // this resolution. + while (!face && !_familyName.empty()) { - // If we missed, try looking a little more by trimming the last word off the requested family name a few times. - // Quite often, folks are specifying weights or something in the familyName and it causes failed resolution and - // an unexpected error dialog. We theoretically could detect the weight words and convert them, but this - // is the quick fix for the majority scenario. - // The long/full fix is backlogged to GH#9744 - // Also this doesn't count as a fallback because we don't want to annoy folks with the warning dialog over - // this resolution. - while (!face && !_familyName.empty()) - { - const auto lastSpace = _familyName.find_last_of(UNICODE_SPACE); + const auto lastSpace = _familyName.find_last_of(UNICODE_SPACE); - // value is unsigned and npos will be greater than size. - // if we didn't find anything to trim, leave. - if (lastSpace >= _familyName.size()) - { - break; - } + // value is unsigned and npos will be greater than size. + // if we didn't find anything to trim, leave. + if (lastSpace >= _familyName.size()) + { + break; + } - // trim string down to just before the found space - // (space found at 6... trim from 0 for 6 length will give us 0-5 as the new string) - _familyName = _familyName.substr(0, lastSpace); + // trim string down to just before the found space + // (space found at 6... trim from 0 for 6 length will give us 0-5 as the new string) + _familyName = _familyName.substr(0, lastSpace); + try + { // Try to find it with the shortened family name face = _FindFontFace(localeName); } + CATCH_LOG(); } } - CATCH_LOG(); // Alright, if our quick shot at trimming didn't work either... // move onto looking up a font from our hard-coded list of fonts @@ -176,7 +184,12 @@ void DxFontInfo::SetFromEngine(const std::wstring_view familyName, { _familyName = fallbackFace; - face = _FindFontFace(localeName); + try + { + face = _FindFontFace(localeName); + } + CATCH_LOG(); + if (face) { _didFallback = true; diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp index f74bf528dc1..e03cfb20081 100644 --- a/src/renderer/dx/DxRenderer.cpp +++ b/src/renderer/dx/DxRenderer.cpp @@ -1696,14 +1696,16 @@ CATCH_RETURN() // - Paints lines around cells (draws in pieces of the grid) // Arguments: // - lines - Which grid lines (top, left, bottom, right) to draw -// - color - The color to use for drawing the lines +// - gridlineColor - The color to use for drawing the gridlines +// - underlineColor - The color to use for drawing the underlines // - cchLine - Length of the line to draw in character cells // - coordTarget - The X,Y character position in the grid where we should start drawing // - We will draw rightward (+X) from here // Return Value: // - S_OK or relevant DirectX error [[nodiscard]] HRESULT DxEngine::PaintBufferGridLines(const GridLineSet lines, - COLORREF const color, + const COLORREF gridlineColor, + const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept try @@ -1711,8 +1713,6 @@ try const auto existingColor = _d2dBrushForeground->GetColor(); const auto restoreBrushOnExit = wil::scope_exit([&]() noexcept { _d2dBrushForeground->SetColor(existingColor); }); - _d2dBrushForeground->SetColor(_ColorFFromColorRef(color | 0xff000000)); - const auto font = _fontRenderData->GlyphCell().to_d2d_size(); const D2D_POINT_2F target = { coordTarget.x * font.width, coordTarget.y * font.height }; const auto fullRunWidth = font.width * gsl::narrow_cast(cchLine); @@ -1721,10 +1721,12 @@ try _d2dDeviceContext->DrawLine({ x0, y0 }, { x1, y1 }, _d2dBrushForeground.Get(), strokeWidth, _strokeStyle.Get()); }; - const auto DrawHyperlinkLine = [=](const auto x0, const auto y0, const auto x1, const auto y1, const auto strokeWidth) noexcept { + const auto DrawDottedLine = [=](const auto x0, const auto y0, const auto x1, const auto y1, const auto strokeWidth) noexcept { _d2dDeviceContext->DrawLine({ x0, y0 }, { x1, y1 }, _d2dBrushForeground.Get(), strokeWidth, _dashStrokeStyle.Get()); }; + _d2dBrushForeground->SetColor(_ColorFFromColorRef(gridlineColor | 0xff000000)); + // NOTE: Line coordinates are centered within the line, so they need to be // offset by half the stroke width. For the start coordinate we add half // the stroke width, and for the end coordinate we subtract half the width. @@ -1773,10 +1775,22 @@ try } } + if (lines.test(GridLines::Strikethrough)) + { + const auto halfStrikethroughWidth = lineMetrics.strikethroughWidth / 2.0f; + const auto startX = target.x + halfStrikethroughWidth; + const auto endX = target.x + fullRunWidth - halfStrikethroughWidth; + const auto y = target.y + lineMetrics.strikethroughOffset; + + DrawLine(startX, y, endX, y, lineMetrics.strikethroughWidth); + } + + _d2dBrushForeground->SetColor(_ColorFFromColorRef(underlineColor | 0xff000000)); + // In the case of the underline and strikethrough offsets, the stroke width // is already accounted for, so they don't require further adjustments. - if (lines.any(GridLines::Underline, GridLines::DoubleUnderline, GridLines::HyperlinkUnderline)) + if (lines.any(GridLines::Underline, GridLines::DoubleUnderline, GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) { const auto halfUnderlineWidth = lineMetrics.underlineWidth / 2.0f; const auto startX = target.x + halfUnderlineWidth; @@ -1788,9 +1802,9 @@ try DrawLine(startX, y, endX, y, lineMetrics.underlineWidth); } - if (lines.test(GridLines::HyperlinkUnderline)) + if (lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) { - DrawHyperlinkLine(startX, y, endX, y, lineMetrics.underlineWidth); + DrawDottedLine(startX, y, endX, y, lineMetrics.underlineWidth); } if (lines.test(GridLines::DoubleUnderline)) @@ -1801,16 +1815,6 @@ try } } - if (lines.test(GridLines::Strikethrough)) - { - const auto halfStrikethroughWidth = lineMetrics.strikethroughWidth / 2.0f; - const auto startX = target.x + halfStrikethroughWidth; - const auto endX = target.x + fullRunWidth - halfStrikethroughWidth; - const auto y = target.y + lineMetrics.strikethroughOffset; - - DrawLine(startX, y, endX, y, lineMetrics.strikethroughWidth); - } - return S_OK; } CATCH_RETURN() @@ -1840,6 +1844,14 @@ try } CATCH_RETURN() +[[nodiscard]] HRESULT DxEngine::PaintSelections(const std::vector& rects) noexcept +try +{ + UNREFERENCED_PARAMETER(rects); + return S_OK; +} +CATCH_RETURN() + // Routine Description: // - Does nothing. Our cursor is drawn in CustomTextRenderer::DrawGlyphRun, // either above or below the text. diff --git a/src/renderer/dx/DxRenderer.hpp b/src/renderer/dx/DxRenderer.hpp index bfb11205a05..990c77e18e5 100644 --- a/src/renderer/dx/DxRenderer.hpp +++ b/src/renderer/dx/DxRenderer.hpp @@ -106,8 +106,9 @@ namespace Microsoft::Console::Render const bool fTrimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, til::point const coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; + [[nodiscard]] HRESULT PaintSelections(const std::vector& rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; diff --git a/src/renderer/gdi/gdirenderer.hpp b/src/renderer/gdi/gdirenderer.hpp index a1ab2290a97..13ecdd25372 100644 --- a/src/renderer/gdi/gdirenderer.hpp +++ b/src/renderer/gdi/gdirenderer.hpp @@ -52,10 +52,12 @@ namespace Microsoft::Console::Render const bool trimLeft, const bool lineWrapped) noexcept override; [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, - const COLORREF color, + const COLORREF gridlineColor, + const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; + [[nodiscard]] HRESULT PaintSelections(const std::vector& rects) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; @@ -115,11 +117,16 @@ namespace Microsoft::Console::Render struct LineMetrics { int gridlineWidth; - int underlineOffset; - int underlineOffset2; + int thinLineWidth; + int underlineCenter; int underlineWidth; + int doubleUnderlinePosTop; + int doubleUnderlinePosBottom; int strikethroughOffset; int strikethroughWidth; + int curlyLineCenter; + int curlyLinePeriod; + int curlyLineControlPointOffset; }; LineMetrics _lineMetrics; diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index 17dc9bddd82..4dbb82d9c6d 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -2,9 +2,10 @@ // Licensed under the MIT license. #include "precomp.h" -#include #include "gdirenderer.hpp" +#include + #include "../inc/unicode.hpp" #pragma hdrstop @@ -509,36 +510,74 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) // - Draws up to one line worth of grid lines on top of characters. // Arguments: // - lines - Enum defining which edges of the rectangle to draw -// - color - The color to use for drawing the edges. +// - gridlineColor - The color to use for drawing the gridlines. +// - underlineColor - The color to use for drawing the underlines. // - cchLine - How many characters we should draw the grid lines along (left to right in a row) // - coordTarget - The starting X/Y position of the first character to draw on. // Return Value: // - S_OK or suitable GDI HRESULT error or E_FAIL for GDI errors in functions that don't reliably return a specific error code. -[[nodiscard]] HRESULT GdiEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const til::point coordTarget) noexcept +[[nodiscard]] HRESULT GdiEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept +try { LOG_IF_FAILED(_FlushBufferLines()); // Convert the target from characters to pixels. const auto ptTarget = coordTarget * _GetFontSize(); - // Set the brush color as requested and save the previous brush to restore at the end. - wil::unique_hbrush hbr(CreateSolidBrush(color)); - RETURN_HR_IF_NULL(E_FAIL, hbr.get()); - - wil::unique_hbrush hbrPrev(SelectBrush(_hdcMemoryContext, hbr.get())); - RETURN_HR_IF_NULL(E_FAIL, hbrPrev.get()); - hbr.release(); // If SelectBrush was successful, GDI owns the brush. Release for now. - // On exit, be sure we try to put the brush back how it was originally. - auto restoreBrushOnExit = wil::scope_exit([&] { hbr.reset(SelectBrush(_hdcMemoryContext, hbrPrev.get())); }); + // Create a brush with the gridline color, and apply it. + wil::unique_hbrush hbr(CreateSolidBrush(gridlineColor)); + RETURN_HR_IF_NULL(E_FAIL, hbr.get()); + const auto prevBrush = wil::SelectObject(_hdcMemoryContext, hbr.get()); + RETURN_HR_IF_NULL(E_FAIL, prevBrush.get()); // Get the font size so we know the size of the rectangle lines we'll be inscribing. const auto fontWidth = _GetFontSize().width; const auto fontHeight = _GetFontSize().height; - const auto widthOfAllCells = fontWidth * gsl::narrow_cast(cchLine); + const auto widthOfAllCells = fontWidth * gsl::narrow_cast(cchLine); - const auto DrawLine = [=](const auto x, const auto y, const auto w, const auto h) { + const auto DrawLine = [=](const til::CoordType x, const til::CoordType y, const til::CoordType w, const til::CoordType h) { return PatBlt(_hdcMemoryContext, x, y, w, h, PATCOPY); }; + const auto DrawStrokedLine = [&](const til::CoordType x, const til::CoordType y, const til::CoordType w) { + RETURN_HR_IF(E_FAIL, !MoveToEx(_hdcMemoryContext, x, y, nullptr)); + RETURN_HR_IF(E_FAIL, !LineTo(_hdcMemoryContext, x + w, y)); + return S_OK; + }; + const auto DrawCurlyLine = [&](const til::CoordType begX, const til::CoordType y, const til::CoordType width) { + const auto period = _lineMetrics.curlyLinePeriod; + const auto halfPeriod = period / 2; + const auto controlPointOffset = _lineMetrics.curlyLineControlPointOffset; + + // To ensure proper continuity of the wavy line between cells of different line color + // this code starts/ends the line earlier/later than it should and then clips it. + // Clipping in GDI is expensive, but it was the easiest approach. + // I've noticed that subtracting -1px prevents missing pixels when GDI draws. They still + // occur at certain (small) font sizes, but I couldn't figure out how to prevent those. + const auto lineStart = ((begX - 1) / period) * period; + const auto lineEnd = begX + width; + + IntersectClipRect(_hdcMemoryContext, begX, ptTarget.y, begX + width, ptTarget.y + fontHeight); + const auto restoreRegion = wil::scope_exit([&]() { + // Luckily no one else uses clip regions. They're weird to use. + SelectClipRgn(_hdcMemoryContext, nullptr); + }); + + // You can assume that each cell has roughly 5 POINTs on average. 128 POINTs is 1KiB. + til::small_vector points; + + // This is the start point of the Bézier curve. + points.emplace_back(lineStart, y); + + for (auto x = lineStart; x < lineEnd; x += period) + { + points.emplace_back(x + halfPeriod, y - controlPointOffset); + points.emplace_back(x + halfPeriod, y + controlPointOffset); + points.emplace_back(x + period, y); + } + + const auto cpt = gsl::narrow_cast(points.size()); + return PolyBezier(_hdcMemoryContext, points.data(), cpt); + }; if (lines.test(GridLines::Left)) { @@ -574,26 +613,60 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.gridlineWidth)); } - if (lines.any(GridLines::Underline, GridLines::DoubleUnderline)) + if (lines.test(GridLines::Strikethrough)) { - const auto y = ptTarget.y + _lineMetrics.underlineOffset; - RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.underlineWidth)); + const auto y = ptTarget.y + _lineMetrics.strikethroughOffset; + RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.strikethroughWidth)); + } - if (lines.test(GridLines::DoubleUnderline)) - { - const auto y2 = ptTarget.y + _lineMetrics.underlineOffset2; - RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y2, widthOfAllCells, _lineMetrics.underlineWidth)); - } + DWORD underlinePenType = PS_SOLID; + if (lines.test(GridLines::DottedUnderline)) + { + underlinePenType = PS_DOT; + } + else if (lines.test(GridLines::DashedUnderline)) + { + underlinePenType = PS_DASH; } - if (lines.test(GridLines::Strikethrough)) + DWORD underlineWidth = _lineMetrics.underlineWidth; + if (lines.any(GridLines::DoubleUnderline, GridLines::CurlyUnderline)) { - const auto y = ptTarget.y + _lineMetrics.strikethroughOffset; - RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.strikethroughWidth)); + underlineWidth = _lineMetrics.thinLineWidth; + } + + const LOGBRUSH brushProp{ .lbStyle = BS_SOLID, .lbColor = underlineColor }; + wil::unique_hpen hpen(ExtCreatePen(underlinePenType | PS_GEOMETRIC | PS_ENDCAP_FLAT, underlineWidth, &brushProp, 0, nullptr)); + + // Apply the pen. + const auto prevPen = wil::SelectObject(_hdcMemoryContext, hpen.get()); + RETURN_HR_IF_NULL(E_FAIL, prevPen.get()); + + if (lines.test(GridLines::Underline)) + { + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineCenter, widthOfAllCells); + } + else if (lines.test(GridLines::DoubleUnderline)) + { + RETURN_IF_FAILED(DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.doubleUnderlinePosTop, widthOfAllCells)); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.doubleUnderlinePosBottom, widthOfAllCells); + } + else if (lines.test(GridLines::CurlyUnderline)) + { + return DrawCurlyLine(ptTarget.x, ptTarget.y + _lineMetrics.curlyLineCenter, widthOfAllCells); + } + else if (lines.test(GridLines::DottedUnderline)) + { + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineCenter, widthOfAllCells); + } + else if (lines.test(GridLines::DashedUnderline)) + { + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineCenter, widthOfAllCells); } return S_OK; } +CATCH_RETURN(); // Routine Description: // - Draws the cursor on the screen @@ -763,6 +836,13 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) return S_OK; } +[[nodiscard]] HRESULT GdiEngine::PaintSelections(const std::vector& rects) noexcept +{ + UNREFERENCED_PARAMETER(rects); + + return S_OK; +} + #ifdef DBG void GdiEngine::_CreateDebugWindow() diff --git a/src/renderer/gdi/state.cpp b/src/renderer/gdi/state.cpp index 556f2df02fb..f993c6f74e6 100644 --- a/src/renderer/gdi/state.cpp +++ b/src/renderer/gdi/state.cpp @@ -344,58 +344,109 @@ GdiEngine::~GdiEngine() // There is no font metric for the grid line width, so we use a small // multiple of the font size, which typically rounds to a pixel. - const auto fontSize = _tmFontMetrics.tmHeight - _tmFontMetrics.tmInternalLeading; - _lineMetrics.gridlineWidth = std::lround(fontSize * 0.025); + const auto cellHeight = static_cast(Font.GetSize().height); + const auto fontSize = static_cast(_tmFontMetrics.tmHeight - _tmFontMetrics.tmInternalLeading); + const auto baseline = static_cast(_tmFontMetrics.tmAscent); + float idealGridlineWidth = std::max(1.0f, fontSize * 0.025f); + float idealUnderlineTop = 0; + float idealUnderlineWidth = 0; + float idealStrikethroughTop = 0; + float idealStrikethroughWidth = 0; OUTLINETEXTMETRICW outlineMetrics; if (GetOutlineTextMetricsW(_hdcMemoryContext, sizeof(outlineMetrics), &outlineMetrics)) { // For TrueType fonts, the other line metrics can be obtained from // the font's outline text metric structure. - _lineMetrics.underlineOffset = outlineMetrics.otmsUnderscorePosition; - _lineMetrics.underlineWidth = outlineMetrics.otmsUnderscoreSize; - _lineMetrics.strikethroughOffset = outlineMetrics.otmsStrikeoutPosition; - _lineMetrics.strikethroughWidth = outlineMetrics.otmsStrikeoutSize; + idealUnderlineTop = static_cast(baseline - outlineMetrics.otmsUnderscorePosition); + idealUnderlineWidth = static_cast(outlineMetrics.otmsUnderscoreSize); + idealStrikethroughWidth = static_cast(outlineMetrics.otmsStrikeoutSize); + idealStrikethroughTop = static_cast(baseline - outlineMetrics.otmsStrikeoutPosition); } else { - // If we can't obtain the outline metrics for the font, we just pick - // some reasonable values for the offsets and widths. - _lineMetrics.underlineOffset = -std::lround(fontSize * 0.05); - _lineMetrics.underlineWidth = _lineMetrics.gridlineWidth; - _lineMetrics.strikethroughOffset = std::lround(_tmFontMetrics.tmAscent / 3.0); - _lineMetrics.strikethroughWidth = _lineMetrics.gridlineWidth; + // If we can't obtain the outline metrics for the font, we just pick some reasonable values for the offsets and widths. + idealUnderlineTop = std::max(1.0f, roundf(baseline - fontSize * 0.05f)); + idealUnderlineWidth = idealGridlineWidth; + idealStrikethroughTop = std::max(1.0f, roundf(baseline * (2.0f / 3.0f))); + idealStrikethroughWidth = idealGridlineWidth; } - // We always want the lines to be visible, so if a stroke width ends - // up being zero, we need to make it at least 1 pixel. - _lineMetrics.gridlineWidth = std::max(_lineMetrics.gridlineWidth, 1); - _lineMetrics.underlineWidth = std::max(_lineMetrics.underlineWidth, 1); - _lineMetrics.strikethroughWidth = std::max(_lineMetrics.strikethroughWidth, 1); - - // Offsets are relative to the base line of the font, so we subtract - // from the ascent to get an offset relative to the top of the cell. - const auto ascent = _tmFontMetrics.tmAscent; - _lineMetrics.underlineOffset = ascent - _lineMetrics.underlineOffset; - _lineMetrics.strikethroughOffset = ascent - _lineMetrics.strikethroughOffset; - - // For double underlines we need a second offset, just below the first, - // but with a bit of a gap (about double the grid line width). - _lineMetrics.underlineOffset2 = _lineMetrics.underlineOffset + - _lineMetrics.underlineWidth + - std::lround(fontSize * 0.05); - - // However, we don't want the underline to extend past the bottom of the - // cell, so we clamp the offset to fit just inside. - const auto maxUnderlineOffset = Font.GetSize().height - _lineMetrics.underlineWidth; - _lineMetrics.underlineOffset2 = std::min(_lineMetrics.underlineOffset2, maxUnderlineOffset); - - // But if the resulting gap isn't big enough even to register as a thicker - // line, it's better to place the second line slightly above the first. - if (_lineMetrics.underlineOffset2 < _lineMetrics.underlineOffset + _lineMetrics.gridlineWidth) - { - _lineMetrics.underlineOffset2 = _lineMetrics.underlineOffset - _lineMetrics.gridlineWidth; - } + // GdiEngine::PaintBufferGridLines paints underlines using HPEN and LineTo, etc., which draws lines centered on the given coordinates. + // This means we need to shift the limit (cellHeight - underlineWidth) and offset (idealUnderlineTop) by half the width. + const auto underlineWidth = std::max(1.0f, roundf(idealUnderlineWidth)); + const auto underlineCenter = std::min(floorf(cellHeight - underlineWidth / 2.0f), roundf(idealUnderlineTop + underlineWidth / 2.0f)); + + const auto strikethroughWidth = std::max(1.0f, roundf(idealStrikethroughWidth)); + const auto strikethroughOffset = std::min(cellHeight - strikethroughWidth, roundf(idealStrikethroughTop)); + + // For double underlines we loosely follow what Word does: + // 1. The lines are half the width of an underline + // 2. Ideally the bottom line is aligned with the bottom of the underline + // 3. The top underline is vertically in the middle between baseline and ideal bottom underline + // 4. If the top line gets too close to the baseline the underlines are shifted downwards + // 5. The minimum gap between the two lines appears to be similar to Tex (1.2pt) + // (Additional notes below.) + + // 1. + const auto thinLineWidth = std::max(1.0f, roundf(idealUnderlineWidth / 2.0f)); + // 2. + auto doubleUnderlinePosBottom = underlineCenter + underlineWidth - thinLineWidth; + // 3. Since we don't align the center of our two lines, but rather the top borders + // we need to subtract half a line width from our center point. + auto doubleUnderlinePosTop = roundf((baseline + doubleUnderlinePosBottom - thinLineWidth) / 2.0f); + // 4. + doubleUnderlinePosTop = std::max(doubleUnderlinePosTop, baseline + thinLineWidth); + // 5. The gap is only the distance _between_ the lines, but we need the distance from the + // top border of the top and bottom lines, which includes an additional line width. + const auto doubleUnderlineGap = std::max(1.0f, roundf(1.2f / 72.0f * _iCurrentDpi)); + doubleUnderlinePosBottom = std::max(doubleUnderlinePosBottom, doubleUnderlinePosTop + doubleUnderlineGap + thinLineWidth); + // Our cells can't overlap each other so we additionally clamp the bottom line to be inside the cell boundaries. + doubleUnderlinePosBottom = std::min(doubleUnderlinePosBottom, cellHeight - thinLineWidth); + + // The wave line is drawn using a cubic Bézier curve (PolyBezier), because that happens to be cheap with GDI. + // We use a Bézier curve where, if the start (a) and end (c) points are at (0,0) and (1,0), the control points are + // at (0.5,0.5) (b) and (0.5,-0.5) (d) respectively. Like this but a/b/c/d are square and the lines are round: + // + // b + // + // ^ + // / \ here's some text so the compiler ignores the trailing \ character + // a \ c + // \ / + // v + // + // d + // + // If you punch x=0.25 into the cubic bezier formula you get y=0.140625. This constant is + // important to us because it (plus the line width) tells us the amplitude of the wave. + // + // We can use the inverse of the constant to figure out how many px one period of the wave has to be to end up being 1px tall. + // In our case we want the amplitude of the wave to have a peak-to-peak amplitude that matches our double-underline. + const auto doubleUnderlineHalfDistance = 0.5f * (doubleUnderlinePosBottom - doubleUnderlinePosTop); + const auto doubleUnderlineCenter = doubleUnderlinePosTop + doubleUnderlineHalfDistance; + const auto curlyLineIdealAmplitude = std::max(1.0f, doubleUnderlineHalfDistance); + // Since GDI can't deal with fractional pixels, we first calculate the control point offsets (0.5 and -0.5) by multiplying by 0.5 and + // then undo that by multiplying by 2.0 for the period. This ensures that our control points can be at curlyLinePeriod/2, an integer. + const auto curlyLineControlPointOffset = roundf(curlyLineIdealAmplitude * (1.0f / 0.140625f) * 0.5f); + const auto curlyLinePeriod = curlyLineControlPointOffset * 2.0f; + // We can reverse the above to get back the actual amplitude of our Bézier curve. The line + // will be drawn with a width of thinLineWidth in the center of the curve (= 0.5x padding). + const auto curlyLineAmplitude = 0.140625f * curlyLinePeriod + 0.5f * thinLineWidth; + // To make the wavy line with its double-underline amplitude look consistent with the double-underline we position it at its center. + const auto curlyLineOffset = std::min(roundf(doubleUnderlineCenter), floorf(cellHeight - curlyLineAmplitude)); + + _lineMetrics.gridlineWidth = lroundf(idealGridlineWidth); + _lineMetrics.thinLineWidth = lroundf(thinLineWidth); + _lineMetrics.underlineCenter = lroundf(underlineCenter); + _lineMetrics.underlineWidth = lroundf(underlineWidth); + _lineMetrics.doubleUnderlinePosTop = lroundf(doubleUnderlinePosTop); + _lineMetrics.doubleUnderlinePosBottom = lroundf(doubleUnderlinePosBottom); + _lineMetrics.strikethroughOffset = lroundf(strikethroughOffset); + _lineMetrics.strikethroughWidth = lroundf(strikethroughWidth); + _lineMetrics.curlyLineCenter = lroundf(curlyLineOffset); + _lineMetrics.curlyLinePeriod = lroundf(curlyLinePeriod); + _lineMetrics.curlyLineControlPointOffset = lroundf(curlyLineControlPointOffset); // Now find the size of a 0 in this current font and save it for conversions done later. _coordFontLast = Font.GetSize(); diff --git a/src/renderer/inc/IRenderData.hpp b/src/renderer/inc/IRenderData.hpp index 69e0dc2a0e6..cfc035a7f9b 100644 --- a/src/renderer/inc/IRenderData.hpp +++ b/src/renderer/inc/IRenderData.hpp @@ -47,6 +47,7 @@ namespace Microsoft::Console::Render virtual const TextBuffer& GetTextBuffer() const noexcept = 0; virtual const FontInfo& GetFontInfo() const noexcept = 0; virtual std::vector GetSelectionRects() noexcept = 0; + virtual std::vector GetSearchSelectionRects() noexcept = 0; virtual void LockConsole() noexcept = 0; virtual void UnlockConsole() noexcept = 0; @@ -71,6 +72,7 @@ namespace Microsoft::Console::Render virtual const bool IsBlockSelection() const = 0; virtual void ClearSelection() = 0; virtual void SelectNewRegion(const til::point coordStart, const til::point coordEnd) = 0; + virtual void SelectSearchRegions(std::vector source) = 0; virtual const til::point GetSelectionAnchor() const noexcept = 0; virtual const til::point GetSelectionEnd() const noexcept = 0; virtual const bool IsUiaDataInitialized() const noexcept = 0; diff --git a/src/renderer/inc/IRenderEngine.hpp b/src/renderer/inc/IRenderEngine.hpp index 46221ae911d..016f0f10baa 100644 --- a/src/renderer/inc/IRenderEngine.hpp +++ b/src/renderer/inc/IRenderEngine.hpp @@ -41,6 +41,9 @@ namespace Microsoft::Console::Render Right, Underline, DoubleUnderline, + CurlyUnderline, + DottedUnderline, + DashedUnderline, Strikethrough, HyperlinkUnderline }; @@ -73,8 +76,9 @@ namespace Microsoft::Console::Render [[nodiscard]] virtual HRESULT PrepareLineTransform(LineRendition lineRendition, til::CoordType targetRow, til::CoordType viewportLeft) noexcept = 0; [[nodiscard]] virtual HRESULT PaintBackground() noexcept = 0; [[nodiscard]] virtual HRESULT PaintBufferLine(std::span clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept = 0; - [[nodiscard]] virtual HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, til::point coordTarget) noexcept = 0; + [[nodiscard]] virtual HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF gridlineColor, COLORREF underlineColor, size_t cchLine, til::point coordTarget) noexcept = 0; [[nodiscard]] virtual HRESULT PaintSelection(const til::rect& rect) noexcept = 0; + [[nodiscard]] virtual HRESULT PaintSelections(const std::vector& rects) noexcept = 0; [[nodiscard]] virtual HRESULT PaintCursor(const CursorOptions& options) noexcept = 0; [[nodiscard]] virtual HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const RenderSettings& renderSettings, gsl::not_null pData, bool usingSoftFont, bool isSettingDefaultBrushes) noexcept = 0; [[nodiscard]] virtual HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept = 0; diff --git a/src/renderer/inc/RenderSettings.hpp b/src/renderer/inc/RenderSettings.hpp index 4b6e7c3981c..c836bdde848 100644 --- a/src/renderer/inc/RenderSettings.hpp +++ b/src/renderer/inc/RenderSettings.hpp @@ -41,6 +41,7 @@ namespace Microsoft::Console::Render size_t GetColorAliasIndex(const ColorAlias alias) const noexcept; std::pair GetAttributeColors(const TextAttribute& attr) const noexcept; std::pair GetAttributeColorsWithAlpha(const TextAttribute& attr) const noexcept; + COLORREF GetAttributeUnderlineColor(const TextAttribute& attr) const noexcept; void ToggleBlinkRendition(class Renderer& renderer) noexcept; private: diff --git a/src/renderer/uia/UiaRenderer.cpp b/src/renderer/uia/UiaRenderer.cpp index e58c227c561..a245491dead 100644 --- a/src/renderer/uia/UiaRenderer.cpp +++ b/src/renderer/uia/UiaRenderer.cpp @@ -47,6 +47,12 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) : [[nodiscard]] HRESULT UiaEngine::Disable() noexcept { _isEnabled = false; + + // If we had buffered any text from NotifyNewText, dump it. When we do come + // back around to actually paint, we will just no-op. No sense in keeping + // the data buffered. + _newOutput = std::wstring{}; + return S_OK; } @@ -171,6 +177,10 @@ CATCH_RETURN(); [[nodiscard]] HRESULT UiaEngine::NotifyNewText(const std::wstring_view newText) noexcept try { + // GH#16217 - don't even buffer this text if we're disabled. We may never + // come around to write it out. + RETURN_HR_IF(S_FALSE, !_isEnabled); + if (!newText.empty()) { _newOutput.append(newText); @@ -347,14 +357,16 @@ void UiaEngine::WaitUntilCanRender() noexcept // For UIA, this doesn't mean anything. So do nothing. // Arguments: // - lines - -// - color - +// - gridlineColor - +// - underlineColor - // - cchLine - // - coordTarget - // Return Value: // - S_FALSE -[[nodiscard]] HRESULT UiaEngine::PaintBufferGridLines(GridLineSet const /*lines*/, - COLORREF const /*color*/, - size_t const /*cchLine*/, +[[nodiscard]] HRESULT UiaEngine::PaintBufferGridLines(const GridLineSet /*lines*/, + const COLORREF /*gridlineColor*/, + const COLORREF /*underlineColor*/, + const size_t /*cchLine*/, const til::point /*coordTarget*/) noexcept { return S_FALSE; @@ -374,6 +386,11 @@ void UiaEngine::WaitUntilCanRender() noexcept return S_FALSE; } +[[nodiscard]] HRESULT UiaEngine::PaintSelections(const std::vector& /*rect*/) noexcept +{ + return S_FALSE; +} + // Routine Description: // - Draws the cursor on the screen // For UIA, this doesn't mean anything. So do nothing. diff --git a/src/renderer/uia/UiaRenderer.hpp b/src/renderer/uia/UiaRenderer.hpp index 5e486b7e0e1..bd1734c5d64 100644 --- a/src/renderer/uia/UiaRenderer.hpp +++ b/src/renderer/uia/UiaRenderer.hpp @@ -49,8 +49,9 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT NotifyNewText(const std::wstring_view newText) noexcept override; [[nodiscard]] HRESULT PaintBackground() noexcept override; [[nodiscard]] HRESULT PaintBufferLine(const std::span clusters, const til::point coord, const bool fTrimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const til::point coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; + [[nodiscard]] HRESULT PaintSelections(const std::vector& rects) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; [[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const RenderSettings& renderSettings, const gsl::not_null pData, const bool usingSoftFont, const bool isSettingDefaultBrushes) noexcept override; [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept override; diff --git a/src/renderer/vt/VtSequences.cpp b/src/renderer/vt/VtSequences.cpp index 1b728aae500..612d3c06d06 100644 --- a/src/renderer/vt/VtSequences.cpp +++ b/src/renderer/vt/VtSequences.cpp @@ -476,25 +476,6 @@ using namespace Microsoft::Console::Render; return _Write(isReversed ? "\x1b[7m" : "\x1b[27m"); } -// Method Description: -// - Send a sequence to the connected terminal to request win32-input-mode from -// them. This will enable the connected terminal to send us full INPUT_RECORDs -// as input. If the terminal doesn't understand this sequence, it'll just -// ignore it. -// Arguments: -// - -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_RequestWin32Input() noexcept -{ - return _Write("\x1b[?9001h"); -} - -[[nodiscard]] HRESULT VtEngine::_RequestFocusEventMode() noexcept -{ - return _Write("\x1b[?1004h"); -} - // Method Description: // - Send a sequence to the connected terminal to switch to the alternate or main screen buffer. // Arguments: diff --git a/src/renderer/vt/invalidate.cpp b/src/renderer/vt/invalidate.cpp index 7ef59bc03b0..8f80d3eb8ea 100644 --- a/src/renderer/vt/invalidate.cpp +++ b/src/renderer/vt/invalidate.cpp @@ -126,6 +126,14 @@ CATCH_RETURN(); // - S_OK [[nodiscard]] HRESULT VtEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept { + // This must be kept in sync with RequestWin32Input(). + // It ensures that we disable the modes that we requested on startup. + // Linux shells for instance don't understand the win32-input-mode 9001. + // + // This can be here, instead of being appended at the end of this final rendering pass, + // because these two states happen to have no influence on the caller's VT parsing. + std::ignore = _Write("\033[?9001l\033[?1004l"); + *pForcePaint = true; return S_OK; } diff --git a/src/renderer/vt/paint.cpp b/src/renderer/vt/paint.cpp index 801390085c0..d8e3e66686b 100644 --- a/src/renderer/vt/paint.cpp +++ b/src/renderer/vt/paint.cpp @@ -187,13 +187,15 @@ using namespace Microsoft::Console::Types; // - Draws up to one line worth of grid lines on top of characters. // Arguments: // - lines - Enum defining which edges of the rectangle to draw -// - color - The color to use for drawing the edges. +// - gridlineColor - The color to use for drawing the gridlines. +// - underlineColor - The color to use for drawing the underlines. // - cchLine - How many characters we should draw the grid lines along (left to right in a row) // - coordTarget - The starting X/Y position of the first character to draw on. // Return Value: // - S_OK [[nodiscard]] HRESULT VtEngine::PaintBufferGridLines(const GridLineSet /*lines*/, - const COLORREF /*color*/, + const COLORREF /*gridlineColor*/, + const COLORREF /*underlineColor*/, const size_t /*cchLine*/, const til::point /*coordTarget*/) noexcept { @@ -231,6 +233,11 @@ using namespace Microsoft::Console::Types; return S_OK; } +[[nodiscard]] HRESULT VtEngine::PaintSelections(const std::vector& /*rect*/) noexcept +{ + return S_OK; +} + // Routine Description: // - Write a VT sequence to change the current colors of text. Writes true RGB // color sequences. diff --git a/src/renderer/vt/state.cpp b/src/renderer/vt/state.cpp index 8408ee3aeb6..5bb6b7d694d 100644 --- a/src/renderer/vt/state.cpp +++ b/src/renderer/vt/state.cpp @@ -167,6 +167,7 @@ void VtEngine::_flushImpl() noexcept void VtEngine::Cork(bool corked) noexcept { _corked = corked; + _Flush(); } // Method Description: @@ -525,11 +526,13 @@ void VtEngine::SetTerminalCursorTextPosition(const til::point cursor) noexcept // - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. HRESULT VtEngine::RequestWin32Input() noexcept { + // On startup we request the modes we require for optimal functioning + // (namely win32 input mode and focus event mode). + // // It's important that any additional modes set here are also mirrored in // the AdaptDispatch::HardReset method, since that needs to re-enable them // in the connected terminal after passing through an RIS sequence. - RETURN_IF_FAILED(_RequestWin32Input()); - RETURN_IF_FAILED(_RequestFocusEventMode()); + RETURN_IF_FAILED(_Write("\033[?9001h\033[?1004h")); _Flush(); return S_OK; } diff --git a/src/renderer/vt/vtrenderer.hpp b/src/renderer/vt/vtrenderer.hpp index 15de6e0e760..7a974850afc 100644 --- a/src/renderer/vt/vtrenderer.hpp +++ b/src/renderer/vt/vtrenderer.hpp @@ -62,8 +62,9 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition, const til::CoordType targetRow, const til::CoordType viewportLeft) noexcept override; [[nodiscard]] HRESULT PaintBackground() noexcept override; [[nodiscard]] HRESULT PaintBufferLine(std::span clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, til::point coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; + [[nodiscard]] HRESULT PaintSelections(const std::vector& rects) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept override; [[nodiscard]] HRESULT UpdateDpi(int iDpi) noexcept override; @@ -80,8 +81,6 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT WriteTerminalUtf8(const std::string_view str) noexcept; [[nodiscard]] virtual HRESULT WriteTerminalW(const std::wstring_view str) noexcept = 0; void SetTerminalOwner(Microsoft::Console::VirtualTerminal::VtIo* const terminalOwner); - void BeginResizeRequest(); - void EndResizeRequest(); void SetResizeQuirk(const bool resizeQuirk); void SetPassthroughMode(const bool passthrough) noexcept; void SetLookingForDSRCallback(std::function pfnLooking) noexcept; @@ -208,11 +207,8 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT _RequestCursor() noexcept; [[nodiscard]] HRESULT _ListenForDSR() noexcept; - [[nodiscard]] HRESULT _RequestWin32Input() noexcept; [[nodiscard]] HRESULT _SwitchScreenBuffer(const bool useAltBuffer) noexcept; - [[nodiscard]] HRESULT _RequestFocusEventMode() noexcept; - [[nodiscard]] virtual HRESULT _MoveCursor(const til::point coord) noexcept = 0; [[nodiscard]] HRESULT _RgbUpdateDrawingBrushes(const TextAttribute& textAttributes) noexcept; [[nodiscard]] HRESULT _16ColorUpdateDrawingBrushes(const TextAttribute& textAttributes) noexcept; diff --git a/src/renderer/wddmcon/WddmConRenderer.cpp b/src/renderer/wddmcon/WddmConRenderer.cpp index 378b1dfd086..db729467cae 100644 --- a/src/renderer/wddmcon/WddmConRenderer.cpp +++ b/src/renderer/wddmcon/WddmConRenderer.cpp @@ -284,9 +284,10 @@ CATCH_RETURN() CATCH_RETURN(); } -[[nodiscard]] HRESULT WddmConEngine::PaintBufferGridLines(GridLineSet const /*lines*/, - COLORREF const /*color*/, - size_t const /*cchLine*/, +[[nodiscard]] HRESULT WddmConEngine::PaintBufferGridLines(const GridLineSet /*lines*/, + const COLORREF /*gridlineColor*/, + const COLORREF /*underlineColor*/, + const size_t /*cchLine*/, const til::point /*coordTarget*/) noexcept { return S_OK; @@ -297,6 +298,11 @@ CATCH_RETURN() return S_OK; } +[[nodiscard]] HRESULT WddmConEngine::PaintSelections(const std::vector& /*rects*/) noexcept +{ + return S_OK; +} + [[nodiscard]] HRESULT WddmConEngine::PaintCursor(const CursorOptions& /*options*/) noexcept { return S_OK; diff --git a/src/renderer/wddmcon/WddmConRenderer.hpp b/src/renderer/wddmcon/WddmConRenderer.hpp index a658ef51180..930fbe6dea6 100644 --- a/src/renderer/wddmcon/WddmConRenderer.hpp +++ b/src/renderer/wddmcon/WddmConRenderer.hpp @@ -44,8 +44,9 @@ namespace Microsoft::Console::Render const til::point coord, const bool trimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, til::point const coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; + [[nodiscard]] HRESULT PaintSelections(const std::vector& rects) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; diff --git a/src/server/ApiDispatchers.cpp b/src/server/ApiDispatchers.cpp index 2449b128742..8039977b5e5 100644 --- a/src/server/ApiDispatchers.cpp +++ b/src/server/ApiDispatchers.cpp @@ -9,7 +9,6 @@ #include "../host/getset.h" #include "../host/stream.h" #include "../host/srvinit.h" -#include "../host/telemetry.hpp" #include "../host/cmdline.h" // Assumes that it will find in the calling environment. @@ -42,12 +41,10 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) if (a->Output) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleOutputCP); m->_pApiRoutines->GetConsoleOutputCodePageImpl(a->CodePage); } else { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleCP); m->_pApiRoutines->GetConsoleInputCodePageImpl(a->CodePage); } return S_OK; @@ -56,7 +53,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetConsoleMode(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleMode); const auto a = &m->u.consoleMsgL1.GetConsoleMode; const auto pObjectHandle = m->GetObjectHandle(); @@ -84,7 +80,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerSetConsoleMode(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleMode); const auto a = &m->u.consoleMsgL1.SetConsoleMode; const auto pObjectHandle = m->GetObjectHandle(); @@ -112,7 +107,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetNumberOfInputEvents(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetNumberOfConsoleInputEvents); const auto a = &m->u.consoleMsgL1.GetNumberOfConsoleInputEvents; const auto pObjectHandle = m->GetObjectHandle(); @@ -130,15 +124,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) *pbReplyPending = FALSE; const auto a = &m->u.consoleMsgL1.GetConsoleInput; - if (WI_IsFlagSet(a->Flags, CONSOLE_READ_NOREMOVE)) - { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::PeekConsoleInput, a->Unicode); - } - else - { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::ReadConsoleInput, a->Unicode); - } - a->NumRecords = 0; // If any flags are set that are not within our enum, it's invalid. @@ -229,7 +214,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) *pbReplyPending = FALSE; const auto a = &m->u.consoleMsgL1.ReadConsole; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::ReadConsole, a->Unicode); a->NumBytes = 0; // we return 0 until proven otherwise. @@ -345,7 +329,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) *pbReplyPending = FALSE; const auto a = &m->u.consoleMsgL1.WriteConsole; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::WriteConsole, a->Unicode); // Make sure we have a valid screen buffer. auto HandleData = m->GetObjectHandle(); @@ -424,21 +407,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL2.FillConsoleOutput; - - switch (a->ElementType) - { - case CONSOLE_ATTRIBUTE: - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::FillConsoleOutputAttribute); - break; - case CONSOLE_ASCII: - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::FillConsoleOutputCharacter, false); - break; - case CONSOLE_REAL_UNICODE: - case CONSOLE_FALSE_UNICODE: - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::FillConsoleOutputCharacter, true); - break; - } - // Capture length of initial fill. size_t fill = a->Length; @@ -498,7 +466,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerSetConsoleActiveScreenBuffer(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleActiveScreenBuffer); const auto pObjectHandle = m->GetObjectHandle(); RETURN_HR_IF_NULL(E_HANDLE, pObjectHandle); @@ -512,7 +479,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerFlushConsoleInputBuffer(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::FlushConsoleInputBuffer); const auto pObjectHandle = m->GetObjectHandle(); RETURN_HR_IF_NULL(E_HANDLE, pObjectHandle); @@ -530,12 +496,10 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) if (a->Output) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleOutputCP); return m->_pApiRoutines->SetConsoleOutputCodePageImpl(a->CodePage); } else { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleCP); return m->_pApiRoutines->SetConsoleInputCodePageImpl(a->CodePage); } } @@ -543,7 +507,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetConsoleCursorInfo(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleCursorInfo); const auto a = &m->u.consoleMsgL2.GetConsoleCursorInfo; const auto pObjectHandle = m->GetObjectHandle(); @@ -561,7 +524,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerSetConsoleCursorInfo(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleCursorInfo); const auto a = &m->u.consoleMsgL2.SetConsoleCursorInfo; const auto pObjectHandle = m->GetObjectHandle(); @@ -576,7 +538,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetConsoleScreenBufferInfo(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleScreenBufferInfoEx); const auto a = &m->u.consoleMsgL2.GetConsoleScreenBufferInfo; CONSOLE_SCREEN_BUFFER_INFOEX ex = { 0 }; @@ -609,7 +570,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerSetConsoleScreenBufferInfo(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleScreenBufferInfoEx); const auto a = &m->u.consoleMsgL2.SetConsoleScreenBufferInfo; const auto pObjectHandle = m->GetObjectHandle(); @@ -646,7 +606,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerSetConsoleScreenBufferSize(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleScreenBufferSize); const auto a = &m->u.consoleMsgL2.SetConsoleScreenBufferSize; const auto pObjectHandle = m->GetObjectHandle(); @@ -665,7 +624,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerSetConsoleCursorPosition(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleCursorPosition); const auto a = &m->u.consoleMsgL2.SetConsoleCursorPosition; const auto pObjectHandle = m->GetObjectHandle(); @@ -680,7 +638,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetLargestConsoleWindowSize(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetLargestConsoleWindowSize); const auto a = &m->u.consoleMsgL2.GetLargestConsoleWindowSize; const auto pObjectHandle = m->GetObjectHandle(); @@ -698,7 +655,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL2.ScrollConsoleScreenBuffer; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::ScrollConsoleScreenBuffer, a->Unicode); const auto pObjectHandle = m->GetObjectHandle(); RETURN_HR_IF_NULL(E_HANDLE, pObjectHandle); @@ -732,7 +688,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerSetConsoleTextAttribute(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleTextAttribute); const auto a = &m->u.consoleMsgL2.SetConsoleTextAttribute; const auto pObjectHandle = m->GetObjectHandle(); @@ -751,7 +706,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerSetConsoleWindowInfo(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleWindowInfo); const auto a = &m->u.consoleMsgL2.SetConsoleWindowInfo; const auto pObjectHandle = m->GetObjectHandle(); @@ -777,21 +731,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) RETURN_HR_IF(E_ACCESSDENIED, !m->GetProcessHandle()->GetPolicy().CanReadOutputBuffer()); const auto a = &m->u.consoleMsgL2.ReadConsoleOutputString; - - switch (a->StringType) - { - case CONSOLE_ATTRIBUTE: - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::ReadConsoleOutputAttribute); - break; - case CONSOLE_ASCII: - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::ReadConsoleOutputCharacter, false); - break; - case CONSOLE_REAL_UNICODE: - case CONSOLE_FALSE_UNICODE: - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::ReadConsoleOutputCharacter, true); - break; - } - a->NumRecords = 0; // Set to 0 records returned in case we have failures. PVOID pvBuffer; @@ -843,8 +782,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) { const auto a = &m->u.consoleMsgL2.WriteConsoleInput; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::WriteConsoleInput, a->Unicode); - a->NumRecords = 0; RETURN_HR_IF(E_ACCESSDENIED, !m->GetProcessHandle()->GetPolicy().CanWriteInputBuffer()); @@ -880,8 +817,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) { const auto a = &m->u.consoleMsgL2.WriteConsoleOutput; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::WriteConsoleOutput, a->Unicode); - // Backup originalRegion and set the written area to a 0 size rectangle in case of failures. const auto originalRegion = Microsoft::Console::Types::Viewport::FromInclusive(til::wrap_small_rect(a->CharRegion)); auto writtenRegion = Microsoft::Console::Types::Viewport::FromDimensions(originalRegion.Origin(), { 0, 0 }); @@ -923,21 +858,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL2.WriteConsoleOutputString; - - switch (a->StringType) - { - case CONSOLE_ATTRIBUTE: - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::WriteConsoleOutputAttribute); - break; - case CONSOLE_ASCII: - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::WriteConsoleOutputCharacter, false); - break; - case CONSOLE_REAL_UNICODE: - case CONSOLE_FALSE_UNICODE: - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::WriteConsoleOutputCharacter, true); - break; - } - // Set written records to 0 in case we early return. a->NumRecords = 0; @@ -1022,8 +942,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) const auto a = &m->u.consoleMsgL2.ReadConsoleOutput; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::ReadConsoleOutput, a->Unicode); - // Backup data region passed and set it to a zero size region in case we exit early for failures. const auto originalRegion = Microsoft::Console::Types::Viewport::FromInclusive(til::wrap_small_rect(a->CharRegion)); const auto zeroRegion = Microsoft::Console::Types::Viewport::FromDimensions(originalRegion.Origin(), { 0, 0 }); @@ -1076,7 +994,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL2.GetConsoleTitle; - Telemetry::Instance().LogApiCall(a->Original ? Telemetry::ApiCall::GetConsoleOriginalTitle : Telemetry::ApiCall::GetConsoleTitle, a->Unicode); PVOID pvBuffer; ULONG cbBuffer; @@ -1133,7 +1050,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL2.SetConsoleTitle; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleTitle, a->Unicode); PVOID pvBuffer; ULONG cbOriginalLength; @@ -1155,7 +1071,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetConsoleMouseInfo(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetNumberOfConsoleMouseButtons); const auto a = &m->u.consoleMsgL3.GetConsoleMouseInfo; m->_pApiRoutines->GetNumberOfConsoleMouseButtonsImpl(a->NumButtons); @@ -1165,7 +1080,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetConsoleFontSize(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleFontSize); const auto a = &m->u.consoleMsgL3.GetConsoleFontSize; const auto pObjectHandle = m->GetObjectHandle(); @@ -1182,7 +1096,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetConsoleCurrentFont(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetCurrentConsoleFontEx); const auto a = &m->u.consoleMsgL3.GetCurrentConsoleFont; const auto pObjectHandle = m->GetObjectHandle(); @@ -1208,7 +1121,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerSetConsoleDisplayMode(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleDisplayMode); const auto a = &m->u.consoleMsgL3.SetConsoleDisplayMode; const auto pObjectHandle = m->GetObjectHandle(); @@ -1225,7 +1137,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetConsoleDisplayMode(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleDisplayMode); const auto a = &m->u.consoleMsgL3.GetConsoleDisplayMode; // Historically this has never checked the handles. It just returns global state. @@ -1238,7 +1149,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL3.AddConsoleAliasW; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::AddConsoleAlias, a->Unicode); // Read the input buffer and validate the strings. PVOID pvBuffer; @@ -1286,7 +1196,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL3.GetConsoleAliasW; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleAlias, a->Unicode); PVOID pvInputBuffer; ULONG cbInputBufferSize; @@ -1360,7 +1269,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL3.GetConsoleAliasesLengthW; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleAliasesLength, a->Unicode); ULONG cbExeNameLength; PVOID pvExeName; @@ -1393,7 +1301,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL3.GetConsoleAliasExesLengthW; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleAliasExesLength, a->Unicode); size_t cbAliasExesLength; if (a->Unicode) @@ -1418,7 +1325,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL3.GetConsoleAliasesW; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleAliases, a->Unicode); PVOID pvExeName; ULONG cbExeNameLength; @@ -1462,7 +1368,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL3.GetConsoleAliasExesW; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleAliasExes, a->Unicode); PVOID pvBuffer; ULONG cbAliasExesBufferLength; @@ -1621,7 +1526,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetConsoleWindow(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleWindow); const auto a = &m->u.consoleMsgL3.GetConsoleWindow; m->_pApiRoutines->GetConsoleWindowImpl(a->hwnd); @@ -1631,7 +1535,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerGetConsoleSelectionInfo(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleSelectionInfo); const auto a = &m->u.consoleMsgL3.GetConsoleSelectionInfo; m->_pApiRoutines->GetConsoleSelectionInfoImpl(a->SelectionInfo); @@ -1642,7 +1545,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL3.GetConsoleHistory; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleHistoryInfo); CONSOLE_HISTORY_INFO info; info.cbSize = sizeof(info); @@ -1660,7 +1562,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL3.SetConsoleHistory; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetConsoleHistoryInfo); CONSOLE_HISTORY_INFO info; info.cbSize = sizeof(info); @@ -1674,7 +1575,6 @@ static DWORD TraceGetThreadId(CONSOLE_API_MSG* const m) [[nodiscard]] HRESULT ApiDispatchers::ServerSetConsoleCurrentFont(_Inout_ CONSOLE_API_MSG* const m, _Inout_ BOOL* const /*pbReplyPending*/) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::SetCurrentConsoleFontEx); const auto a = &m->u.consoleMsgL3.SetCurrentConsoleFont; const auto pObjectHandle = m->GetObjectHandle(); diff --git a/src/server/ApiDispatchersInternal.cpp b/src/server/ApiDispatchersInternal.cpp index 062cb5e5aa7..cc4e7be3cdb 100644 --- a/src/server/ApiDispatchersInternal.cpp +++ b/src/server/ApiDispatchersInternal.cpp @@ -8,7 +8,6 @@ #include "../host/globals.h" #include "../host/handle.h" #include "../host/server.h" -#include "../host/telemetry.hpp" #include "../host/ntprivapi.hpp" @@ -27,7 +26,6 @@ using Microsoft::Console::Interactivity::ServiceLocator; { const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto a = &m->u.consoleMsgL3.GetConsoleProcessList; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleProcessList); PVOID Buffer; ULONG BufferSize; @@ -60,7 +58,6 @@ using Microsoft::Console::Interactivity::ServiceLocator; _Inout_ BOOL* const /*pbReplyPending*/) { const auto a = &m->u.consoleMsgL1.GetConsoleLangId; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GetConsoleLangId); // TODO: MSFT: 9115192 - This should probably just ask through GetOutputCP and convert it ourselves on this side. return m->_pApiRoutines->GetConsoleLangIdImpl(a->LangId); @@ -71,7 +68,6 @@ using Microsoft::Console::Interactivity::ServiceLocator; { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto a = &m->u.consoleMsgL2.GenerateConsoleCtrlEvent; - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::GenerateConsoleCtrlEvent); LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); diff --git a/src/server/IoDispatchers.cpp b/src/server/IoDispatchers.cpp index fb3f025c95c..664e8ebab85 100644 --- a/src/server/IoDispatchers.cpp +++ b/src/server/IoDispatchers.cpp @@ -12,7 +12,6 @@ #include "../host/directio.h" #include "../host/handle.h" #include "../host/srvinit.h" -#include "../host/telemetry.hpp" #include "../interactivity/base/HostSignalInputThread.hpp" #include "../interactivity/inc/ServiceLocator.hpp" @@ -413,7 +412,6 @@ PCONSOLE_API_MSG IoDispatchers::ConsoleHandleConnectionRequest(_In_ PCONSOLE_API { auto& Globals = ServiceLocator::LocateGlobals(); auto& gci = Globals.getConsoleInformation(); - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::AttachConsole); ConsoleProcessHandle* ProcessData = nullptr; NTSTATUS Status; @@ -562,8 +560,6 @@ PCONSOLE_API_MSG IoDispatchers::ConsoleHandleConnectionRequest(_In_ PCONSOLE_API // - A pointer to the reply message. PCONSOLE_API_MSG IoDispatchers::ConsoleClientDisconnectRoutine(_In_ PCONSOLE_API_MSG pMessage) { - Telemetry::Instance().LogApiCall(Telemetry::ApiCall::FreeConsole); - const auto pProcessData = pMessage->GetProcessHandle(); auto pNotifier = ServiceLocator::LocateAccessibilityNotifier(); diff --git a/src/server/ProcessHandle.cpp b/src/server/ProcessHandle.cpp index 51b6f221c6b..eb5798d7648 100644 --- a/src/server/ProcessHandle.cpp +++ b/src/server/ProcessHandle.cpp @@ -6,7 +6,6 @@ #include "ProcessHandle.h" #include "../host/globals.h" -#include "../host/telemetry.hpp" // Routine Description: // - Constructs an instance of the ConsoleProcessHandle Class @@ -30,10 +29,6 @@ ConsoleProcessHandle::ConsoleProcessHandle(const DWORD dwProcessId, _shimPolicy(_hProcess.get()), _processCreationTime{} { - if (nullptr != _hProcess.get()) - { - Telemetry::Instance().LogProcessConnected(_hProcess.get()); - } } // Routine Description: diff --git a/src/terminal/adapter/DispatchTypes.hpp b/src/terminal/adapter/DispatchTypes.hpp index 612f82c093f..a0cabb0fde8 100644 --- a/src/terminal/adapter/DispatchTypes.hpp +++ b/src/terminal/adapter/DispatchTypes.hpp @@ -500,11 +500,18 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes enum class StatusType : VTInt { - OS_OperatingStatus = ANSIStandardStatus(5), - CPR_CursorPositionReport = ANSIStandardStatus(6), - ExCPR_ExtendedCursorPositionReport = DECPrivateStatus(6), - MSR_MacroSpaceReport = DECPrivateStatus(62), - MEM_MemoryChecksum = DECPrivateStatus(63), + OperatingStatus = ANSIStandardStatus(5), + CursorPositionReport = ANSIStandardStatus(6), + ExtendedCursorPositionReport = DECPrivateStatus(6), + PrinterStatus = DECPrivateStatus(15), + UserDefinedKeys = DECPrivateStatus(25), + KeyboardStatus = DECPrivateStatus(26), + LocatorStatus = DECPrivateStatus(55), + LocatorIdentity = DECPrivateStatus(56), + MacroSpaceReport = DECPrivateStatus(62), + MemoryChecksum = DECPrivateStatus(63), + DataIntegrity = DECPrivateStatus(75), + MultipleSessionStatus = DECPrivateStatus(85), }; using ANSIStandardMode = FlaggedEnumValue<0x00000000>; @@ -558,6 +565,11 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes ClearAllColumns = 3 }; + enum TabSetType : VTInt + { + SetEvery8Columns = 5 + }; + enum WindowManipulationType : VTInt { Invalid = 0, diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index 9221ea3cb4f..1b847d32ba0 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -68,6 +68,7 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch virtual bool ForwardTab(const VTInt numTabs) = 0; // CHT, HT virtual bool BackwardsTab(const VTInt numTabs) = 0; // CBT virtual bool TabClear(const DispatchTypes::TabClearType clearType) = 0; // TBC + virtual bool TabSet(const VTParameter setType) = 0; // DECST8C virtual bool SetColorTableEntry(const size_t tableIndex, const DWORD color) = 0; // OSCColorTable virtual bool SetDefaultForeground(const DWORD color) = 0; // OSCDefaultForeground virtual bool SetDefaultBackground(const DWORD color) = 0; // OSCDefaultBackground diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 85582542980..fb504c20200 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -1473,23 +1473,53 @@ bool AdaptDispatch::SetLineRendition(const LineRendition rendition) // - True if handled successfully. False otherwise. bool AdaptDispatch::DeviceStatusReport(const DispatchTypes::StatusType statusType, const VTParameter id) { + constexpr auto GoodCondition = L"0"; + constexpr auto PrinterNotConnected = L"?13"; + constexpr auto UserDefinedKeysNotSupported = L"?23"; + constexpr auto UnknownPcKeyboard = L"?27;0;0;5"; + constexpr auto LocatorNotConnected = L"?53"; + constexpr auto UnknownLocatorDevice = L"?57;0"; + constexpr auto TerminalReady = L"?70"; + constexpr auto MultipleSessionsNotSupported = L"?83"; + switch (statusType) { - case DispatchTypes::StatusType::OS_OperatingStatus: - _OperatingStatus(); + case DispatchTypes::StatusType::OperatingStatus: + _DeviceStatusReport(GoodCondition); return true; - case DispatchTypes::StatusType::CPR_CursorPositionReport: + case DispatchTypes::StatusType::CursorPositionReport: _CursorPositionReport(false); return true; - case DispatchTypes::StatusType::ExCPR_ExtendedCursorPositionReport: + case DispatchTypes::StatusType::ExtendedCursorPositionReport: _CursorPositionReport(true); return true; - case DispatchTypes::StatusType::MSR_MacroSpaceReport: + case DispatchTypes::StatusType::PrinterStatus: + _DeviceStatusReport(PrinterNotConnected); + return true; + case DispatchTypes::StatusType::UserDefinedKeys: + _DeviceStatusReport(UserDefinedKeysNotSupported); + return true; + case DispatchTypes::StatusType::KeyboardStatus: + _DeviceStatusReport(UnknownPcKeyboard); + return true; + case DispatchTypes::StatusType::LocatorStatus: + _DeviceStatusReport(LocatorNotConnected); + return true; + case DispatchTypes::StatusType::LocatorIdentity: + _DeviceStatusReport(UnknownLocatorDevice); + return true; + case DispatchTypes::StatusType::MacroSpaceReport: _MacroSpaceReport(); return true; - case DispatchTypes::StatusType::MEM_MemoryChecksum: + case DispatchTypes::StatusType::MemoryChecksum: _MacroChecksumReport(id); return true; + case DispatchTypes::StatusType::DataIntegrity: + _DeviceStatusReport(TerminalReady); + return true; + case DispatchTypes::StatusType::MultipleSessionStatus: + _DeviceStatusReport(MultipleSessionsNotSupported); + return true; default: return false; } @@ -1609,15 +1639,14 @@ bool AdaptDispatch::RequestTerminalParameters(const DispatchTypes::ReportingPerm } // Routine Description: -// - DSR-OS - Reports the operating status back to the input channel +// - DSR - Transmits a device status report with a given parameter string. // Arguments: -// - +// - parameters - One or more parameter values representing the status // Return Value: // - -void AdaptDispatch::_OperatingStatus() const +void AdaptDispatch::_DeviceStatusReport(const wchar_t* parameters) const { - // We always report a good operating condition. - _api.ReturnResponse(L"\x1b[0n"); + _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033[{}n"), parameters)); } // Routine Description: @@ -1917,7 +1946,13 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con return !_api.IsConsolePty(); case DispatchTypes::ModeParams::W32IM_Win32InputMode: _terminalInput.SetInputMode(TerminalInput::Mode::Win32, enable); - return !_PassThroughInputModes(); + // ConPTY requests the Win32InputMode on startup and disables it on shutdown. When nesting ConPTY inside + // ConPTY then this should not bubble up. Otherwise, when the inner ConPTY exits and the outer ConPTY + // passes the disable sequence up to the hosting terminal, we'd stop getting Win32InputMode entirely! + // It also makes more sense to not bubble it up, because this mode is specifically for INPUT_RECORD interop + // and thus entirely between a PTY's input records and its INPUT_RECORD-aware VT-aware console clients. + // Returning true here will mark this as being handled and avoid this. + return true; default: // If no functions to call, overall dispatch was a failure. return false; @@ -2787,16 +2822,23 @@ void AdaptDispatch::_ClearAllTabStops() noexcept } // Routine Description: -// - Clears all tab stops and sets the _initDefaultTabStops flag to indicate +// - DECST8C - If the parameter is SetEvery8Columns or is omitted, then this +// clears all tab stops and sets the _initDefaultTabStops flag to indicate // that the default positions should be reinitialized when needed. // Arguments: -// - +// - setType - only SetEvery8Columns is supported // Return value: -// - -void AdaptDispatch::_ResetTabStops() noexcept +// - True if handled successfully. False otherwise. +bool AdaptDispatch::TabSet(const VTParameter setType) noexcept { - _tabStopColumns.clear(); - _initDefaultTabStops = true; + constexpr auto SetEvery8Columns = DispatchTypes::TabSetType::SetEvery8Columns; + if (setType.value_or(SetEvery8Columns) == SetEvery8Columns) + { + _tabStopColumns.clear(); + _initDefaultTabStops = true; + return true; + } + return false; } // Routine Description: @@ -3070,7 +3112,7 @@ bool AdaptDispatch::HardReset() _api.GetTextBuffer().GetCursor().SetBlinkingAllowed(true); // Delete all current tab stops and reapply - _ResetTabStops(); + TabSet(DispatchTypes::TabSetType::SetEvery8Columns); // Clear the soft font in the renderer and delete the font buffer. _renderer.UpdateSoftFont({}, {}, false); @@ -3097,7 +3139,7 @@ bool AdaptDispatch::HardReset() if (stateMachine.FlushToTerminal()) { auto& engine = stateMachine.Engine(); - engine.ActionPassThroughString(L"\033[?9001;1004h"); + engine.ActionPassThroughString(L"\033[?9001h\033[?1004h"); } } return true; @@ -3158,18 +3200,7 @@ bool AdaptDispatch::_EraseScrollback() auto& cursor = textBuffer.GetCursor(); const auto row = cursor.GetPosition().y; - // Clear all the marks below the new viewport position. - textBuffer.ClearMarksInRange(til::point{ 0, height }, - til::point{ bufferSize.width, bufferSize.height }); - // Then scroll all the remaining marks up. This will trim ones that are now "outside" the buffer - textBuffer.ScrollMarks(-top); - - // Scroll the viewport content to the top of the buffer. - textBuffer.ScrollRows(top, height, -top); - // Clear everything after the viewport. - _FillRect(textBuffer, { 0, height, bufferSize.width, bufferSize.height }, whitespace, {}); - // Also reset the line rendition for all of the cleared rows. - textBuffer.ResetLineRenditionRange(height, bufferSize.height); + textBuffer.ClearScrollback(top, height); // Move the viewport _api.SetViewportPosition({ viewport.left, 0 }); // Move the cursor to the same relative location. diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 4b145f1b8ca..0a24d01577d 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -105,6 +105,7 @@ namespace Microsoft::Console::VirtualTerminal bool ForwardTab(const VTInt numTabs) override; // CHT, HT bool BackwardsTab(const VTInt numTabs) override; // CBT bool TabClear(const DispatchTypes::TabClearType clearType) override; // TBC + bool TabSet(const VTParameter setType) noexcept override; // DECST8C bool DesignateCodingSystem(const VTID codingSystem) override; // DOCS bool Designate94Charset(const VTInt gsetNumber, const VTID charset) override; // SCS bool Designate96Charset(const VTInt gsetNumber, const VTID charset) override; // SCS @@ -239,7 +240,7 @@ namespace Microsoft::Console::VirtualTerminal void _DoLineFeed(TextBuffer& textBuffer, const bool withReturn, const bool wrapForced); - void _OperatingStatus() const; + void _DeviceStatusReport(const wchar_t* parameters) const; void _CursorPositionReport(const bool extendedReport); void _MacroSpaceReport() const; void _MacroChecksumReport(const VTParameter id) const; @@ -251,7 +252,6 @@ namespace Microsoft::Console::VirtualTerminal void _ClearSingleTabStop(); void _ClearAllTabStops() noexcept; - void _ResetTabStops() noexcept; void _InitTabStopsForWidth(const VTInt width); StringHandler _RestoreColorTable(); @@ -271,7 +271,7 @@ namespace Microsoft::Console::VirtualTerminal StringHandler _CreateDrcsPassthroughHandler(const DispatchTypes::DrcsCharsetSize charsetSize); StringHandler _CreatePassthroughHandler(); - std::vector _tabStopColumns; + std::vector _tabStopColumns; bool _initDefaultTabStops = true; ITerminalApi& _api; diff --git a/src/terminal/adapter/lib/adapter.vcxproj b/src/terminal/adapter/lib/adapter.vcxproj index 3ecefa5e838..2780c818dfa 100644 --- a/src/terminal/adapter/lib/adapter.vcxproj +++ b/src/terminal/adapter/lib/adapter.vcxproj @@ -16,9 +16,7 @@ - - Create @@ -32,12 +30,10 @@ - - @@ -50,4 +46,4 @@ - + \ No newline at end of file diff --git a/src/terminal/adapter/lib/adapter.vcxproj.filters b/src/terminal/adapter/lib/adapter.vcxproj.filters index 21524f61cd6..798522c651f 100644 --- a/src/terminal/adapter/lib/adapter.vcxproj.filters +++ b/src/terminal/adapter/lib/adapter.vcxproj.filters @@ -18,15 +18,9 @@ Source Files - - Source Files - Source Files - - Source Files - Source Files @@ -53,15 +47,9 @@ Header Files - - Header Files - Header Files - - Header Files - Header Files @@ -89,5 +77,6 @@ + \ No newline at end of file diff --git a/src/terminal/adapter/precomp.h b/src/terminal/adapter/precomp.h index 838240d9b83..abed7d0b9f6 100644 --- a/src/terminal/adapter/precomp.h +++ b/src/terminal/adapter/precomp.h @@ -19,7 +19,4 @@ Module Name: #include -#include "telemetry.hpp" -#include "tracing.hpp" - #include "../../inc/conattrs.hpp" diff --git a/src/terminal/adapter/sources.inc b/src/terminal/adapter/sources.inc index d1e63430bb4..a7a07a29814 100644 --- a/src/terminal/adapter/sources.inc +++ b/src/terminal/adapter/sources.inc @@ -36,8 +36,6 @@ SOURCES= \ ..\MacroBuffer.cpp \ ..\adaptDispatchGraphics.cpp \ ..\terminalOutput.cpp \ - ..\telemetry.cpp \ - ..\tracing.cpp \ INCLUDES = \ $(INCLUDES); \ diff --git a/src/terminal/adapter/telemetry.cpp b/src/terminal/adapter/telemetry.cpp deleted file mode 100644 index f4a63025a0e..00000000000 --- a/src/terminal/adapter/telemetry.cpp +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "telemetry.hpp" diff --git a/src/terminal/adapter/telemetry.hpp b/src/terminal/adapter/telemetry.hpp deleted file mode 100644 index 1a2ba41a862..00000000000 --- a/src/terminal/adapter/telemetry.hpp +++ /dev/null @@ -1,20 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- telemetry.hpp - -Abstract: -- This module is used for recording all telemetry feedback from the console virtual terminal parser - ---*/ -#pragma once - -// Including TraceLogging essentials for the binary -#include -#include -#include -#include - -TRACELOGGING_DECLARE_PROVIDER(g_hConsoleVirtTermParserEventTraceProvider); diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index 4f11ae8bb93..122b48820bb 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -61,6 +61,7 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons bool ForwardTab(const VTInt /*numTabs*/) override { return false; } // CHT, HT bool BackwardsTab(const VTInt /*numTabs*/) override { return false; } // CBT bool TabClear(const DispatchTypes::TabClearType /*clearType*/) override { return false; } // TBC + bool TabSet(const VTParameter /*setType*/) override { return false; } // DECST8C bool SetColorTableEntry(const size_t /*tableIndex*/, const DWORD /*color*/) override { return false; } // OSCColorTable bool SetDefaultForeground(const DWORD /*color*/) override { return false; } // OSCDefaultForeground bool SetDefaultBackground(const DWORD /*color*/) override { return false; } // OSCDefaultBackground diff --git a/src/terminal/adapter/tracing.cpp b/src/terminal/adapter/tracing.cpp deleted file mode 100644 index b72682fe914..00000000000 --- a/src/terminal/adapter/tracing.cpp +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "tracing.hpp" diff --git a/src/terminal/adapter/tracing.hpp b/src/terminal/adapter/tracing.hpp deleted file mode 100644 index dad93792aed..00000000000 --- a/src/terminal/adapter/tracing.hpp +++ /dev/null @@ -1,18 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- tracing.hpp - -Abstract: -- This module is used for recording tracing/debugging information to the telemetry ETW channel -- The data is not automatically broadcast to telemetry backends as it does not set the TELEMETRY keyword. -- NOTE: Many functions in this file appear to be copy/pastes. This is because the TraceLog documentation warns - to not be "cute" in trying to reduce its macro usages with variables as it can cause unexpected behavior. - ---*/ - -#pragma once - -#include "telemetry.hpp" diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index c3192462052..a1e775d3dfb 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -1508,7 +1508,7 @@ class AdapterTest Log::Comment(L"Test 1: Verify good operating condition."); _testGetSet->PrepData(); - VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::OS_OperatingStatus, {})); + VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::OperatingStatus, {})); _testGetSet->ValidateInputEvent(L"\x1b[0n"); } @@ -1531,7 +1531,7 @@ class AdapterTest coordCursorExpected.x++; coordCursorExpected.y++; - VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::CPR_CursorPositionReport, {})); + VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::CursorPositionReport, {})); wchar_t pwszBuffer[50]; @@ -1555,7 +1555,7 @@ class AdapterTest // Then note that VT is 1,1 based for the top left, so add 1. (The rest of the console uses 0,0 for array index bases.) coordCursorExpectedFirst += til::point{ 1, 1 }; - VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::CPR_CursorPositionReport, {})); + VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::CursorPositionReport, {})); auto cursorPos = _testGetSet->_textBuffer->GetCursor().GetPosition(); cursorPos.x++; @@ -1565,7 +1565,7 @@ class AdapterTest auto coordCursorExpectedSecond{ coordCursorExpectedFirst }; coordCursorExpectedSecond += til::point{ 1, 1 }; - VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::CPR_CursorPositionReport, {})); + VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::CursorPositionReport, {})); wchar_t pwszBuffer[50]; @@ -1594,7 +1594,7 @@ class AdapterTest // Until we support paging (GH#13892) the reported page number should always be 1. const auto pageExpected = 1; - VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::ExCPR_ExtendedCursorPositionReport, {})); + VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::ExtendedCursorPositionReport, {})); wchar_t pwszBuffer[50]; swprintf_s(pwszBuffer, ARRAYSIZE(pwszBuffer), L"\x1b[?%d;%d;%dR", coordCursorExpected.y, coordCursorExpected.x, pageExpected); @@ -1610,7 +1610,7 @@ class AdapterTest Log::Comment(L"Test 1: Verify maximum space available"); _testGetSet->PrepData(); - VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MSR_MacroSpaceReport, {})); + VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MacroSpaceReport, {})); wchar_t pwszBuffer[50]; swprintf_s(pwszBuffer, ARRAYSIZE(pwszBuffer), L"\x1b[%zu*{", availableSpace); @@ -1623,7 +1623,7 @@ class AdapterTest _stateMachine->ProcessString(L"\033P2;0;0!z12345678\033\\"); _stateMachine->ProcessString(L"\033P3;0;0!z12345678\033\\"); _stateMachine->ProcessString(L"\033P4;0;0!z12345678\033\\"); - VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MSR_MacroSpaceReport, {})); + VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MacroSpaceReport, {})); swprintf_s(pwszBuffer, ARRAYSIZE(pwszBuffer), L"\x1b[%zu*{", availableSpace - 2); _testGetSet->ValidateInputEvent(pwszBuffer); @@ -1631,7 +1631,7 @@ class AdapterTest Log::Comment(L"Test 3: Verify space reset"); _testGetSet->PrepData(); VERIFY_IS_TRUE(_pDispatch->HardReset()); - VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MSR_MacroSpaceReport, {})); + VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MacroSpaceReport, {})); swprintf_s(pwszBuffer, ARRAYSIZE(pwszBuffer), L"\x1b[%zu*{", availableSpace); _testGetSet->ValidateInputEvent(pwszBuffer); @@ -1643,7 +1643,7 @@ class AdapterTest Log::Comment(L"Test 1: Verify initial checksum is 0"); _testGetSet->PrepData(); - VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MEM_MemoryChecksum, 12)); + VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MemoryChecksum, 12)); _testGetSet->ValidateInputEvent(L"\033P12!~0000\033\\"); @@ -1652,7 +1652,7 @@ class AdapterTest // Define a couple of text macros _stateMachine->ProcessString(L"\033P1;0;0!zABCD\033\\"); _stateMachine->ProcessString(L"\033P2;0;0!zabcd\033\\"); - VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MEM_MemoryChecksum, 34)); + VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MemoryChecksum, 34)); // Checksum is a 16-bit negated sum of the macro buffer characters. const auto checksum = gsl::narrow_cast(-('A' + 'B' + 'C' + 'D' + 'a' + 'b' + 'c' + 'd')); @@ -1663,11 +1663,51 @@ class AdapterTest Log::Comment(L"Test 3: Verify checksum resets to 0"); _testGetSet->PrepData(); VERIFY_IS_TRUE(_pDispatch->HardReset()); - VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MEM_MemoryChecksum, 56)); + VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MemoryChecksum, 56)); _testGetSet->ValidateInputEvent(L"\033P56!~0000\033\\"); } + TEST_METHOD(DeviceStatus_PrivateStatusTests) + { + Log::Comment(L"Starting test..."); + + Log::Comment(L"Test 1: Verify printer is not connected."); + _testGetSet->PrepData(); + VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::PrinterStatus, {})); + _testGetSet->ValidateInputEvent(L"\x1b[?13n"); + + Log::Comment(L"Test 2: Verify UDKs are not supported."); + _testGetSet->PrepData(); + VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::UserDefinedKeys, {})); + _testGetSet->ValidateInputEvent(L"\x1b[?23n"); + + Log::Comment(L"Test 3: Verify PC keyboard with unknown dialect."); + _testGetSet->PrepData(); + VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::KeyboardStatus, {})); + _testGetSet->ValidateInputEvent(L"\x1b[?27;0;0;5n"); + + Log::Comment(L"Test 4: Verify locator is not connected."); + _testGetSet->PrepData(); + VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::LocatorStatus, {})); + _testGetSet->ValidateInputEvent(L"\x1b[?53n"); + + Log::Comment(L"Test 5: Verify locator type is unknown."); + _testGetSet->PrepData(); + VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::LocatorIdentity, {})); + _testGetSet->ValidateInputEvent(L"\x1b[?57;0n"); + + Log::Comment(L"Test 6: Verify terminal is ready."); + _testGetSet->PrepData(); + VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::DataIntegrity, {})); + _testGetSet->ValidateInputEvent(L"\x1b[?70n"); + + Log::Comment(L"Test 7: Verify multiple sessions are not supported."); + _testGetSet->PrepData(); + VERIFY_IS_TRUE(_pDispatch->DeviceStatusReport(DispatchTypes::StatusType::MultipleSessionStatus, {})); + _testGetSet->ValidateInputEvent(L"\x1b[?83n"); + } + TEST_METHOD(DeviceAttributesTests) { Log::Comment(L"Starting test..."); diff --git a/src/terminal/adapter/ut_adapter/inputTest.cpp b/src/terminal/adapter/ut_adapter/inputTest.cpp index 581f03ed925..d3c4c3d0d06 100644 --- a/src/terminal/adapter/ut_adapter/inputTest.cpp +++ b/src/terminal/adapter/ut_adapter/inputTest.cpp @@ -66,6 +66,32 @@ class Microsoft::Console::VirtualTerminal::InputTest { return WI_IsFlagSet(uiKeystate, SHIFT_PRESSED); } + + static void TestKey(const TerminalInput::OutputType& expected, TerminalInput& input, const unsigned int uiKeystate, const BYTE vkey, const wchar_t wch = 0) + { + Log::Comment(NoThrowString().Format(L"Testing key, state =0x%x, 0x%x", vkey, uiKeystate)); + + INPUT_RECORD irTest = { 0 }; + irTest.EventType = KEY_EVENT; + irTest.Event.KeyEvent.wRepeatCount = 1; + irTest.Event.KeyEvent.bKeyDown = TRUE; + + // If we want to test a key with the Right Alt modifier, we must generate + // an event for the Alt key first, otherwise the modifier will be dropped. + if (WI_IsFlagSet(uiKeystate, RIGHT_ALT_PRESSED)) + { + irTest.Event.KeyEvent.wVirtualKeyCode = VK_MENU; + irTest.Event.KeyEvent.dwControlKeyState = uiKeystate | ENHANCED_KEY; + VERIFY_ARE_EQUAL(TerminalInput::MakeOutput({}), input.HandleKey(irTest)); + } + + irTest.Event.KeyEvent.dwControlKeyState = uiKeystate; + irTest.Event.KeyEvent.wVirtualKeyCode = vkey; + irTest.Event.KeyEvent.uChar.UnicodeChar = wch; + + // Send key into object (will trigger callback and verification) + VERIFY_ARE_EQUAL(expected, input.HandleKey(irTest), L"Verify key was handled if it should have been."); + } }; void InputTest::TerminalInputTests() @@ -86,7 +112,8 @@ void InputTest::TerminalInputTests() irTest.Event.KeyEvent.bKeyDown = TRUE; irTest.Event.KeyEvent.uChar.UnicodeChar = LOWORD(OneCoreSafeMapVirtualKeyW(vkey, MAPVK_VK_TO_CHAR)); - TerminalInput::OutputType expected; + // Unhandled keys are expected to return an empty string. + TerminalInput::OutputType expected = TerminalInput::MakeOutput({}); switch (vkey) { case VK_TAB: @@ -113,6 +140,9 @@ void InputTest::TerminalInputTests() case VK_LEFT: expected = TerminalInput::MakeOutput(L"\x1b[D"); break; + case VK_CLEAR: + expected = TerminalInput::MakeOutput(L"\x1b[E"); + break; case VK_HOME: expected = TerminalInput::MakeOutput(L"\x1b[H"); break; @@ -167,11 +197,36 @@ void InputTest::TerminalInputTests() case VK_F12: expected = TerminalInput::MakeOutput(L"\x1b[24~"); break; + case VK_F13: + expected = TerminalInput::MakeOutput(L"\x1b[25~"); + break; + case VK_F14: + expected = TerminalInput::MakeOutput(L"\x1b[26~"); + break; + case VK_F15: + expected = TerminalInput::MakeOutput(L"\x1b[28~"); + break; + case VK_F16: + expected = TerminalInput::MakeOutput(L"\x1b[29~"); + break; + case VK_F17: + expected = TerminalInput::MakeOutput(L"\x1b[31~"); + break; + case VK_F18: + expected = TerminalInput::MakeOutput(L"\x1b[32~"); + break; + case VK_F19: + expected = TerminalInput::MakeOutput(L"\x1b[33~"); + break; + case VK_F20: + expected = TerminalInput::MakeOutput(L"\x1b[34~"); + break; case VK_CANCEL: expected = TerminalInput::MakeOutput(L"\x3"); break; default: - if (irTest.Event.KeyEvent.uChar.UnicodeChar != 0) + const auto synthesizedKeyPress = vkey == VK_PACKET || vkey == 0; + if (irTest.Event.KeyEvent.uChar.UnicodeChar != 0 || synthesizedKeyPress) { expected = TerminalInput::MakeOutput({ &irTest.Event.KeyEvent.uChar.UnicodeChar, 1 }); } @@ -194,7 +249,7 @@ void InputTest::TerminalInputTests() irTest.Event.KeyEvent.bKeyDown = FALSE; // Send key into object (will trigger callback and verification) - VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(irTest), L"Verify key was NOT handled."); + VERIFY_ARE_EQUAL(TerminalInput::MakeOutput({}), input.HandleKey(irTest), L"Verify output is blank."); } Log::Comment(L"Verify other types of events are not handled/intercepted."); @@ -259,13 +314,7 @@ void InputTest::TerminalInputModifierKeyTests() auto fExpectedKeyHandled = true; auto fModifySequence = false; - INPUT_RECORD irTest = { 0 }; - irTest.EventType = KEY_EVENT; - irTest.Event.KeyEvent.dwControlKeyState = uiKeystate; - irTest.Event.KeyEvent.wRepeatCount = 1; - irTest.Event.KeyEvent.wVirtualKeyCode = vkey; - irTest.Event.KeyEvent.bKeyDown = TRUE; - irTest.Event.KeyEvent.uChar.UnicodeChar = LOWORD(OneCoreSafeMapVirtualKeyW(vkey, MAPVK_VK_TO_CHAR)); + wchar_t ch = LOWORD(OneCoreSafeMapVirtualKeyW(vkey, MAPVK_VK_TO_CHAR)); if (ControlPressed(uiKeystate)) { @@ -282,16 +331,13 @@ void InputTest::TerminalInputModifierKeyTests() } } - TerminalInput::OutputType expected; + // Unhandled keys are expected to return an empty string. + TerminalInput::OutputType expected = TerminalInput::MakeOutput({}); switch (vkey) { case VK_BACK: // Backspace is kinda different from other keys - we'll handle in another test. - case VK_OEM_2: - // VK_OEM_2 is typically the '/?' key continue; - // expected = TerminalInput::MakeOutput(L"\x7f"); - break; case VK_PAUSE: expected = TerminalInput::MakeOutput(L"\x1a"); break; @@ -311,6 +357,10 @@ void InputTest::TerminalInputModifierKeyTests() fModifySequence = true; expected = TerminalInput::MakeOutput(L"\x1b[1;mD"); break; + case VK_CLEAR: + fModifySequence = true; + expected = TerminalInput::MakeOutput(L"\x1b[1;mE"); + break; case VK_HOME: fModifySequence = true; expected = TerminalInput::MakeOutput(L"\x1b[1;mH"); @@ -383,6 +433,55 @@ void InputTest::TerminalInputModifierKeyTests() fModifySequence = true; expected = TerminalInput::MakeOutput(L"\x1b[24;m~"); break; + case VK_F13: + fModifySequence = true; + expected = TerminalInput::MakeOutput(L"\x1b[25;m~"); + break; + case VK_F14: + fModifySequence = true; + expected = TerminalInput::MakeOutput(L"\x1b[26;m~"); + break; + case VK_F15: + fModifySequence = true; + expected = TerminalInput::MakeOutput(L"\x1b[28;m~"); + break; + case VK_F16: + fModifySequence = true; + expected = TerminalInput::MakeOutput(L"\x1b[29;m~"); + break; + case VK_F17: + fModifySequence = true; + expected = TerminalInput::MakeOutput(L"\x1b[31;m~"); + break; + case VK_F18: + fModifySequence = true; + expected = TerminalInput::MakeOutput(L"\x1b[32;m~"); + break; + case VK_F19: + fModifySequence = true; + expected = TerminalInput::MakeOutput(L"\x1b[33;m~"); + break; + case VK_F20: + fModifySequence = true; + expected = TerminalInput::MakeOutput(L"\x1b[34;m~"); + break; + case VK_PACKET: + case 0: + // VK_PACKET and 0 virtual keys are used for synthesized key presses. + expected = TerminalInput::MakeOutput({ &ch, 1 }); + break; + case VK_RETURN: + if (AltPressed(uiKeystate)) + { + const auto str = ControlPressed(uiKeystate) ? L"\x1b\n" : L"\x1b\r"; + expected = TerminalInput::MakeOutput(str); + } + else + { + const auto str = ControlPressed(uiKeystate) ? L"\n" : L"\r"; + expected = TerminalInput::MakeOutput(str); + } + break; case VK_TAB: if (AltPressed(uiKeystate)) { @@ -398,8 +497,35 @@ void InputTest::TerminalInputModifierKeyTests() expected = TerminalInput::MakeOutput(L"\t"); } break; + case VK_OEM_2: + case VK_OEM_3: + case VK_OEM_4: + case VK_OEM_5: + case VK_OEM_6: + case VK_OEM_102: + // OEM keys require special case handling when combined with a Ctrl + // modifier, but otherwise work the same way as regular keys. + if (ControlPressed(uiKeystate)) + { + continue; + } + [[fallthrough]]; default: - auto ch = irTest.Event.KeyEvent.uChar.UnicodeChar; + if (ControlPressed(uiKeystate) && (vkey >= '1' && vkey <= '9')) + { + // The C-# keys get translated into very specific control + // characters that don't play nicely with this test. These keys + // are tested in the CtrlNumTest Test instead. + continue; + } + + if (vkey >= VK_NUMPAD0 && vkey <= VK_NUMPAD9) + { + // Numpad keys have the same complications as numeric keys + // when used with a Ctrl modifier, and with Alt they're used + // for Alt-Numpad composition, so it's best we skip them. + continue; + } // Alt+Key generates [0x1b, Ctrl+key] into the stream // Pressing the control key causes all bits but the 5 least @@ -408,28 +534,25 @@ void InputTest::TerminalInputModifierKeyTests() { const wchar_t buffer[2]{ L'\x1b', gsl::narrow_cast(ch & 0b11111) }; expected = TerminalInput::MakeOutput({ &buffer[0], 2 }); + ch = 0; break; } // Alt+Key generates [0x1b, key] into the stream - if (AltPressed(uiKeystate) && !ControlPressed(uiKeystate) && ch != 0) + if (AltPressed(uiKeystate) && ch != 0) { const wchar_t buffer[2]{ L'\x1b', ch }; expected = TerminalInput::MakeOutput({ &buffer[0], 2 }); + if (ControlPressed(uiKeystate)) + { + ch = 0; + } break; } - if (ControlPressed(uiKeystate) && (vkey >= '1' && vkey <= '9')) - { - // The C-# keys get translated into very specific control - // characters that don't play nicely with this test. These keys - // are tested in the CtrlNumTest Test instead. - continue; - } - if (ch != 0) { - expected = TerminalInput::MakeOutput({ &irTest.Event.KeyEvent.uChar.UnicodeChar, 1 }); + expected = TerminalInput::MakeOutput({ &ch, 1 }); break; } @@ -446,8 +569,7 @@ void InputTest::TerminalInputModifierKeyTests() str[str.size() - 2] = L'1' + (fShift ? 1 : 0) + (fAlt ? 2 : 0) + (fCtrl ? 4 : 0); } - // Send key into object (will trigger callback and verification) - VERIFY_ARE_EQUAL(expected, input.HandleKey(irTest), L"Verify key was handled if it should have been."); + TestKey(expected, input, uiKeystate, vkey, ch); } } @@ -491,22 +613,6 @@ void InputTest::TerminalInputNullKeyTests() VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b\0"sv), input.HandleKey(irTest), L"Verify key was handled if it should have been."); } -static void TestKey(const TerminalInput::OutputType& expected, TerminalInput& input, const unsigned int uiKeystate, const BYTE vkey, const wchar_t wch = 0) -{ - Log::Comment(NoThrowString().Format(L"Testing key, state =0x%x, 0x%x", vkey, uiKeystate)); - - INPUT_RECORD irTest = { 0 }; - irTest.EventType = KEY_EVENT; - irTest.Event.KeyEvent.dwControlKeyState = uiKeystate; - irTest.Event.KeyEvent.wRepeatCount = 1; - irTest.Event.KeyEvent.wVirtualKeyCode = vkey; - irTest.Event.KeyEvent.bKeyDown = TRUE; - irTest.Event.KeyEvent.uChar.UnicodeChar = wch; - - // Send key into object (will trigger callback and verification) - VERIFY_ARE_EQUAL(expected, input.HandleKey(irTest), L"Verify key was handled if it should have been."); -} - void InputTest::DifferentModifiersTest() { Log::Comment(L"Starting test..."); @@ -556,9 +662,9 @@ void InputTest::DifferentModifiersTest() // C-/ -> C-_ -> 0x1f uiKeystate = LEFT_CTRL_PRESSED; vkey = LOBYTE(OneCoreSafeVkKeyScanW(L'/')); - TestKey(TerminalInput::MakeOutput(L"\x1f"), input, uiKeystate, vkey, L'/'); + TestKey(TerminalInput::MakeOutput(L"\x1f"), input, uiKeystate, vkey); uiKeystate = RIGHT_CTRL_PRESSED; - TestKey(TerminalInput::MakeOutput(L"\x1f"), input, uiKeystate, vkey, L'/'); + TestKey(TerminalInput::MakeOutput(L"\x1f"), input, uiKeystate, vkey); // M-/ -> ESC / uiKeystate = LEFT_ALT_PRESSED; @@ -572,26 +678,26 @@ void InputTest::DifferentModifiersTest() Log::Comment(NoThrowString().Format(L"Checking C-?")); // Use SHIFT_PRESSED to force us into differentiating between '/' and '?' vkey = LOBYTE(OneCoreSafeVkKeyScanW(L'?')); - TestKey(TerminalInput::MakeOutput(L"\x7f"), input, SHIFT_PRESSED | LEFT_CTRL_PRESSED, vkey, L'?'); - TestKey(TerminalInput::MakeOutput(L"\x7f"), input, SHIFT_PRESSED | RIGHT_CTRL_PRESSED, vkey, L'?'); + TestKey(TerminalInput::MakeOutput(L"\x7f"), input, SHIFT_PRESSED | LEFT_CTRL_PRESSED, vkey); + TestKey(TerminalInput::MakeOutput(L"\x7f"), input, SHIFT_PRESSED | RIGHT_CTRL_PRESSED, vkey); // C-M-/ -> 0x1b0x1f Log::Comment(NoThrowString().Format(L"Checking C-M-/")); uiKeystate = LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED; vkey = LOBYTE(OneCoreSafeVkKeyScanW(L'/')); - TestKey(TerminalInput::MakeOutput(L"\x1b\x1f"), input, LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED, vkey, L'/'); - TestKey(TerminalInput::MakeOutput(L"\x1b\x1f"), input, RIGHT_CTRL_PRESSED | LEFT_ALT_PRESSED, vkey, L'/'); + TestKey(TerminalInput::MakeOutput(L"\x1b\x1f"), input, LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED, vkey); + TestKey(TerminalInput::MakeOutput(L"\x1b\x1f"), input, RIGHT_CTRL_PRESSED | LEFT_ALT_PRESSED, vkey); // LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED is skipped because that's AltGr - TestKey(TerminalInput::MakeOutput(L"\x1b\x1f"), input, RIGHT_CTRL_PRESSED | RIGHT_ALT_PRESSED, vkey, L'/'); + TestKey(TerminalInput::MakeOutput(L"\x1b\x1f"), input, RIGHT_CTRL_PRESSED | RIGHT_ALT_PRESSED, vkey); // C-M-? -> 0x1b0x7f Log::Comment(NoThrowString().Format(L"Checking C-M-?")); uiKeystate = LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED; vkey = LOBYTE(OneCoreSafeVkKeyScanW(L'?')); - TestKey(TerminalInput::MakeOutput(L"\x1b\x7f"), input, SHIFT_PRESSED | LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED, vkey, L'?'); - TestKey(TerminalInput::MakeOutput(L"\x1b\x7f"), input, SHIFT_PRESSED | RIGHT_CTRL_PRESSED | LEFT_ALT_PRESSED, vkey, L'?'); + TestKey(TerminalInput::MakeOutput(L"\x1b\x7f"), input, SHIFT_PRESSED | LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED, vkey); + TestKey(TerminalInput::MakeOutput(L"\x1b\x7f"), input, SHIFT_PRESSED | RIGHT_CTRL_PRESSED | LEFT_ALT_PRESSED, vkey); // LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED is skipped because that's AltGr - TestKey(TerminalInput::MakeOutput(L"\x1b\x7f"), input, SHIFT_PRESSED | RIGHT_CTRL_PRESSED | RIGHT_ALT_PRESSED, vkey, L'?'); + TestKey(TerminalInput::MakeOutput(L"\x1b\x7f"), input, SHIFT_PRESSED | RIGHT_CTRL_PRESSED | RIGHT_ALT_PRESSED, vkey); } void InputTest::CtrlNumTest() @@ -674,7 +780,7 @@ void InputTest::AutoRepeatModeTest() VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down)); VERIFY_ARE_EQUAL(TerminalInput::MakeOutput({}), input.HandleKey(down)); VERIFY_ARE_EQUAL(TerminalInput::MakeOutput({}), input.HandleKey(down)); - VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(up)); + VERIFY_ARE_EQUAL(TerminalInput::MakeOutput({}), input.HandleKey(up)); Log::Comment(L"Sending repeating keypresses with DECARM enabled."); @@ -682,5 +788,5 @@ void InputTest::AutoRepeatModeTest() VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down)); VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down)); VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"A"), input.HandleKey(down)); - VERIFY_ARE_EQUAL(TerminalInput::MakeUnhandled(), input.HandleKey(up)); + VERIFY_ARE_EQUAL(TerminalInput::MakeOutput({}), input.HandleKey(up)); } diff --git a/src/terminal/adapter/ut_adapter/sources b/src/terminal/adapter/ut_adapter/sources index 2f3850c2d63..8b931860d9a 100644 --- a/src/terminal/adapter/ut_adapter/sources +++ b/src/terminal/adapter/ut_adapter/sources @@ -49,7 +49,6 @@ TARGETLIBS = \ $(ONECOREUAP_EXTERNAL_SDK_LIB_PATH)\d3dcompiler.lib \ $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\api-ms-win-mm-playsound-l1.lib \ $(ONECORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-dwmapi-ext-l1.lib \ - $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-edputil-policy-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-create-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-draw-l1.lib \ @@ -105,12 +104,12 @@ DELAYLOAD = \ DXGI.dll; \ D3D11.dll; \ OLEAUT32.dll; \ + icu.dll; \ api-ms-win-mm-playsound-l1.dll; \ api-ms-win-shcore-scaling-l1.dll; \ api-ms-win-shell-dataobject-l1.dll; \ api-ms-win-shell-namespace-l1.dll; \ ext-ms-win-dwmapi-ext-l1.dll; \ - ext-ms-win-edputil-policy-l1.dll; \ ext-ms-win-usp10-l1.dll; \ ext-ms-win-gdi-dc-l1.dll; \ ext-ms-win-gdi-dc-create-l1.dll; \ @@ -144,7 +143,6 @@ DLOAD_ERROR_HANDLER = kernelbase #INCLUDES = $(INCLUDES); \ # ..\..\..\inc; \ -# $(SDKTOOLS_INC_PATH)\WexTest\Cue; \ # #SOURCES = $(SOURCES) \ # diff --git a/src/terminal/input/mouseInput.cpp b/src/terminal/input/mouseInput.cpp index ddcb81b7e9b..596df0360e1 100644 --- a/src/terminal/input/mouseInput.cpp +++ b/src/terminal/input/mouseInput.cpp @@ -11,12 +11,6 @@ using namespace Microsoft::Console::VirtualTerminal; static constexpr int s_MaxDefaultCoordinate = 94; -// Alternate scroll sequences -static constexpr std::wstring_view CursorUpSequence{ L"\x1b[A" }; -static constexpr std::wstring_view CursorDownSequence{ L"\x1b[B" }; -static constexpr std::wstring_view ApplicationUpSequence{ L"\x1bOA" }; -static constexpr std::wstring_view ApplicationDownSequence{ L"\x1bOB" }; - // Routine Description: // - Determines if the input windows message code describes a button event // (left, middle, right button and any of up, down or double click) @@ -147,11 +141,11 @@ constexpr unsigned int TerminalInput::s_GetPressedButton(const MouseButtonState // - modifierKeyState - the modifier keys _in console format_ // - delta - scroll wheel delta // Return value: -// - the int representing the equivalent X button encoding. -static constexpr int _windowsButtonToXEncoding(const unsigned int button, - const bool isHover, - const short modifierKeyState, - const short delta) noexcept +// - the character representing the equivalent X button encoding. +static constexpr wchar_t _windowsButtonToXEncoding(const unsigned int button, + const bool isHover, + const short modifierKeyState, + const short delta) noexcept { auto xvalue = 0; switch (button) @@ -191,7 +185,7 @@ static constexpr int _windowsButtonToXEncoding(const unsigned int button, WI_UpdateFlag(xvalue, 0x08, WI_IsAnyFlagSet(modifierKeyState, ALT_PRESSED)); WI_UpdateFlag(xvalue, 0x10, WI_IsAnyFlagSet(modifierKeyState, CTRL_PRESSED)); - return xvalue; + return gsl::narrow_cast(L' ' + xvalue); } // Routine Description: @@ -270,9 +264,9 @@ static constexpr til::point _winToVTCoord(const til::point coordWinCoordinate) n // - sCoordinateValue - the value to encode. // Return value: // - the encoded value. -static constexpr til::CoordType _encodeDefaultCoordinate(const til::CoordType sCoordinateValue) noexcept +static constexpr wchar_t _encodeDefaultCoordinate(const til::CoordType sCoordinateValue) noexcept { - return sCoordinateValue + 32; + return gsl::narrow_cast(sCoordinateValue + 32); } // Routine Description: @@ -334,11 +328,6 @@ TerminalInput::OutputType TerminalInput::HandleMouse(const til::point position, _mouseInputState.accumulatedDelta = 0; } - if (ShouldSendAlternateScroll(button, delta)) - { - return _makeAlternateScrollOutput(delta); - } - if (IsTrackingMouseInput()) { // isHover is only true for WM_MOUSEMOVE events @@ -392,6 +381,11 @@ TerminalInput::OutputType TerminalInput::HandleMouse(const til::point position, } } + if (ShouldSendAlternateScroll(button, delta)) + { + return _makeAlternateScrollOutput(delta); + } + return {}; } @@ -415,12 +409,9 @@ TerminalInput::OutputType TerminalInput::_GenerateDefaultSequence(const til::poi const auto vtCoords = _winToVTCoord(position); const auto encodedX = _encodeDefaultCoordinate(vtCoords.x); const auto encodedY = _encodeDefaultCoordinate(vtCoords.y); + const auto encodedButton = _windowsButtonToXEncoding(button, isHover, modifierKeyState, delta); - StringType format{ L"\x1b[Mbxy" }; - til::at(format, 3) = gsl::narrow_cast(L' ' + _windowsButtonToXEncoding(button, isHover, modifierKeyState, delta)); - til::at(format, 4) = gsl::narrow_cast(encodedX); - til::at(format, 5) = gsl::narrow_cast(encodedY); - return format; + return fmt::format(FMT_COMPILE(L"{}M{}{}{}"), _csi, encodedButton, encodedX, encodedY); } return {}; @@ -458,13 +449,9 @@ TerminalInput::OutputType TerminalInput::_GenerateUtf8Sequence(const til::point const auto vtCoords = _winToVTCoord(position); const auto encodedX = _encodeDefaultCoordinate(vtCoords.x); const auto encodedY = _encodeDefaultCoordinate(vtCoords.y); + const auto encodedButton = _windowsButtonToXEncoding(button, isHover, modifierKeyState, delta); - StringType format{ L"\x1b[Mbxy" }; - // The short cast is safe because we know s_WindowsButtonToXEncoding never returns more than xff - til::at(format, 3) = gsl::narrow_cast(L' ' + _windowsButtonToXEncoding(button, isHover, modifierKeyState, delta)); - til::at(format, 4) = gsl::narrow_cast(encodedX); - til::at(format, 5) = gsl::narrow_cast(encodedY); - return format; + return fmt::format(FMT_COMPILE(L"{}M{}{}{}"), _csi, encodedButton, encodedX, encodedY); } return {}; @@ -487,7 +474,7 @@ TerminalInput::OutputType TerminalInput::_GenerateSGRSequence(const til::point p // Format for SGR events is: // "\x1b[<%d;%d;%d;%c", xButton, x+1, y+1, fButtonDown? 'M' : 'm' const auto xbutton = _windowsButtonToSGREncoding(button, isHover, modifierKeyState, delta); - return fmt::format(FMT_COMPILE(L"\x1b[<{};{};{}{}"), xbutton, position.x + 1, position.y + 1, isDown ? L'M' : L'm'); + return fmt::format(FMT_COMPILE(L"{}<{};{};{}{}"), _csi, xbutton, position.x + 1, position.y + 1, isDown ? L'M' : L'm'); } // Routine Description: @@ -515,10 +502,10 @@ TerminalInput::OutputType TerminalInput::_makeAlternateScrollOutput(const short { if (delta > 0) { - return MakeOutput(_inputMode.test(Mode::CursorKey) ? ApplicationUpSequence : CursorUpSequence); + return MakeOutput(_keyMap.at(VK_UP)); } else { - return MakeOutput(_inputMode.test(Mode::CursorKey) ? ApplicationDownSequence : CursorDownSequence); + return MakeOutput(_keyMap.at(VK_DOWN)); } } diff --git a/src/terminal/input/terminalInput.cpp b/src/terminal/input/terminalInput.cpp index 855ba74b28a..7c09ef46155 100644 --- a/src/terminal/input/terminalInput.cpp +++ b/src/terminal/input/terminalInput.cpp @@ -10,226 +10,26 @@ #include "../../interactivity/inc/VtApiRedirection.hpp" #include "../types/inc/IInputEvent.hpp" +using namespace std::string_literals; using namespace Microsoft::Console::VirtualTerminal; -struct TermKeyMap +namespace { - const WORD vkey; - const std::wstring_view sequence; - const DWORD modifiers; - - constexpr TermKeyMap(WORD vkey, std::wstring_view sequence) noexcept : - TermKeyMap(vkey, 0, sequence) - { - } + // These modifier constants are added to the virtual key code + // to produce a lookup value for determining the appropriate + // VT sequence for a particular modifier + key combination. + constexpr int VTModifier(const int m) { return m << 8; } + constexpr auto Unmodified = VTModifier(0); + constexpr auto Shift = VTModifier(1); + constexpr auto Alt = VTModifier(2); + constexpr auto Ctrl = VTModifier(4); + constexpr auto Enhanced = VTModifier(8); +} - constexpr TermKeyMap(const WORD vkey, const DWORD modifiers, std::wstring_view sequence) noexcept : - vkey(vkey), - sequence(sequence), - modifiers(modifiers) - { - } -}; - -// See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys -// For the source for these tables. -// Also refer to the values in terminfo for kcub1, kcud1, kcuf1, kcuu1, kend, khome. -// the 'xterm' setting lists the application mode versions of these sequences. -static constexpr std::array s_cursorKeysNormalMapping = { - TermKeyMap{ VK_UP, L"\x1b[A" }, - TermKeyMap{ VK_DOWN, L"\x1b[B" }, - TermKeyMap{ VK_RIGHT, L"\x1b[C" }, - TermKeyMap{ VK_LEFT, L"\x1b[D" }, - TermKeyMap{ VK_HOME, L"\x1b[H" }, - TermKeyMap{ VK_END, L"\x1b[F" }, -}; - -static constexpr std::array s_cursorKeysApplicationMapping{ - TermKeyMap{ VK_UP, L"\x1bOA" }, - TermKeyMap{ VK_DOWN, L"\x1bOB" }, - TermKeyMap{ VK_RIGHT, L"\x1bOC" }, - TermKeyMap{ VK_LEFT, L"\x1bOD" }, - TermKeyMap{ VK_HOME, L"\x1bOH" }, - TermKeyMap{ VK_END, L"\x1bOF" }, -}; - -static constexpr std::array s_cursorKeysVt52Mapping{ - TermKeyMap{ VK_UP, L"\033A" }, - TermKeyMap{ VK_DOWN, L"\033B" }, - TermKeyMap{ VK_RIGHT, L"\033C" }, - TermKeyMap{ VK_LEFT, L"\033D" }, - TermKeyMap{ VK_HOME, L"\033H" }, - TermKeyMap{ VK_END, L"\033F" }, -}; - -static constexpr std::array s_keypadNumericMapping{ - TermKeyMap{ VK_TAB, L"\x09" }, - TermKeyMap{ VK_PAUSE, L"\x1a" }, - TermKeyMap{ VK_ESCAPE, L"\x1b" }, - TermKeyMap{ VK_INSERT, L"\x1b[2~" }, - TermKeyMap{ VK_DELETE, L"\x1b[3~" }, - TermKeyMap{ VK_PRIOR, L"\x1b[5~" }, - TermKeyMap{ VK_NEXT, L"\x1b[6~" }, - TermKeyMap{ VK_F1, L"\x1bOP" }, // also \x1b[11~, PuTTY uses \x1b\x1b[A - TermKeyMap{ VK_F2, L"\x1bOQ" }, // also \x1b[12~, PuTTY uses \x1b\x1b[B - TermKeyMap{ VK_F3, L"\x1bOR" }, // also \x1b[13~, PuTTY uses \x1b\x1b[C - TermKeyMap{ VK_F4, L"\x1bOS" }, // also \x1b[14~, PuTTY uses \x1b\x1b[D - TermKeyMap{ VK_F5, L"\x1b[15~" }, - TermKeyMap{ VK_F6, L"\x1b[17~" }, - TermKeyMap{ VK_F7, L"\x1b[18~" }, - TermKeyMap{ VK_F8, L"\x1b[19~" }, - TermKeyMap{ VK_F9, L"\x1b[20~" }, - TermKeyMap{ VK_F10, L"\x1b[21~" }, - TermKeyMap{ VK_F11, L"\x1b[23~" }, - TermKeyMap{ VK_F12, L"\x1b[24~" }, -}; - -//Application mode - Some terminals support both a "Numeric" input mode, and an "Application" mode -// The standards vary on what each key translates to in the various modes, so I tried to make it as close -// to the VT220 standard as possible. -// The notable difference is in the arrow keys, which in application mode translate to "^[0A" (etc) as opposed to "^[[A" in numeric -//Some very unclear documentation at http://invisible-island.net/xterm/ctlseqs/ctlseqs.html also suggests alternate encodings for F1-4 -// which I have left in the comments on those entries as something to possibly add in the future, if need be. -//It seems to me as though this was used for early numpad implementations, where presently numlock would enable -// "numeric" mode, outputting the numbers on the keys, while "application" mode does things like pgup/down, arrow keys, etc. -//These keys aren't translated at all in numeric mode, so I figured I'd leave them out of the numeric table. -static constexpr std::array s_keypadApplicationMapping{ - TermKeyMap{ VK_TAB, L"\x09" }, - TermKeyMap{ VK_PAUSE, L"\x1a" }, - TermKeyMap{ VK_ESCAPE, L"\x1b" }, - TermKeyMap{ VK_INSERT, L"\x1b[2~" }, - TermKeyMap{ VK_DELETE, L"\x1b[3~" }, - TermKeyMap{ VK_PRIOR, L"\x1b[5~" }, - TermKeyMap{ VK_NEXT, L"\x1b[6~" }, - TermKeyMap{ VK_F1, L"\x1bOP" }, // also \x1b[11~, PuTTY uses \x1b\x1b[A - TermKeyMap{ VK_F2, L"\x1bOQ" }, // also \x1b[12~, PuTTY uses \x1b\x1b[B - TermKeyMap{ VK_F3, L"\x1bOR" }, // also \x1b[13~, PuTTY uses \x1b\x1b[C - TermKeyMap{ VK_F4, L"\x1bOS" }, // also \x1b[14~, PuTTY uses \x1b\x1b[D - TermKeyMap{ VK_F5, L"\x1b[15~" }, - TermKeyMap{ VK_F6, L"\x1b[17~" }, - TermKeyMap{ VK_F7, L"\x1b[18~" }, - TermKeyMap{ VK_F8, L"\x1b[19~" }, - TermKeyMap{ VK_F9, L"\x1b[20~" }, - TermKeyMap{ VK_F10, L"\x1b[21~" }, - TermKeyMap{ VK_F11, L"\x1b[23~" }, - TermKeyMap{ VK_F12, L"\x1b[24~" }, - // The numpad has a variety of mappings, none of which seem standard or really configurable by the OS. - // See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys - // to see just how convoluted this all is. - // PuTTY uses a set of mappings that don't work in ViM without reamapping them back to the numpad - // (see http://vim.wikia.com/wiki/PuTTY_numeric_keypad_mappings#Comments) - // I think the best solution is to just not do any for the time being. - // Putty also provides configuration for choosing which of the 5 mappings it has through the settings, which is more work than we can manage now. - // TermKeyMap{ VK_MULTIPLY, L"\x1bOj" }, // PuTTY: \x1bOR (I believe putty is treating the top row of the numpad as PF1-PF4) - // TermKeyMap{ VK_ADD, L"\x1bOk" }, // PuTTY: \x1bOl, \x1bOm (with shift) - // TermKeyMap{ VK_SEPARATOR, L"\x1bOl" }, // ? I'm not sure which key this is... - // TermKeyMap{ VK_SUBTRACT, L"\x1bOm" }, // \x1bOS - // TermKeyMap{ VK_DECIMAL, L"\x1bOn" }, // \x1bOn - // TermKeyMap{ VK_DIVIDE, L"\x1bOo" }, // \x1bOQ - // TermKeyMap{ VK_NUMPAD0, L"\x1bOp" }, - // TermKeyMap{ VK_NUMPAD1, L"\x1bOq" }, - // TermKeyMap{ VK_NUMPAD2, L"\x1bOr" }, - // TermKeyMap{ VK_NUMPAD3, L"\x1bOs" }, - // TermKeyMap{ VK_NUMPAD4, L"\x1bOt" }, - // TermKeyMap{ VK_NUMPAD5, L"\x1bOu" }, // \x1b0E - // TermKeyMap{ VK_NUMPAD5, L"\x1bOE" }, // PuTTY \x1b[G - // TermKeyMap{ VK_NUMPAD6, L"\x1bOv" }, - // TermKeyMap{ VK_NUMPAD7, L"\x1bOw" }, - // TermKeyMap{ VK_NUMPAD8, L"\x1bOx" }, - // TermKeyMap{ VK_NUMPAD9, L"\x1bOy" }, - // TermKeyMap{ '=', L"\x1bOX" }, // I've also seen these codes mentioned in some documentation, - // TermKeyMap{ VK_SPACE, L"\x1bO " }, // but I wasn't really sure if they should be included or not... - // TermKeyMap{ VK_TAB, L"\x1bOI" }, // So I left them here as a reference just in case. -}; - -static constexpr std::array s_keypadVt52Mapping{ - TermKeyMap{ VK_TAB, L"\x09" }, - TermKeyMap{ VK_PAUSE, L"\x1a" }, - TermKeyMap{ VK_ESCAPE, L"\x1b" }, - TermKeyMap{ VK_INSERT, L"\x1b[2~" }, - TermKeyMap{ VK_DELETE, L"\x1b[3~" }, - TermKeyMap{ VK_PRIOR, L"\x1b[5~" }, - TermKeyMap{ VK_NEXT, L"\x1b[6~" }, - TermKeyMap{ VK_F1, L"\x1bP" }, - TermKeyMap{ VK_F2, L"\x1bQ" }, - TermKeyMap{ VK_F3, L"\x1bR" }, - TermKeyMap{ VK_F4, L"\x1bS" }, - TermKeyMap{ VK_F5, L"\x1b[15~" }, - TermKeyMap{ VK_F6, L"\x1b[17~" }, - TermKeyMap{ VK_F7, L"\x1b[18~" }, - TermKeyMap{ VK_F8, L"\x1b[19~" }, - TermKeyMap{ VK_F9, L"\x1b[20~" }, - TermKeyMap{ VK_F10, L"\x1b[21~" }, - TermKeyMap{ VK_F11, L"\x1b[23~" }, - TermKeyMap{ VK_F12, L"\x1b[24~" }, -}; - -// Sequences to send when a modifier is pressed with any of these keys -// Basically, the 'm' will be replaced with a character indicating which -// modifier keys are pressed. -static constexpr std::array s_modifierKeyMapping{ - TermKeyMap{ VK_UP, L"\x1b[1;mA" }, - TermKeyMap{ VK_DOWN, L"\x1b[1;mB" }, - TermKeyMap{ VK_RIGHT, L"\x1b[1;mC" }, - TermKeyMap{ VK_LEFT, L"\x1b[1;mD" }, - TermKeyMap{ VK_HOME, L"\x1b[1;mH" }, - TermKeyMap{ VK_END, L"\x1b[1;mF" }, - TermKeyMap{ VK_F1, L"\x1b[1;mP" }, - TermKeyMap{ VK_F2, L"\x1b[1;mQ" }, - TermKeyMap{ VK_F3, L"\x1b[1;mR" }, - TermKeyMap{ VK_F4, L"\x1b[1;mS" }, - TermKeyMap{ VK_INSERT, L"\x1b[2;m~" }, - TermKeyMap{ VK_DELETE, L"\x1b[3;m~" }, - TermKeyMap{ VK_PRIOR, L"\x1b[5;m~" }, - TermKeyMap{ VK_NEXT, L"\x1b[6;m~" }, - TermKeyMap{ VK_F5, L"\x1b[15;m~" }, - TermKeyMap{ VK_F6, L"\x1b[17;m~" }, - TermKeyMap{ VK_F7, L"\x1b[18;m~" }, - TermKeyMap{ VK_F8, L"\x1b[19;m~" }, - TermKeyMap{ VK_F9, L"\x1b[20;m~" }, - TermKeyMap{ VK_F10, L"\x1b[21;m~" }, - TermKeyMap{ VK_F11, L"\x1b[23;m~" }, - TermKeyMap{ VK_F12, L"\x1b[24;m~" }, - // Ubuntu's inputrc also defines \x1b[5C, \x1b\x1bC (and D) as 'forward/backward-word' mappings - // I believe '\x1b\x1bC' is listed because the C1 ESC (x9B) gets encoded as - // \xC2\x9B, but then translated to \x1b\x1b if the C1 codepoint isn't supported by the current encoding -}; - -// Sequences to send when a modifier is pressed with any of these keys -// These sequences are not later updated to encode the modifier state in the -// sequence itself, they are just weird exceptional cases to the general -// rules above. -static constexpr std::array s_simpleModifiedKeyMapping{ - TermKeyMap{ VK_TAB, CTRL_PRESSED, L"\t" }, - TermKeyMap{ VK_TAB, SHIFT_PRESSED, L"\x1b[Z" }, - TermKeyMap{ VK_DIVIDE, CTRL_PRESSED, L"\x1F" }, - - // GH#3507 - We should also be encoding Ctrl+# according to the following table: - // https://vt100.net/docs/vt220-rm/table3-5.html - // * 1 and 9 do not send any special characters, but they _should_ send - // through the character unmodified. - // * 0 doesn't seem to send even an unmodified '0' through. - // * Ctrl+2 is already special-cased below in `HandleKey`, so it's not - // included here. - TermKeyMap{ static_cast('1'), CTRL_PRESSED, L"1" }, - // TermKeyMap{ static_cast('2'), CTRL_PRESSED, L"\x00" }, - TermKeyMap{ static_cast('3'), CTRL_PRESSED, L"\x1B" }, - TermKeyMap{ static_cast('4'), CTRL_PRESSED, L"\x1C" }, - TermKeyMap{ static_cast('5'), CTRL_PRESSED, L"\x1D" }, - TermKeyMap{ static_cast('6'), CTRL_PRESSED, L"\x1E" }, - TermKeyMap{ static_cast('7'), CTRL_PRESSED, L"\x1F" }, - TermKeyMap{ static_cast('8'), CTRL_PRESSED, L"\x7F" }, - TermKeyMap{ static_cast('9'), CTRL_PRESSED, L"9" }, - - // These two are not implemented here, because they are system keys. - // TermKeyMap{ VK_TAB, ALT_PRESSED, L""}, This is the Windows system shortcut for switching windows. - // TermKeyMap{ VK_ESCAPE, ALT_PRESSED, L""}, This is another Windows system shortcut for switching windows. -}; - -const wchar_t* const CTRL_SLASH_SEQUENCE = L"\x1f"; -const wchar_t* const CTRL_QUESTIONMARK_SEQUENCE = L"\x7F"; -const wchar_t* const CTRL_ALT_SLASH_SEQUENCE = L"\x1b\x1f"; -const wchar_t* const CTRL_ALT_QUESTIONMARK_SEQUENCE = L"\x1b\x7F"; +TerminalInput::TerminalInput() noexcept +{ + _initKeyboardMap(); +} void TerminalInput::SetInputMode(const Mode mode, const bool enabled) noexcept { @@ -250,6 +50,14 @@ void TerminalInput::SetInputMode(const Mode mode, const bool enabled) noexcept } _inputMode.set(mode, enabled); + + // If we've changed one of the modes that alter the VT input sequences, + // we'll need to regenerate our keyboard map. + static constexpr auto keyMapModes = til::enumset{ Mode::LineFeed, Mode::Ansi, Mode::Keypad, Mode::CursorKey, Mode::BackarrowKey }; + if (keyMapModes.test(mode)) + { + _initKeyboardMap(); + } } bool TerminalInput::GetInputMode(const Mode mode) const noexcept @@ -259,9 +67,10 @@ bool TerminalInput::GetInputMode(const Mode mode) const noexcept void TerminalInput::ResetInputModes() noexcept { - _inputMode = { Mode::Ansi, Mode::AutoRepeat }; + _inputMode = { Mode::Ansi, Mode::AutoRepeat, Mode::AlternateScroll }; _mouseInputState.lastPos = { -1, -1 }; _mouseInputState.lastButton = 0; + _initKeyboardMap(); } void TerminalInput::ForceDisableWin32InputMode(const bool win32InputMode) noexcept @@ -269,188 +78,6 @@ void TerminalInput::ForceDisableWin32InputMode(const bool win32InputMode) noexce _forceDisableWin32InputMode = win32InputMode; } -static std::span _getKeyMapping(const KEY_EVENT_RECORD& keyEvent, const bool ansiMode, const bool cursorApplicationMode, const bool keypadApplicationMode) noexcept -{ - // Cursor keys: VK_END, VK_HOME, VK_LEFT, VK_UP, VK_RIGHT, VK_DOWN - const auto isCursorKey = keyEvent.wVirtualKeyCode >= VK_END && keyEvent.wVirtualKeyCode <= VK_DOWN; - - if (ansiMode) - { - if (isCursorKey) - { - if (cursorApplicationMode) - { - return s_cursorKeysApplicationMapping; - } - else - { - return s_cursorKeysNormalMapping; - } - } - else - { - if (keypadApplicationMode) - { - return s_keypadApplicationMapping; - } - else - { - return s_keypadNumericMapping; - } - } - } - else - { - if (isCursorKey) - { - return s_cursorKeysVt52Mapping; - } - else - { - return s_keypadVt52Mapping; - } - } -} - -// Routine Description: -// - Searches the keyMapping for a entry corresponding to this key event, and returns it. -// Arguments: -// - keyEvent - Key event to translate -// - keyMapping - Array of key mappings to search -// Return Value: -// - Has value if there was a match to a key translation. -static std::optional _searchKeyMapping(const KEY_EVENT_RECORD& keyEvent, - std::span keyMapping) noexcept -{ - for (auto& map : keyMapping) - { - if (map.vkey == keyEvent.wVirtualKeyCode) - { - // If the mapping has no modifiers set, then it doesn't really care - // what the modifiers are on the key. The caller will likely do - // something with them. - // However, if there are modifiers set, then we only want to match - // if the key's modifiers are the same as the modifiers in the - // mapping. - auto modifiersMatch = WI_AreAllFlagsClear(map.modifiers, MOD_PRESSED); - if (!modifiersMatch) - { - // The modifier mapping expects certain modifier keys to be - // pressed. Check those as well. - modifiersMatch = - WI_IsAnyFlagSet(map.modifiers, SHIFT_PRESSED) == WI_IsAnyFlagSet(keyEvent.dwControlKeyState, SHIFT_PRESSED) && - WI_IsAnyFlagSet(map.modifiers, ALT_PRESSED) == WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED) && - WI_IsAnyFlagSet(map.modifiers, CTRL_PRESSED) == WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED); - } - - if (modifiersMatch) - { - return map; - } - } - } - return std::nullopt; -} - -// Searches the s_modifierKeyMapping for a entry corresponding to this key event. -// Changes the second to last byte to correspond to the currently pressed modifier keys. -TerminalInput::OutputType TerminalInput::_searchWithModifier(const KEY_EVENT_RECORD& keyEvent) -{ - if (const auto match = _searchKeyMapping(keyEvent, s_modifierKeyMapping)) - { - const auto& v = match.value(); - if (!v.sequence.empty()) - { - const auto shift = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, SHIFT_PRESSED); - const auto alt = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED); - const auto ctrl = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED); - StringType str{ v.sequence }; - str.at(str.size() - 2) = L'1' + (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0); - return str; - } - } - - // We didn't find the key in the map of modified keys that need editing, - // maybe it's in the other map of modified keys with sequences that - // don't need editing before sending. - else if (const auto match2 = _searchKeyMapping(keyEvent, s_simpleModifiedKeyMapping)) - { - // This mapping doesn't need to be changed at all. - return MakeOutput(match2->sequence); - } - else - { - // One last check: - // * C-/ is supposed to be ^_ (the C0 character US) - // * C-? is supposed to be DEL - // * C-M-/ is supposed to be ^[^_ - // * C-M-? is supposed to be ^[^? - // - // But this whole scenario is tricky. '/' is not the same VKEY on - // all keyboards. On USASCII keyboards, '/' and '?' share the _same_ - // key. So we have to figure out the vkey at runtime, and we have to - // determine if the key that was pressed was '?' with some - // modifiers, or '/' with some modifiers. - // - // These translations are not in s_simpleModifiedKeyMapping, because - // the aforementioned fact that they aren't the same VKEY on all - // keyboards. - // - // See GH#3079 for details. - // Also see https://github.com/microsoft/terminal/pull/4947#issuecomment-600382856 - - // VkKeyScan will give us both the Vkey of the key needed for this - // character, and the modifiers the user might need to press to get - // this character. - const auto slashKeyScan = OneCoreSafeVkKeyScanW(L'/'); // On USASCII: 0x00bf - const auto questionMarkKeyScan = OneCoreSafeVkKeyScanW(L'?'); //On USASCII: 0x01bf - - const auto slashVkey = LOBYTE(slashKeyScan); - const auto questionMarkVkey = LOBYTE(questionMarkKeyScan); - - const auto ctrl = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED); - const auto alt = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED); - const auto shift = WI_IsAnyFlagSet(keyEvent.dwControlKeyState, SHIFT_PRESSED); - - // From the KeyEvent we're translating, synthesize the equivalent VkKeyScan result - const auto vkey = keyEvent.wVirtualKeyCode; - const short keyScanFromEvent = vkey | - (shift ? 0x100 : 0) | - (ctrl ? 0x200 : 0) | - (alt ? 0x400 : 0); - - // Make sure the VKEY is an _exact_ match, and that the modifier - // bits also match. This handles the hypothetical case we get a - // keyscan back that's ctrl+alt+some_random_VK, and some_random_VK - // has bits that are a superset of the bits set for question mark. - const auto wasQuestionMark = vkey == questionMarkVkey && WI_AreAllFlagsSet(keyScanFromEvent, questionMarkKeyScan); - const auto wasSlash = vkey == slashVkey && WI_AreAllFlagsSet(keyScanFromEvent, slashKeyScan); - - // If the key pressed was exactly the ? key, then try to send the - // appropriate sequence for a modified '?'. Otherwise, check if this - // was a modified '/' keypress. These mappings don't need to be - // changed at all. - if ((ctrl && alt) && wasQuestionMark) - { - return MakeOutput(CTRL_ALT_QUESTIONMARK_SEQUENCE); - } - else if (ctrl && wasQuestionMark) - { - return MakeOutput(CTRL_QUESTIONMARK_SEQUENCE); - } - else if ((ctrl && alt) && wasSlash) - { - return MakeOutput(CTRL_ALT_SLASH_SEQUENCE); - } - else if (ctrl && wasSlash) - { - return MakeOutput(CTRL_SLASH_SEQUENCE); - } - } - - return MakeUnhandled(); -} - TerminalInput::OutputType TerminalInput::MakeUnhandled() noexcept { return {}; @@ -483,7 +110,7 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event) return MakeUnhandled(); } - auto keyEvent = event.Event.KeyEvent; + const auto keyEvent = event.Event.KeyEvent; // GH#4999 - If we're in win32-input mode, skip straight to doing that. // Since this mode handles all types of key events, do nothing else. @@ -493,8 +120,12 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event) return _makeWin32Output(keyEvent); } + const auto controlKeyState = _trackControlKeyState(keyEvent); + const auto virtualKeyCode = keyEvent.wVirtualKeyCode; + auto unicodeChar = keyEvent.uChar.UnicodeChar; + // Check if this key matches the last recorded key code. - const auto matchingLastKeyPress = _lastVirtualKeyCode == keyEvent.wVirtualKeyCode; + const auto matchingLastKeyPress = _lastVirtualKeyCode == virtualKeyCode; // Only need to handle key down. See raw key handler (see RawReadWaitRoutine in stream.cpp) if (!keyEvent.bKeyDown) @@ -504,7 +135,35 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event) { _lastVirtualKeyCode = std::nullopt; } - return MakeUnhandled(); + // If NumLock is on, and this is an Alt release with a unicode char, + // it must be the generated character from an Alt-Numpad composition. + if (WI_IsFlagSet(controlKeyState, NUMLOCK_ON) && virtualKeyCode == VK_MENU && unicodeChar != 0) + { + return MakeOutput({ &unicodeChar, 1 }); + } + // Otherwise we should return an empty string here to prevent unwanted + // characters being transmitted by the release event. + return _makeNoOutput(); + } + + // Unpaired surrogates are no good -> early return. + if (til::is_leading_surrogate(unicodeChar)) + { + _leadingSurrogate = unicodeChar; + return _makeNoOutput(); + } + // Using a scope_exit ensures that a previous leading surrogate is forgotten + // even if the KEY_EVENT that followed didn't end up calling _makeCharOutput. + const auto leadingSurrogateReset = wil::scope_exit([&]() { + _leadingSurrogate = 0; + }); + + // If this is a VK_PACKET or 0 virtual key, it's likely a synthesized + // keyboard event, so the UnicodeChar is transmitted as is. This must be + // handled before the Auto Repeat test, other we'll end up dropping chars. + if (virtualKeyCode == VK_PACKET || virtualKeyCode == 0) + { + return _makeCharOutput(unicodeChar); } // If this is a repeat of the last recorded key press, and Auto Repeat Mode @@ -513,209 +172,467 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event) { // Note that we must return an empty string here to imply that we've handled // the event, otherwise the key press can still end up being submitted. - return MakeOutput({}); + return _makeNoOutput(); } - _lastVirtualKeyCode = keyEvent.wVirtualKeyCode; + _lastVirtualKeyCode = virtualKeyCode; - // The VK_BACK key depends on the state of Backarrow Key mode (DECBKM). - // If the mode is set, we should send BS. If reset, we should send DEL. - if (keyEvent.wVirtualKeyCode == VK_BACK) + // If this is a modifier, it won't produce output, so we can return early. + if (virtualKeyCode >= VK_SHIFT && virtualKeyCode <= VK_MENU) { - // The Ctrl modifier reverses the interpretation of DECBKM. - const auto backarrowMode = _inputMode.test(Mode::BackarrowKey) != WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED); - const auto seq = backarrowMode ? L'\x08' : L'\x7f'; - // The Alt modifier adds an escape prefix. - if (WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED)) - { - return _makeEscapedOutput(seq); - } - else - { - return MakeOutput({ &seq, 1 }); - } + return _makeNoOutput(); } - // When the Line Feed mode is set, a VK_RETURN key should send both CR and LF. - // When reset, we fall through to the default behavior, which is to send just - // CR, or when the Ctrl modifier is pressed, just LF. - if (keyEvent.wVirtualKeyCode == VK_RETURN && _inputMode.test(Mode::LineFeed)) + // Keyboards that have an AltGr key will generate both a RightAlt key press + // and a fake LeftCtrl key press. In order to support key combinations where + // the Ctrl key is manually pressed in addition to the AltGr key, we have to + // be able to detect when the Ctrl key isn't genuine. We do so by tracking + // the time between the Alt and Ctrl key presses, and only consider the Ctrl + // key to really be pressed if the difference is more than 50ms. + auto leftCtrlIsReallyPressed = WI_IsFlagSet(controlKeyState, LEFT_CTRL_PRESSED); + if (WI_AreAllFlagsSet(controlKeyState, LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)) { - return MakeOutput(L"\r\n"); + const auto timeBetweenCtrlAlt = _lastRightAltTime > _lastLeftCtrlTime ? + _lastRightAltTime - _lastLeftCtrlTime : + _lastLeftCtrlTime - _lastRightAltTime; + leftCtrlIsReallyPressed = timeBetweenCtrlAlt > 50; } - // Many keyboard layouts have an AltGr key, which makes widely used characters accessible. - // For instance on a German keyboard layout "[" is written by pressing AltGr+8. - // Furthermore Ctrl+Alt is traditionally treated as an alternative way to AltGr by Windows. - // When AltGr is pressed, the caller needs to make sure to send us a pretranslated character in uChar.UnicodeChar. - // --> Strip out the AltGr flags, in order for us to not step into the Alt/Ctrl conditions below. - if (WI_AreAllFlagsSet(keyEvent.dwControlKeyState, LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)) + const auto ctrlIsPressed = WI_IsAnyFlagSet(controlKeyState, CTRL_PRESSED); + const auto ctrlIsReallyPressed = leftCtrlIsReallyPressed || WI_IsFlagSet(controlKeyState, RIGHT_CTRL_PRESSED); + const auto shiftIsPressed = WI_IsFlagSet(controlKeyState, SHIFT_PRESSED); + const auto altIsPressed = WI_IsAnyFlagSet(controlKeyState, ALT_PRESSED); + const auto altGrIsPressed = altIsPressed && ctrlIsPressed; + + // If it's a numeric keypad key, and Alt is pressed (but not Ctrl), then + // this is an Alt-Numpad composition and we should ignore these keys. The + // generated character will be transmitted when the Alt is released. + if (virtualKeyCode >= VK_NUMPAD0 && virtualKeyCode <= VK_NUMPAD9 && altIsPressed && !ctrlIsPressed) { - WI_ClearAllFlags(keyEvent.dwControlKeyState, LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED); + return _makeNoOutput(); } - // The Alt modifier initiates a so called "escape sequence". - // See: https://en.wikipedia.org/wiki/ANSI_escape_code#Escape_sequences - // See: ECMA-48, section 5.3, http://www.ecma-international.org/publications/standards/Ecma-048.htm - // - // This section in particular handles Alt+Ctrl combinations though. - // The Ctrl modifier causes all of the char code's bits except - // for the 5 least significant ones to be zeroed out. - if (WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED) && WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED)) + // The only enhanced key we care about is the Return key, because that + // indicates that it's the key on the numeric keypad, which will transmit + // different escape sequences when the Keypad mode is enabled. + const auto enhancedReturnKey = WI_IsFlagSet(controlKeyState, ENHANCED_KEY) && virtualKeyCode == VK_RETURN; + + // Using the control key state that we calculated above, combined with the + // virtual key code, we've got a unique identifier for the key combination + // that we can lookup in our map of predefined key sequences. + auto keyCombo = virtualKeyCode; + WI_SetFlagIf(keyCombo, Ctrl, ctrlIsReallyPressed); + WI_SetFlagIf(keyCombo, Alt, altIsPressed); + WI_SetFlagIf(keyCombo, Shift, shiftIsPressed); + WI_SetFlagIf(keyCombo, Enhanced, enhancedReturnKey); + const auto keyMatch = _keyMap.find(keyCombo); + if (keyMatch != _keyMap.end()) { - const auto ch = keyEvent.uChar.UnicodeChar; - const auto vkey = keyEvent.wVirtualKeyCode; - - // For Alt+Ctrl+Key messages uChar.UnicodeChar usually returns 0. - // Luckily the numerical values of the ASCII characters and virtual key codes - // of and A-Z, as used below, are numerically identical. - // -> Get the char from the virtual key if it's 0. - const auto ctrlAltChar = keyEvent.uChar.UnicodeChar != 0 ? keyEvent.uChar.UnicodeChar : keyEvent.wVirtualKeyCode; - - // Alt+Ctrl acts as a substitute for AltGr on Windows. - // For instance using a German keyboard both AltGr+< and Alt+Ctrl+< produce a | (pipe) character. - // The below condition primitively ensures that we allow all common Alt+Ctrl combinations - // while preserving most of the functionality of Alt+Ctrl as a substitute for AltGr. - if (ctrlAltChar == UNICODE_SPACE || (ctrlAltChar > 0x40 && ctrlAltChar <= 0x5A)) - { - // Pressing the control key causes all bits but the 5 least - // significant ones to be zeroed out (when using ASCII). - return _makeEscapedOutput(ctrlAltChar & 0b11111); - } + return keyMatch->second; + } - // Currently, when we're called with Alt+Ctrl+@, ch will be 0, since Ctrl+@ equals a null byte. - // VkKeyScanW(0) in turn returns the vkey for the null character (ASCII @). - // -> Use the vkey to determine if Ctrl+@ is being pressed and produce ^[^@. - if (ch == UNICODE_NULL && vkey == LOBYTE(OneCoreSafeVkKeyScanW(0))) + // If it's not in the key map, we'll use the UnicodeChar, if provided, + // except in the case of Ctrl+Space, which is often mapped incorrectly as + // a space character when it's expected to be mapped to NUL. We need to + // let that fall through to the standard mapping algorithm below. + const auto ctrlSpaceKey = ctrlIsReallyPressed && virtualKeyCode == VK_SPACE; + if (unicodeChar != 0 && !ctrlSpaceKey) + { + // In the case of an AltGr key, we may still need to apply a Ctrl + // modifier to the char, either because both Ctrl keys were pressed, + // or we got a LeftCtrl that was distinctly separate from the RightAlt. + const auto bothCtrlsArePressed = WI_AreAllFlagsSet(controlKeyState, CTRL_PRESSED); + const auto rightAltIsPressed = WI_IsFlagSet(controlKeyState, RIGHT_ALT_PRESSED); + if (altGrIsPressed && (bothCtrlsArePressed || (rightAltIsPressed && leftCtrlIsReallyPressed))) { - return _makeEscapedOutput(L'\0'); + unicodeChar = _makeCtrlChar(unicodeChar); } + auto charSequence = _makeCharOutput(unicodeChar); + // We may also need to apply an Alt prefix to the char sequence, but + // if this is an AltGr key, we only do so if both Alts are pressed. + const auto bothAltsArePressed = WI_AreAllFlagsSet(controlKeyState, ALT_PRESSED); + _escapeOutput(charSequence, altGrIsPressed ? bothAltsArePressed : altIsPressed); + return charSequence; + } + + // If we don't have a UnicodeChar, we'll try and determine what the key + // would have transmitted without any Ctrl or Alt modifiers applied. But + // this only makes sense if there were actually modifiers pressed. + if (!altIsPressed && !ctrlIsPressed) + { + return _makeNoOutput(); + } + + // We need the current keyboard layout and state to lookup the character + // that would be transmitted in that state (via the ToUnicodeEx API). + const auto hkl = GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), nullptr)); + auto keyState = _getKeyboardState(virtualKeyCode, controlKeyState); + const auto flags = 4u; // Don't modify the state in the ToUnicodeEx call. + const auto bufferSize = 16; + auto buffer = std::array{}; + + // However, we first need to query the key with the original state, to check + // whether it's a dead key. If that is the case, ToUnicodeEx should return a + // negative number, although in practice it's more likely to return a string + // of length two, with two identical characters. This is because the system + // sees this as a second press of the dead key, which would typically result + // in the combining character representation being transmit twice. + auto length = ToUnicodeEx(virtualKeyCode, 0, keyState.data(), buffer.data(), bufferSize, flags, hkl); + if (length < 0 || (length == 2 && buffer.at(0) == buffer.at(1))) + { + return _makeNoOutput(); + } + + // Once we know it's not a dead key, we run the query again, but with the + // Ctrl and Alt modifiers disabled to obtain the base character mapping. + keyState.at(VK_CONTROL) = keyState.at(VK_LCONTROL) = keyState.at(VK_RCONTROL) = 0; + keyState.at(VK_MENU) = keyState.at(VK_LMENU) = keyState.at(VK_RMENU) = 0; + length = ToUnicodeEx(virtualKeyCode, 0, keyState.data(), buffer.data(), bufferSize, flags, hkl); + if (length <= 0) + { + // If we've got nothing usable, we'll just return an empty string. The event + // has technically still been handled, even if it's an unmapped key. + return _makeNoOutput(); } - // If a modifier key was pressed, then we need to try and send the modified sequence. - if (WI_IsAnyFlagSet(keyEvent.dwControlKeyState, MOD_PRESSED)) + auto charSequence = StringType{ buffer.data(), gsl::narrow_cast(length) }; + // Once we've got the base character, we can apply the Ctrl modifier. + if (ctrlIsReallyPressed && charSequence.length() == 1) { - if (auto out = _searchWithModifier(keyEvent)) + auto ch = _makeCtrlChar(charSequence.at(0)); + // If we haven't found a Ctrl mapping for the key, and it's one of + // the alphanumeric keys, we try again using the virtual key code. + // On keyboard layouts where the alphanumeric keys are not mapped to + // their typical ASCII values, this provides a simple fallback. + if (ch >= L' ' && virtualKeyCode >= '2' && virtualKeyCode <= 'Z') { - return out; + ch = _makeCtrlChar(virtualKeyCode); } + charSequence.at(0) = ch; } + // If Alt is pressed, that also needs to be applied to the sequence. + _escapeOutput(charSequence, altIsPressed); + return charSequence; +} - // This section is similar to the Alt modifier section above, - // but handles cases without Ctrl modifiers. - if (WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED) && !WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED) && keyEvent.uChar.UnicodeChar != 0) +TerminalInput::OutputType TerminalInput::HandleFocus(const bool focused) const +{ + if (!_inputMode.test(Mode::FocusEvent)) { - return _makeEscapedOutput(keyEvent.uChar.UnicodeChar); + return MakeUnhandled(); } - // Pressing the control key causes all bits but the 5 least - // significant ones to be zeroed out (when using ASCII). - // This results in Ctrl+Space and Ctrl+@ being equal to a null byte. - // Normally the C0 control code set only defines Ctrl+@, - // but Ctrl+Space is also widely accepted by most terminals. - // -> Send a "null input sequence" in that case. - // We don't need to handle other kinds of Ctrl combinations, - // as we rely on the caller to pretranslate those to characters for us. - if (!WI_IsAnyFlagSet(keyEvent.dwControlKeyState, ALT_PRESSED) && WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED)) - { - const auto ch = keyEvent.uChar.UnicodeChar; - const auto vkey = keyEvent.wVirtualKeyCode; + return MakeOutput(focused ? _focusInSequence : _focusOutSequence); +} - // Currently, when we're called with Ctrl+@, ch will be 0, since Ctrl+@ equals a null byte. - // VkKeyScanW(0) in turn returns the vkey for the null character (ASCII @). - // -> Use the vkey to alternatively determine if Ctrl+@ is being pressed. - if (ch == UNICODE_SPACE || (ch == UNICODE_NULL && vkey == LOBYTE(OneCoreSafeVkKeyScanW(0)))) +void TerminalInput::_initKeyboardMap() noexcept +try +{ + auto defineKeyWithUnusedModifiers = [this](const int keyCode, const std::wstring& sequence) { + for (auto m = 0; m < 8; m++) + _keyMap[VTModifier(m) + keyCode] = sequence; + }; + auto defineKeyWithAltModifier = [this](const int keyCode, const std::wstring& sequence) { + _keyMap[keyCode] = sequence; + _keyMap[Alt + keyCode] = L"\x1B" + sequence; + }; + auto defineKeypadKey = [this](const int keyCode, const wchar_t* prefix, const wchar_t finalChar) { + _keyMap[keyCode] = fmt::format(FMT_COMPILE(L"{}{}"), prefix, finalChar); + for (auto m = 1; m < 8; m++) + _keyMap[VTModifier(m) + keyCode] = fmt::format(FMT_COMPILE(L"{}1;{}{}"), _csi, m + 1, finalChar); + }; + auto defineEditingKey = [this](const int keyCode, const int parm) { + _keyMap[keyCode] = fmt::format(FMT_COMPILE(L"{}{}~"), _csi, parm); + for (auto m = 1; m < 8; m++) + _keyMap[VTModifier(m) + keyCode] = fmt::format(FMT_COMPILE(L"{}{};{}~"), _csi, parm, m + 1); + }; + auto defineNumericKey = [this](const int keyCode, const wchar_t finalChar) { + _keyMap[keyCode] = fmt::format(FMT_COMPILE(L"{}{}"), _ss3, finalChar); + for (auto m = 1; m < 8; m++) + _keyMap[VTModifier(m) + keyCode] = fmt::format(FMT_COMPILE(L"{}{}{}"), _ss3, m + 1, finalChar); + }; + + _keyMap.clear(); + + // PAUSE doesn't have a VT mapping, but traditionally we've mapped it to ^Z, + // regardless of modifiers. + defineKeyWithUnusedModifiers(VK_PAUSE, L"\x1A"s); + + // BACKSPACE maps to either DEL or BS, depending on the Backarrow Key mode. + // The Ctrl modifier inverts the active mode, swapping BS and DEL (this is + // not standard, but a modern terminal convention). The Alt modifier adds + // an ESC prefix (also not standard). + const auto backSequence = _inputMode.test(Mode::BackarrowKey) ? L"\b"s : L"\x7F"s; + const auto ctrlBackSequence = _inputMode.test(Mode::BackarrowKey) ? L"\x7F"s : L"\b"s; + defineKeyWithAltModifier(VK_BACK, backSequence); + defineKeyWithAltModifier(Ctrl + VK_BACK, ctrlBackSequence); + defineKeyWithAltModifier(Shift + VK_BACK, backSequence); + defineKeyWithAltModifier(Ctrl + Shift + VK_BACK, ctrlBackSequence); + + // TAB maps to HT, and Shift+TAB to CBT. The Ctrl modifier has no effect. + // The Alt modifier adds an ESC prefix, although in practice all the Alt + // mappings are likely to be system hotkeys. + const auto shiftTabSequence = fmt::format(FMT_COMPILE(L"{}Z"), _csi); + defineKeyWithAltModifier(VK_TAB, L"\t"s); + defineKeyWithAltModifier(Ctrl + VK_TAB, L"\t"s); + defineKeyWithAltModifier(Shift + VK_TAB, shiftTabSequence); + defineKeyWithAltModifier(Ctrl + Shift + VK_TAB, shiftTabSequence); + + // RETURN maps to either CR or CR LF, depending on the Line Feed mode. With + // a Ctrl modifier it maps to LF, because that's the expected behavior for + // most PC keyboard layouts. The Alt modifier adds an ESC prefix. + const auto returnSequence = _inputMode.test(Mode::LineFeed) ? L"\r\n"s : L"\r"s; + defineKeyWithAltModifier(VK_RETURN, returnSequence); + defineKeyWithAltModifier(Shift + VK_RETURN, returnSequence); + defineKeyWithAltModifier(Ctrl + VK_RETURN, L"\n"s); + defineKeyWithAltModifier(Ctrl + Shift + VK_RETURN, L"\n"s); + + // The keypad RETURN key works the same way, except when Keypad mode is + // enabled, but that's handled below with the other keypad keys. + defineKeyWithAltModifier(Enhanced + VK_RETURN, returnSequence); + defineKeyWithAltModifier(Shift + Enhanced + VK_RETURN, returnSequence); + defineKeyWithAltModifier(Ctrl + Enhanced + VK_RETURN, L"\n"s); + defineKeyWithAltModifier(Ctrl + Shift + Enhanced + VK_RETURN, L"\n"s); + + if (_inputMode.test(Mode::Ansi)) + { + // F1 to F4 map to the VT keypad function keys, which are SS3 sequences. + // When combined with a modifier, we use CSI sequences with the modifier + // embedded as a parameter (not standard - a modern terminal extension). + defineKeypadKey(VK_F1, _ss3, L'P'); + defineKeypadKey(VK_F2, _ss3, L'Q'); + defineKeypadKey(VK_F3, _ss3, L'R'); + defineKeypadKey(VK_F4, _ss3, L'S'); + + // F5 through F20 map to the top row VT function keys. They use standard + // DECFNK sequences with the modifier embedded as a parameter. The first + // five function keys on a VT terminal are typically local functions, so + // there's not much need to support mappings for them. + for (auto vk = VK_F5; vk <= VK_F20; vk++) { - return _makeCharOutput(0); + static constexpr std::array parameters = { 15, 17, 18, 19, 20, 21, 23, 24, 25, 26, 28, 29, 31, 32, 33, 34 }; + const auto parm = parameters.at(static_cast(vk) - VK_F5); + defineEditingKey(vk, parm); } - // Not all keyboard layouts contain mappings for Ctrl-key combinations. - // For instance the US one contains a mapping of Ctrl+\ to ^\, - // but the UK extended layout doesn't, in which case ch is null. - if (ch == UNICODE_NULL) + // Cursor keys follow a similar pattern to the VT keypad function keys, + // although they only use an SS3 prefix when the Cursor Key mode is set. + // When combined with a modifier, they'll use CSI sequences with the + // modifier embedded as a parameter (again not standard). + const auto ckIntroducer = _inputMode.test(Mode::CursorKey) ? _ss3 : _csi; + defineKeypadKey(VK_UP, ckIntroducer, L'A'); + defineKeypadKey(VK_DOWN, ckIntroducer, L'B'); + defineKeypadKey(VK_RIGHT, ckIntroducer, L'C'); + defineKeypadKey(VK_LEFT, ckIntroducer, L'D'); + defineKeypadKey(VK_CLEAR, ckIntroducer, L'E'); + defineKeypadKey(VK_HOME, ckIntroducer, L'H'); + defineKeypadKey(VK_END, ckIntroducer, L'F'); + + // Editing keys follow the same pattern as the top row VT function + // keys, using standard DECFNK sequences with the modifier embedded. + defineEditingKey(VK_INSERT, 2); + defineEditingKey(VK_DELETE, 3); + defineEditingKey(VK_PRIOR, 5); + defineEditingKey(VK_NEXT, 6); + + // Keypad keys depend on the Keypad mode. When reset, they transmit + // the ASCII character assigned by the keyboard layout, but when set + // they transmit SS3 escape sequences. When used with a modifier, the + // modifier is embedded as a parameter value (not standard). + if (Feature_KeypadModeEnabled::IsEnabled() && _inputMode.test(Mode::Keypad)) { - // -> Try to infer the character from the vkey. - auto mappedChar = LOWORD(OneCoreSafeMapVirtualKeyW(keyEvent.wVirtualKeyCode, MAPVK_VK_TO_CHAR)); - if (mappedChar) - { - // Pressing the control key causes all bits but the 5 least - // significant ones to be zeroed out (when using ASCII). - mappedChar &= 0b11111; - return _makeCharOutput(mappedChar); - } + defineNumericKey(VK_MULTIPLY, L'j'); + defineNumericKey(VK_ADD, L'k'); + defineNumericKey(VK_SEPARATOR, L'l'); + defineNumericKey(VK_SUBTRACT, L'm'); + defineNumericKey(VK_DECIMAL, L'n'); + defineNumericKey(VK_DIVIDE, L'o'); + + defineNumericKey(VK_NUMPAD0, L'p'); + defineNumericKey(VK_NUMPAD1, L'q'); + defineNumericKey(VK_NUMPAD2, L'r'); + defineNumericKey(VK_NUMPAD3, L's'); + defineNumericKey(VK_NUMPAD4, L't'); + defineNumericKey(VK_NUMPAD5, L'u'); + defineNumericKey(VK_NUMPAD6, L'v'); + defineNumericKey(VK_NUMPAD7, L'w'); + defineNumericKey(VK_NUMPAD8, L'x'); + defineNumericKey(VK_NUMPAD9, L'y'); + + defineNumericKey(Enhanced + VK_RETURN, L'M'); } } - - // Check any other key mappings (like those for the F1-F12 keys). - // These mappings will kick in no matter which modifiers are pressed and as such - // must be checked last, or otherwise we'd override more complex key combinations. - const auto mapping = _getKeyMapping(keyEvent, _inputMode.test(Mode::Ansi), _inputMode.test(Mode::CursorKey), _inputMode.test(Mode::Keypad)); - if (const auto match = _searchKeyMapping(keyEvent, mapping)) + else { - return MakeOutput(match->sequence); + // In VT52 mode, the sequences tend to use the same final character as + // their ANSI counterparts, but with a simple ESC prefix. The modifier + // keys have no effect. + + // VT52 only support PF1 through PF4 function keys. + defineKeyWithUnusedModifiers(VK_F1, L"\033P"s); + defineKeyWithUnusedModifiers(VK_F2, L"\033Q"s); + defineKeyWithUnusedModifiers(VK_F3, L"\033R"s); + defineKeyWithUnusedModifiers(VK_F4, L"\033S"s); + + // But terminals with application functions keys would + // map some of them as controls keys in VT52 mode. + defineKeyWithUnusedModifiers(VK_F11, L"\033"s); + defineKeyWithUnusedModifiers(VK_F12, L"\b"s); + defineKeyWithUnusedModifiers(VK_F13, L"\n"s); + + // Cursor keys use the same finals as the ANSI sequences. + defineKeyWithUnusedModifiers(VK_UP, L"\033A"s); + defineKeyWithUnusedModifiers(VK_DOWN, L"\033B"s); + defineKeyWithUnusedModifiers(VK_RIGHT, L"\033C"s); + defineKeyWithUnusedModifiers(VK_LEFT, L"\033D"s); + defineKeyWithUnusedModifiers(VK_CLEAR, L"\033E"s); + defineKeyWithUnusedModifiers(VK_HOME, L"\033H"s); + defineKeyWithUnusedModifiers(VK_END, L"\033F"s); + + // Keypad keys also depend on Keypad mode, the same as ANSI mappings, + // but the sequences use an ESC ? prefix instead of SS3. + if (Feature_KeypadModeEnabled::IsEnabled() && _inputMode.test(Mode::Keypad)) + { + defineKeyWithUnusedModifiers(VK_MULTIPLY, L"\033?j"s); + defineKeyWithUnusedModifiers(VK_ADD, L"\033?k"s); + defineKeyWithUnusedModifiers(VK_SEPARATOR, L"\033?l"s); + defineKeyWithUnusedModifiers(VK_SUBTRACT, L"\033?m"s); + defineKeyWithUnusedModifiers(VK_DECIMAL, L"\033?n"s); + defineKeyWithUnusedModifiers(VK_DIVIDE, L"\033?o"s); + + defineKeyWithUnusedModifiers(VK_NUMPAD0, L"\033?p"s); + defineKeyWithUnusedModifiers(VK_NUMPAD1, L"\033?q"s); + defineKeyWithUnusedModifiers(VK_NUMPAD2, L"\033?r"s); + defineKeyWithUnusedModifiers(VK_NUMPAD3, L"\033?s"s); + defineKeyWithUnusedModifiers(VK_NUMPAD4, L"\033?t"s); + defineKeyWithUnusedModifiers(VK_NUMPAD5, L"\033?u"s); + defineKeyWithUnusedModifiers(VK_NUMPAD6, L"\033?v"s); + defineKeyWithUnusedModifiers(VK_NUMPAD7, L"\033?w"s); + defineKeyWithUnusedModifiers(VK_NUMPAD8, L"\033?x"s); + defineKeyWithUnusedModifiers(VK_NUMPAD9, L"\033?y"s); + + defineKeyWithUnusedModifiers(Enhanced + VK_RETURN, L"\033?M"s); + } } - // If all else fails we can finally try to send the character itself if there is any. - if (keyEvent.uChar.UnicodeChar != 0) + _focusInSequence = _csi + L"I"s; + _focusOutSequence = _csi + L"O"s; +} +CATCH_LOG() + +DWORD TerminalInput::_trackControlKeyState(const KEY_EVENT_RECORD& key) +{ + // First record which key state bits were previously off but are now on. + const auto pressedKeyState = ~_lastControlKeyState & key.dwControlKeyState; + // Then save the new key state so we can determine future state changes. + _lastControlKeyState = key.dwControlKeyState; + // But if this latest change has set the RightAlt bit, without having + // received a RightAlt key press, then we need to clear that bit. This + // can happen when pressing the AltGr key on the On-Screen keyboard. It + // actually generates LeftCtrl and LeftAlt key presses, but also sets + // the RightAlt bit on the final key state. If we don't clear that, it + // can be misinterpreted as an Alt+AltGr key combination. + const auto rightAltDown = key.bKeyDown && key.wVirtualKeyCode == VK_MENU && WI_IsFlagSet(key.dwControlKeyState, ENHANCED_KEY); + WI_ClearFlagIf(_lastControlKeyState, RIGHT_ALT_PRESSED, WI_IsFlagSet(pressedKeyState, RIGHT_ALT_PRESSED) && !rightAltDown); + // We also take this opportunity to record the time at which the LeftCtrl + // and RightAlt keys are pressed. This is needed to determine whether the + // Ctrl key was pressed by the user, or fabricated by an AltGr key press. + if (key.bKeyDown) { - return _makeCharOutput(keyEvent.uChar.UnicodeChar); + if (WI_IsFlagSet(pressedKeyState, LEFT_CTRL_PRESSED)) + { + _lastLeftCtrlTime = GetTickCount64(); + } + if (WI_IsFlagSet(pressedKeyState, RIGHT_ALT_PRESSED)) + { + _lastRightAltTime = GetTickCount64(); + } } - - return MakeUnhandled(); + return _lastControlKeyState; } -TerminalInput::OutputType TerminalInput::HandleFocus(const bool focused) const +// Returns a simplified representation of the keyboard state, based on the most +// recent key press and associated control key state (which is all we need for +// our ToUnicodeEx queries). This is a substitute for the GetKeyboardState API, +// which can't be used when serving as a conpty host. +std::array TerminalInput::_getKeyboardState(const WORD virtualKeyCode, const DWORD controlKeyState) const { - if (!_inputMode.test(Mode::FocusEvent)) + auto keyState = std::array{}; + if (virtualKeyCode < keyState.size()) { - return MakeUnhandled(); + keyState.at(virtualKeyCode) = 0x80; } + keyState.at(VK_LCONTROL) = WI_IsFlagSet(controlKeyState, LEFT_CTRL_PRESSED) ? 0x80 : 0; + keyState.at(VK_RCONTROL) = WI_IsFlagSet(controlKeyState, RIGHT_CTRL_PRESSED) ? 0x80 : 0; + keyState.at(VK_CONTROL) = keyState.at(VK_LCONTROL) | keyState.at(VK_RCONTROL); + keyState.at(VK_LMENU) = WI_IsFlagSet(controlKeyState, LEFT_ALT_PRESSED) ? 0x80 : 0; + keyState.at(VK_RMENU) = WI_IsFlagSet(controlKeyState, RIGHT_ALT_PRESSED) ? 0x80 : 0; + keyState.at(VK_MENU) = keyState.at(VK_LMENU) | keyState.at(VK_RMENU); + keyState.at(VK_SHIFT) = keyState.at(VK_LSHIFT) = WI_IsFlagSet(controlKeyState, SHIFT_PRESSED) ? 0x80 : 0; + keyState.at(VK_CAPITAL) = WI_IsFlagSet(controlKeyState, CAPSLOCK_ON); + return keyState; +} - return MakeOutput(focused ? L"\x1b[I" : L"\x1b[O"); +wchar_t TerminalInput::_makeCtrlChar(const wchar_t ch) +{ + if (ch >= L'@' && ch <= L'~') + { + return ch & 0b11111; + } + if (ch == L' ') + { + return 0x00; + } + if (ch == L'/') + { + return 0x1F; + } + if (ch == L'?') + { + return 0x7F; + } + if (ch >= L'2' && ch <= L'8') + { + constexpr auto numericCtrls = std::array{ 0, 27, 28, 29, 30, 31, 127 }; + return numericCtrls.at(ch - L'2'); + } + return ch; } -// Turns the given character into OutputType. +// Turns the given character into StringType. // If it encounters a surrogate pair, it'll buffer the leading character until a // trailing one has been received and then flush both of them simultaneously. // Surrogate pairs should always be handled as proper pairs after all. -TerminalInput::OutputType TerminalInput::_makeCharOutput(const wchar_t ch) +TerminalInput::StringType TerminalInput::_makeCharOutput(const wchar_t ch) { StringType str; - if (til::is_leading_surrogate(ch)) - { - _leadingSurrogate.emplace(ch); - } - else if (_leadingSurrogate) - { - const auto lead = *_leadingSurrogate; - _leadingSurrogate.reset(); - - if (til::is_trailing_surrogate(ch)) - { - str.push_back(lead); - str.push_back(ch); - } - } - else + if (_leadingSurrogate && til::is_trailing_surrogate(ch)) { - str.push_back(ch); + str.push_back(_leadingSurrogate); } + str.push_back(ch); return str; } -// Sends the given char as a sequence representing Alt+wch, also the same as Meta+wch. -TerminalInput::OutputType TerminalInput::_makeEscapedOutput(const wchar_t wch) +TerminalInput::StringType TerminalInput::_makeNoOutput() noexcept { - StringType str; - str.push_back(L'\x1b'); - str.push_back(wch); - return str; + return {}; +} + +// Sends the given char as a sequence representing Alt+char, also the same as Meta+char. +void TerminalInput::_escapeOutput(StringType& charSequence, const bool altIsPressed) const +{ + // Alt+char combinations are only applicable in ANSI mode. + if (altIsPressed && _inputMode.test(Mode::Ansi)) + { + charSequence.insert(0, 1, L'\x1b'); + } } // Turns an KEY_EVENT_RECORD into a win32-input-mode VT sequence. // It allows us to send KEY_EVENT_RECORD data losslessly to conhost. -TerminalInput::OutputType TerminalInput::_makeWin32Output(const KEY_EVENT_RECORD& key) +TerminalInput::OutputType TerminalInput::_makeWin32Output(const KEY_EVENT_RECORD& key) const { // .uChar.UnicodeChar must be cast to an integer because we want its numerical value. // Casting the rest to uint16_t as well doesn't hurt because that's MAX_PARAMETER_VALUE anyways. @@ -728,7 +645,7 @@ TerminalInput::OutputType TerminalInput::_makeWin32Output(const KEY_EVENT_RECORD // Sequences are formatted as follows: // - // ^[ [ Vk ; Sc ; Uc ; Kd ; Cs ; Rc _ + // CSI Vk ; Sc ; Uc ; Kd ; Cs ; Rc _ // // Vk: the value of wVirtualKeyCode - any number. If omitted, defaults to '0'. // Sc: the value of wVirtualScanCode - any number. If omitted, defaults to '0'. @@ -737,5 +654,5 @@ TerminalInput::OutputType TerminalInput::_makeWin32Output(const KEY_EVENT_RECORD // Kd: the value of bKeyDown - either a '0' or '1'. If omitted, defaults to '0'. // Cs: the value of dwControlKeyState - any number. If omitted, defaults to '0'. // Rc: the value of wRepeatCount - any number. If omitted, defaults to '1'. - return fmt::format(FMT_COMPILE(L"\x1b[{};{};{};{};{};{}_"), vk, sc, uc, kd, cs, rc); + return fmt::format(FMT_COMPILE(L"{}{};{};{};{};{};{}_"), _csi, vk, sc, uc, kd, cs, rc); } diff --git a/src/terminal/input/terminalInput.hpp b/src/terminal/input/terminalInput.hpp index 1bb49cca7b2..e8ae53267e2 100644 --- a/src/terminal/input/terminalInput.hpp +++ b/src/terminal/input/terminalInput.hpp @@ -46,6 +46,7 @@ namespace Microsoft::Console::VirtualTerminal AlternateScroll }; + TerminalInput() noexcept; void SetInputMode(const Mode mode, const bool enabled) noexcept; bool GetInputMode(const Mode mode) const noexcept; void ResetInputModes() noexcept; @@ -66,17 +67,32 @@ namespace Microsoft::Console::VirtualTerminal private: // storage location for the leading surrogate of a utf-16 surrogate pair - std::optional _leadingSurrogate; + wchar_t _leadingSurrogate = 0; std::optional _lastVirtualKeyCode; - - til::enumset _inputMode{ Mode::Ansi, Mode::AutoRepeat }; + DWORD _lastControlKeyState = 0; + uint64_t _lastLeftCtrlTime = 0; + uint64_t _lastRightAltTime = 0; + std::unordered_map _keyMap; + std::wstring _focusInSequence; + std::wstring _focusOutSequence; + + til::enumset _inputMode{ Mode::Ansi, Mode::AutoRepeat, Mode::AlternateScroll }; bool _forceDisableWin32InputMode{ false }; - [[nodiscard]] OutputType _makeCharOutput(wchar_t ch); - [[nodiscard]] static OutputType _makeEscapedOutput(wchar_t wch); - [[nodiscard]] static OutputType _makeWin32Output(const KEY_EVENT_RECORD& key); - [[nodiscard]] static OutputType _searchWithModifier(const KEY_EVENT_RECORD& keyEvent); + // In the future, if we add support for "8-bit" input mode, these prefixes + // will sometimes be replaced with equivalent C1 control characters. + static constexpr auto _csi = L"\x1B["; + static constexpr auto _ss3 = L"\x1BO"; + + void _initKeyboardMap() noexcept; + DWORD _trackControlKeyState(const KEY_EVENT_RECORD& key); + std::array _getKeyboardState(const WORD virtualKeyCode, const DWORD controlKeyState) const; + [[nodiscard]] static wchar_t _makeCtrlChar(const wchar_t ch); + [[nodiscard]] StringType _makeCharOutput(wchar_t ch); + [[nodiscard]] static StringType _makeNoOutput() noexcept; + [[nodiscard]] void _escapeOutput(StringType& charSequence, const bool altIsPressed) const; + [[nodiscard]] OutputType _makeWin32Output(const KEY_EVENT_RECORD& key) const; #pragma region MouseInputState Management // These methods are defined in mouseInputState.cpp diff --git a/src/terminal/parser/IStateMachineEngine.hpp b/src/terminal/parser/IStateMachineEngine.hpp index 371ba96c1d5..3f0356ebb84 100644 --- a/src/terminal/parser/IStateMachineEngine.hpp +++ b/src/terminal/parser/IStateMachineEngine.hpp @@ -28,6 +28,8 @@ namespace Microsoft::Console::VirtualTerminal IStateMachineEngine& operator=(const IStateMachineEngine&) = default; IStateMachineEngine& operator=(IStateMachineEngine&&) = default; + virtual bool EncounteredWin32InputModeSequence() const noexcept = 0; + virtual bool ActionExecute(const wchar_t wch) = 0; virtual bool ActionExecuteFromEscape(const wchar_t wch) = 0; virtual bool ActionPrint(const wchar_t wch) = 0; diff --git a/src/terminal/parser/InputStateMachineEngine.cpp b/src/terminal/parser/InputStateMachineEngine.cpp index 95fb5a3e509..cdc270c1858 100644 --- a/src/terminal/parser/InputStateMachineEngine.cpp +++ b/src/terminal/parser/InputStateMachineEngine.cpp @@ -102,6 +102,11 @@ InputStateMachineEngine::InputStateMachineEngine(std::unique_ptrWriteCtrlKey(key); + _encounteredWin32InputModeSequence = true; break; } default: @@ -794,7 +800,6 @@ DWORD InputStateMachineEngine::_GetModifier(const size_t modifierParam) noexcept // VT Modifiers are 1+(modifier flags) const auto vtParam = modifierParam - 1; DWORD modifierState = 0; - WI_SetFlagIf(modifierState, ENHANCED_KEY, modifierParam > 0); WI_SetFlagIf(modifierState, SHIFT_PRESSED, WI_IsFlagSet(vtParam, VT_SHIFT)); WI_SetFlagIf(modifierState, LEFT_ALT_PRESSED, WI_IsFlagSet(vtParam, VT_ALT)); WI_SetFlagIf(modifierState, LEFT_CTRL_PRESSED, WI_IsFlagSet(vtParam, VT_CTRL)); diff --git a/src/terminal/parser/InputStateMachineEngine.hpp b/src/terminal/parser/InputStateMachineEngine.hpp index 74af20a9b39..b0687f6a0b3 100644 --- a/src/terminal/parser/InputStateMachineEngine.hpp +++ b/src/terminal/parser/InputStateMachineEngine.hpp @@ -132,6 +132,7 @@ namespace Microsoft::Console::VirtualTerminal InputStateMachineEngine(std::unique_ptr pDispatch, const bool lookingForDSR); + bool EncounteredWin32InputModeSequence() const noexcept override; void SetLookingForDSR(const bool looking) noexcept; bool ActionExecute(const wchar_t wch) override; @@ -167,6 +168,7 @@ namespace Microsoft::Console::VirtualTerminal const std::unique_ptr _pDispatch; std::function _pfnFlushToInputQueue; bool _lookingForDSR; + bool _encounteredWin32InputModeSequence = false; DWORD _mouseButtonState = 0; std::chrono::milliseconds _doubleClickTime; std::optional _lastMouseClickPos{}; diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index e744c95ad08..9d247bfc4fe 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -23,6 +23,11 @@ OutputStateMachineEngine::OutputStateMachineEngine(std::unique_ptrPrint(L'\u2E2E'); + // typically a reverse question mark (Unicode substitute form two). + _dispatch->Print(L'\u2426'); break; case AsciiChars::DEL: // The DEL control can sometimes be translated into a printable glyph @@ -561,6 +566,11 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete return _dispatch->TabClear(clearType); }); break; + case CsiActionCodes::DECST8C_SetTabEvery8Columns: + success = parameters.for_each([&](const auto setType) { + return _dispatch->TabSet(setType); + }); + break; case CsiActionCodes::ECH_EraseCharacters: success = _dispatch->EraseCharacters(parameters.at(0)); break; diff --git a/src/terminal/parser/OutputStateMachineEngine.hpp b/src/terminal/parser/OutputStateMachineEngine.hpp index 1983fa9baaa..32105133bf3 100644 --- a/src/terminal/parser/OutputStateMachineEngine.hpp +++ b/src/terminal/parser/OutputStateMachineEngine.hpp @@ -29,6 +29,8 @@ namespace Microsoft::Console::VirtualTerminal OutputStateMachineEngine(std::unique_ptr pDispatch); + bool EncounteredWin32InputModeSequence() const noexcept override; + bool ActionExecute(const wchar_t wch) override; bool ActionExecuteFromEscape(const wchar_t wch) override; @@ -119,6 +121,7 @@ namespace Microsoft::Console::VirtualTerminal DCH_DeleteCharacter = VTID("P"), SU_ScrollUp = VTID("S"), SD_ScrollDown = VTID("T"), + DECST8C_SetTabEvery8Columns = VTID("?W"), ECH_EraseCharacters = VTID("X"), CBT_CursorBackTab = VTID("Z"), HPA_HorizontalPositionAbsolute = VTID("`"), diff --git a/src/terminal/parser/stateMachine.cpp b/src/terminal/parser/stateMachine.cpp index eb22ff95382..88727ad6ec2 100644 --- a/src/terminal/parser/stateMachine.cpp +++ b/src/terminal/parser/stateMachine.cpp @@ -2127,6 +2127,8 @@ void StateMachine::ProcessString(const std::wstring_view string) // If we're at the end of the string and have remaining un-printed characters, if (_state != VTStates::Ground) { + const auto run = _CurrentRun(); + // One of the "weird things" in VT input is the case of something like // alt+[. In VT, that's encoded as `\x1b[`. However, that's // also the start of a CSI, and could be the start of a longer sequence, @@ -2136,60 +2138,34 @@ void StateMachine::ProcessString(const std::wstring_view string) // alt+[, A would be processed like `\x1b[A`, // which is _wrong_). // - // Fortunately, for VT input, each keystroke comes in as an individual - // write operation. So, if at the end of processing a string for the - // InputEngine, we find that we're not in the Ground state, that implies - // that we've processed some input, but not dispatched it yet. This - // block at the end of `ProcessString` will then re-process the - // undispatched string, but it will ensure that it dispatches on the - // last character of the string. For our previous `\x1b[` scenario, that - // means we'll make sure to call `_ActionEscDispatch('[')`., which will - // properly decode the string as alt+[. - const auto run = _CurrentRun(); - + // At the same time, input may be broken up arbitrarily, depending on the pipe's + // buffer size, our read-buffer size, the sender's write-buffer size, and more. + // In fact, with the current WSL, input is broken up in 16 byte chunks (Why? :(), + // which breaks up many of our longer sequences, like our Win32InputMode ones. + // + // As a heuristic, this code specifically checks for a trailing Esc or Alt+key. + // If we encountered a win32-input-mode sequence before, we know that our \x1b[?9001h + // request to enable them was successful. While a client may still send \x1b{some char} + // intentionally, it's far more likely now that we're looking at a broken up sequence. + // The most common win32-input-mode is ConPTY itself after all, and we never emit + // \x1b{some char} once it's enabled. if (_isEngineForInput) { - // Reset our state, and put all but the last char in again. - ResetState(); - _processingLastCharacter = false; - // Chars to flush are [pwchSequenceStart, pwchCurr) - auto wchIter = run.cbegin(); - while (wchIter < run.cend() - 1) - { - ProcessCharacter(*wchIter); - wchIter++; - } - // Manually execute the last char [pwchCurr] - _processingLastCharacter = true; - switch (_state) + const auto win32 = _engine->EncounteredWin32InputModeSequence(); + if (!win32 && run.size() <= 2 && run.front() == L'\x1b') { - case VTStates::Ground: - _ActionExecute(*wchIter); - break; - case VTStates::Escape: - case VTStates::EscapeIntermediate: - _ActionEscDispatch(*wchIter); - break; - case VTStates::CsiEntry: - case VTStates::CsiIntermediate: - case VTStates::CsiIgnore: - case VTStates::CsiParam: - case VTStates::CsiSubParam: - _ActionCsiDispatch(*wchIter); - break; - case VTStates::OscParam: - case VTStates::OscString: - case VTStates::OscTermination: - _ActionOscDispatch(*wchIter); - break; - case VTStates::Ss3Entry: - case VTStates::Ss3Param: - _ActionSs3Dispatch(*wchIter); - break; + _EnterGround(); + if (run.size() == 1) + { + _ActionExecute(L'\x1b'); + } + else + { + _EnterEscape(); + _ActionEscDispatch(run.back()); + } + _EnterGround(); } - // microsoft/terminal#2746: Make sure to return to the ground state - // after dispatching the characters - _EnterGround(); } else if (_state != VTStates::SosPmApcString && _state != VTStates::DcsPassThrough && _state != VTStates::DcsIgnore) { diff --git a/src/terminal/parser/tracing.cpp b/src/terminal/parser/tracing.cpp index 9ab684ac95d..862c9c1ed80 100644 --- a/src/terminal/parser/tracing.cpp +++ b/src/terminal/parser/tracing.cpp @@ -7,6 +7,7 @@ using namespace Microsoft::Console::VirtualTerminal; #pragma warning(push) +#pragma warning(disable : 26426) // Global initializer calls a non-constexpr function '...' (i.22).) #pragma warning(disable : 26447) // The function is declared 'noexcept' but calls function '_tlgWrapBinary()' which may throw exceptions #pragma warning(disable : 26477) // Use 'nullptr' rather than 0 or NULL @@ -15,6 +16,13 @@ TRACELOGGING_DEFINE_PROVIDER(g_hConsoleVirtTermParserEventTraceProvider, // {c9ba2a84-d3ca-5e19-2bd6-776a0910cb9d} (0xc9ba2a84, 0xd3ca, 0x5e19, 0x2b, 0xd6, 0x77, 0x6a, 0x09, 0x10, 0xcb, 0x9d)); +static const auto cleanup = []() noexcept { + TraceLoggingRegister(g_hConsoleVirtTermParserEventTraceProvider); + return wil::scope_exit([]() noexcept { + TraceLoggingUnregister(g_hConsoleVirtTermParserEventTraceProvider); + }); +}(); + void ParserTracing::TraceStateChange(_In_z_ const wchar_t* name) const noexcept { TraceLoggingWrite(g_hConsoleVirtTermParserEventTraceProvider, diff --git a/src/terminal/parser/tracing.hpp b/src/terminal/parser/tracing.hpp index ecc357c3236..56e449f06fe 100644 --- a/src/terminal/parser/tracing.hpp +++ b/src/terminal/parser/tracing.hpp @@ -18,8 +18,6 @@ Module Name: #include #include -TRACELOGGING_DECLARE_PROVIDER(g_hConsoleVirtTermParserEventTraceProvider); - namespace Microsoft::Console::VirtualTerminal { class ParserTracing sealed diff --git a/src/terminal/parser/ut_parser/InputEngineTest.cpp b/src/terminal/parser/ut_parser/InputEngineTest.cpp index 2e21c77c74f..fcb23734be5 100644 --- a/src/terminal/parser/ut_parser/InputEngineTest.cpp +++ b/src/terminal/parser/ut_parser/InputEngineTest.cpp @@ -260,6 +260,7 @@ class Microsoft::Console::VirtualTerminal::InputEngineTest TEST_METHOD(AltCtrlDTest); TEST_METHOD(AltIntermediateTest); TEST_METHOD(AltBackspaceEnterTest); + TEST_METHOD(ChunkedSequence); TEST_METHOD(SGRMouseTest_ButtonClick); TEST_METHOD(SGRMouseTest_Modifiers); TEST_METHOD(SGRMouseTest_Movement); @@ -1041,6 +1042,19 @@ void InputEngineTest::AltBackspaceEnterTest() VerifyExpectedInputDrained(); } +void InputEngineTest::ChunkedSequence() +{ + // This test ensures that a DSC sequence that's split up into multiple chunks isn't + // confused with a single Alt+key combination like in the AltBackspaceEnterTest(). + // Basically, it tests the selectivity of the AltBackspaceEnterTest() fix. + + auto dispatch = std::make_unique(nullptr, nullptr); + auto inputEngine = std::make_unique(std::move(dispatch)); + StateMachine stateMachine{ std::move(inputEngine) }; + stateMachine.ProcessString(L"\x1b[1"); + VERIFY_ARE_EQUAL(StateMachine::VTStates::CsiParam, stateMachine._state); +} + // Method Description: // - Writes an SGR VT sequence based on the necessary parameters // Arguments: diff --git a/src/terminal/parser/ut_parser/OutputEngineTest.cpp b/src/terminal/parser/ut_parser/OutputEngineTest.cpp index 9695c80936f..c5517a89af6 100644 --- a/src/terminal/parser/ut_parser/OutputEngineTest.cpp +++ b/src/terminal/parser/ut_parser/OutputEngineTest.cpp @@ -2159,29 +2159,29 @@ class StateMachineExternalTest final auto engine = std::make_unique(std::move(dispatch)); StateMachine mach(std::move(engine)); - Log::Comment(L"Test 1: Check OS (operating status) case 5. Should succeed."); + Log::Comment(L"Test 1: Check operating status (case 5). Should succeed."); mach.ProcessCharacter(AsciiChars::ESC); mach.ProcessCharacter(L'['); mach.ProcessCharacter(L'5'); mach.ProcessCharacter(L'n'); VERIFY_IS_TRUE(pDispatch->_deviceStatusReport); - VERIFY_ARE_EQUAL(DispatchTypes::StatusType::OS_OperatingStatus, pDispatch->_statusReportType); + VERIFY_ARE_EQUAL(DispatchTypes::StatusType::OperatingStatus, pDispatch->_statusReportType); pDispatch->ClearState(); - Log::Comment(L"Test 2: Check CPR (cursor position report) case 6. Should succeed."); + Log::Comment(L"Test 2: Check cursor position report (case 6). Should succeed."); mach.ProcessCharacter(AsciiChars::ESC); mach.ProcessCharacter(L'['); mach.ProcessCharacter(L'6'); mach.ProcessCharacter(L'n'); VERIFY_IS_TRUE(pDispatch->_deviceStatusReport); - VERIFY_ARE_EQUAL(DispatchTypes::StatusType::CPR_CursorPositionReport, pDispatch->_statusReportType); + VERIFY_ARE_EQUAL(DispatchTypes::StatusType::CursorPositionReport, pDispatch->_statusReportType); pDispatch->ClearState(); - Log::Comment(L"Test 3: Check DECXCPR (extended cursor position report) case ?6. Should succeed."); + Log::Comment(L"Test 3: Check extended cursor position report (case ?6). Should succeed."); mach.ProcessCharacter(AsciiChars::ESC); mach.ProcessCharacter(L'['); mach.ProcessCharacter(L'?'); @@ -2189,7 +2189,124 @@ class StateMachineExternalTest final mach.ProcessCharacter(L'n'); VERIFY_IS_TRUE(pDispatch->_deviceStatusReport); - VERIFY_ARE_EQUAL(DispatchTypes::StatusType::ExCPR_ExtendedCursorPositionReport, pDispatch->_statusReportType); + VERIFY_ARE_EQUAL(DispatchTypes::StatusType::ExtendedCursorPositionReport, pDispatch->_statusReportType); + + pDispatch->ClearState(); + + Log::Comment(L"Test 4: Check printer status (case ?15). Should succeed."); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'['); + mach.ProcessCharacter(L'?'); + mach.ProcessCharacter(L'1'); + mach.ProcessCharacter(L'5'); + mach.ProcessCharacter(L'n'); + + VERIFY_IS_TRUE(pDispatch->_deviceStatusReport); + VERIFY_ARE_EQUAL(DispatchTypes::StatusType::PrinterStatus, pDispatch->_statusReportType); + + pDispatch->ClearState(); + + Log::Comment(L"Test 5: Check user-defined keys (case ?25). Should succeed."); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'['); + mach.ProcessCharacter(L'?'); + mach.ProcessCharacter(L'2'); + mach.ProcessCharacter(L'5'); + mach.ProcessCharacter(L'n'); + + VERIFY_IS_TRUE(pDispatch->_deviceStatusReport); + VERIFY_ARE_EQUAL(DispatchTypes::StatusType::UserDefinedKeys, pDispatch->_statusReportType); + + pDispatch->ClearState(); + + Log::Comment(L"Test 6: Check keyboard status / dialect (case ?26). Should succeed."); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'['); + mach.ProcessCharacter(L'?'); + mach.ProcessCharacter(L'2'); + mach.ProcessCharacter(L'6'); + mach.ProcessCharacter(L'n'); + + VERIFY_IS_TRUE(pDispatch->_deviceStatusReport); + VERIFY_ARE_EQUAL(DispatchTypes::StatusType::KeyboardStatus, pDispatch->_statusReportType); + + pDispatch->ClearState(); + + Log::Comment(L"Test 7: Check locator status (case ?55). Should succeed."); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'['); + mach.ProcessCharacter(L'?'); + mach.ProcessCharacter(L'5'); + mach.ProcessCharacter(L'5'); + mach.ProcessCharacter(L'n'); + + VERIFY_IS_TRUE(pDispatch->_deviceStatusReport); + VERIFY_ARE_EQUAL(DispatchTypes::StatusType::LocatorStatus, pDispatch->_statusReportType); + + pDispatch->ClearState(); + + Log::Comment(L"Test 8: Check locator identity (case ?56). Should succeed."); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'['); + mach.ProcessCharacter(L'?'); + mach.ProcessCharacter(L'5'); + mach.ProcessCharacter(L'6'); + mach.ProcessCharacter(L'n'); + + VERIFY_IS_TRUE(pDispatch->_deviceStatusReport); + VERIFY_ARE_EQUAL(DispatchTypes::StatusType::LocatorIdentity, pDispatch->_statusReportType); + + pDispatch->ClearState(); + + Log::Comment(L"Test 9: Check macro space report (case ?62). Should succeed."); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'['); + mach.ProcessCharacter(L'?'); + mach.ProcessCharacter(L'6'); + mach.ProcessCharacter(L'2'); + mach.ProcessCharacter(L'n'); + + VERIFY_IS_TRUE(pDispatch->_deviceStatusReport); + VERIFY_ARE_EQUAL(DispatchTypes::StatusType::MacroSpaceReport, pDispatch->_statusReportType); + + pDispatch->ClearState(); + + Log::Comment(L"Test 10: Check memory checksum (case ?63). Should succeed."); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'['); + mach.ProcessCharacter(L'?'); + mach.ProcessCharacter(L'6'); + mach.ProcessCharacter(L'3'); + mach.ProcessCharacter(L'n'); + + VERIFY_IS_TRUE(pDispatch->_deviceStatusReport); + VERIFY_ARE_EQUAL(DispatchTypes::StatusType::MemoryChecksum, pDispatch->_statusReportType); + + pDispatch->ClearState(); + + Log::Comment(L"Test 11: Check data integrity report (case ?75). Should succeed."); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'['); + mach.ProcessCharacter(L'?'); + mach.ProcessCharacter(L'7'); + mach.ProcessCharacter(L'5'); + mach.ProcessCharacter(L'n'); + + VERIFY_IS_TRUE(pDispatch->_deviceStatusReport); + VERIFY_ARE_EQUAL(DispatchTypes::StatusType::DataIntegrity, pDispatch->_statusReportType); + + pDispatch->ClearState(); + + Log::Comment(L"Test 12: Check multiple session status (case ?85). Should succeed."); + mach.ProcessCharacter(AsciiChars::ESC); + mach.ProcessCharacter(L'['); + mach.ProcessCharacter(L'?'); + mach.ProcessCharacter(L'8'); + mach.ProcessCharacter(L'5'); + mach.ProcessCharacter(L'n'); + + VERIFY_IS_TRUE(pDispatch->_deviceStatusReport); + VERIFY_ARE_EQUAL(DispatchTypes::StatusType::MultipleSessionStatus, pDispatch->_statusReportType); pDispatch->ClearState(); } diff --git a/src/terminal/parser/ut_parser/StateMachineTest.cpp b/src/terminal/parser/ut_parser/StateMachineTest.cpp index 4893964c249..d7e49537207 100644 --- a/src/terminal/parser/ut_parser/StateMachineTest.cpp +++ b/src/terminal/parser/ut_parser/StateMachineTest.cpp @@ -40,6 +40,11 @@ class Microsoft::Console::VirtualTerminal::TestStateMachineEngine : public IStat dcsDataString.clear(); } + bool EncounteredWin32InputModeSequence() const noexcept override + { + return false; + } + bool ActionExecute(const wchar_t wch) override { executed += wch; diff --git a/src/terminal/parser/ut_parser/sources b/src/terminal/parser/ut_parser/sources index 1c63146466f..d4da3cf1d49 100644 --- a/src/terminal/parser/ut_parser/sources +++ b/src/terminal/parser/ut_parser/sources @@ -39,7 +39,6 @@ TARGETLIBS = \ $(ONECOREUAP_EXTERNAL_SDK_LIB_PATH)\d3dcompiler.lib \ $(MODERNCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\api-ms-win-mm-playsound-l1.lib \ $(ONECORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-dwmapi-ext-l1.lib \ - $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-edputil-policy-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-dc-create-l1.lib \ $(MINCORE_INTERNAL_PRIV_SDK_LIB_VPATH_L)\ext-ms-win-gdi-draw-l1.lib \ @@ -96,12 +95,12 @@ DELAYLOAD = \ DXGI.dll; \ D3D11.dll; \ OLEAUT32.dll; \ + icu.dll; \ api-ms-win-mm-playsound-l1.dll; \ api-ms-win-shcore-scaling-l1.dll; \ api-ms-win-shell-dataobject-l1.dll; \ api-ms-win-shell-namespace-l1.dll; \ ext-ms-win-dwmapi-ext-l1.dll; \ - ext-ms-win-edputil-policy-l1.dll; \ ext-ms-win-gdi-dc-l1.dll; \ ext-ms-win-gdi-dc-create-l1.dll; \ ext-ms-win-gdi-draw-l1.dll; \ diff --git a/src/tools/ColorTool/schemes/OneHalfDark.itermcolors b/src/tools/ColorTool/schemes/OneHalfDark.itermcolors index 3dc69aca6a7..115741c8811 100644 --- a/src/tools/ColorTool/schemes/OneHalfDark.itermcolors +++ b/src/tools/ColorTool/schemes/OneHalfDark.itermcolors @@ -1,376 +1,376 @@ - - - - - - Ansi 0 Color - - Alpha Component - 1 - Blue Component - 0.203921568627 - Color Space - Calibrated - Green Component - 0.172549019608 - Red Component - 0.156862745098 - - Ansi 1 Color - - Alpha Component - 1 - Blue Component - 0.458823529412 - Color Space - Calibrated - Green Component - 0.423529411765 - Red Component - 0.878431372549 - - Ansi 10 Color - - Alpha Component - 1 - Blue Component - 0.474509803922 - Color Space - Calibrated - Green Component - 0.764705882353 - Red Component - 0.596078431373 - - Ansi 11 Color - - Alpha Component - 1 - Blue Component - 0.482352941176 - Color Space - Calibrated - Green Component - 0.752941176471 - Red Component - 0.898039215686 - - Ansi 12 Color - - Alpha Component - 1 - Blue Component - 0.937254901961 - Color Space - Calibrated - Green Component - 0.686274509804 - Red Component - 0.380392156863 - - Ansi 13 Color - - Alpha Component - 1 - Blue Component - 0.866666666667 - Color Space - Calibrated - Green Component - 0.470588235294 - Red Component - 0.776470588235 - - Ansi 14 Color - - Alpha Component - 1 - Blue Component - 0.760784313725 - Color Space - Calibrated - Green Component - 0.713725490196 - Red Component - 0.337254901961 - - Ansi 15 Color - - Alpha Component - 1 - Blue Component - 0.894117647059 - Color Space - Calibrated - Green Component - 0.874509803922 - Red Component - 0.862745098039 - - Ansi 2 Color - - Alpha Component - 1 - Blue Component - 0.474509803922 - Color Space - Calibrated - Green Component - 0.764705882353 - Red Component - 0.596078431373 - - Ansi 3 Color - - Alpha Component - 1 - Blue Component - 0.482352941176 - Color Space - Calibrated - Green Component - 0.752941176471 - Red Component - 0.898039215686 - - Ansi 4 Color - - Alpha Component - 1 - Blue Component - 0.937254901961 - Color Space - Calibrated - Green Component - 0.686274509804 - Red Component - 0.380392156863 - - Ansi 5 Color - - Alpha Component - 1 - Blue Component - 0.866666666667 - Color Space - Calibrated - Green Component - 0.470588235294 - Red Component - 0.776470588235 - - Ansi 6 Color - - Alpha Component - 1 - Blue Component - 0.760784313725 - Color Space - Calibrated - Green Component - 0.713725490196 - Red Component - 0.337254901961 - - Ansi 7 Color - - Alpha Component - 1 - Blue Component - 0.894117647059 - Color Space - Calibrated - Green Component - 0.874509803922 - Red Component - 0.862745098039 - - Ansi 8 Color - - Alpha Component - 1 - Blue Component - 0.4549019607843137 - Color Space - Calibrated - Green Component - 0.38823529411764707 - Red Component - 0.35294117647058826 - - Ansi 9 Color - - Alpha Component - 1 - Blue Component - 0.458823529412 - Color Space - Calibrated - Green Component - 0.423529411765 - Red Component - 0.878431372549 - - Background Color - - Alpha Component - 1 - Blue Component - 0.203921568627 - Color Space - Calibrated - Green Component - 0.172549019608 - Red Component - 0.156862745098 - - Badge Color - - Alpha Component - 0.5 - Blue Component - 0.0 - Color Space - Calibrated - Green Component - 0.0 - Red Component - 1 - - Bold Color - - Alpha Component - 1 - Blue Component - 0.74901962280273438 - Color Space - Calibrated - Green Component - 0.69803923368453979 - Red Component - 0.67058825492858887 - - Cursor Color - - Alpha Component - 1 - Blue Component - 0.8 - Color Space - Calibrated - Green Component - 0.701960784314 - Red Component - 0.639215686275 - - Cursor Guide Color - - Alpha Component - 0.25 - Blue Component - 0.250980392157 - Color Space - Calibrated - Green Component - 0.211764705882 - Red Component - 0.192156862745 - - Cursor Text Color - - Alpha Component - 1 - Blue Component - 0.894117647059 - Color Space - Calibrated - Green Component - 0.874509803922 - Red Component - 0.862745098039 - - Foreground Color - - Alpha Component - 1 - Blue Component - 0.894117647059 - Color Space - Calibrated - Green Component - 0.874509803922 - Red Component - 0.862745098039 - - Link Color - - Alpha Component - 1 - Blue Component - 0.937254901961 - Color Space - Calibrated - Green Component - 0.686274509804 - Red Component - 0.380392156863 - - Selected Text Color - - Alpha Component - 1 - Blue Component - 0.894117647059 - Color Space - Calibrated - Green Component - 0.874509803922 - Red Component - 0.862745098039 - - Selection Color - - Alpha Component - 1 - Blue Component - 0.364705882353 - Color Space - Calibrated - Green Component - 0.305882352941 - Red Component - 0.278431372549 - - + + + + + + Ansi 0 Color + + Alpha Component + 1 + Blue Component + 0.203921568627 + Color Space + Calibrated + Green Component + 0.172549019608 + Red Component + 0.156862745098 + + Ansi 1 Color + + Alpha Component + 1 + Blue Component + 0.458823529412 + Color Space + Calibrated + Green Component + 0.423529411765 + Red Component + 0.878431372549 + + Ansi 10 Color + + Alpha Component + 1 + Blue Component + 0.474509803922 + Color Space + Calibrated + Green Component + 0.764705882353 + Red Component + 0.596078431373 + + Ansi 11 Color + + Alpha Component + 1 + Blue Component + 0.482352941176 + Color Space + Calibrated + Green Component + 0.752941176471 + Red Component + 0.898039215686 + + Ansi 12 Color + + Alpha Component + 1 + Blue Component + 0.937254901961 + Color Space + Calibrated + Green Component + 0.686274509804 + Red Component + 0.380392156863 + + Ansi 13 Color + + Alpha Component + 1 + Blue Component + 0.866666666667 + Color Space + Calibrated + Green Component + 0.470588235294 + Red Component + 0.776470588235 + + Ansi 14 Color + + Alpha Component + 1 + Blue Component + 0.760784313725 + Color Space + Calibrated + Green Component + 0.713725490196 + Red Component + 0.337254901961 + + Ansi 15 Color + + Alpha Component + 1 + Blue Component + 0.894117647059 + Color Space + Calibrated + Green Component + 0.874509803922 + Red Component + 0.862745098039 + + Ansi 2 Color + + Alpha Component + 1 + Blue Component + 0.474509803922 + Color Space + Calibrated + Green Component + 0.764705882353 + Red Component + 0.596078431373 + + Ansi 3 Color + + Alpha Component + 1 + Blue Component + 0.482352941176 + Color Space + Calibrated + Green Component + 0.752941176471 + Red Component + 0.898039215686 + + Ansi 4 Color + + Alpha Component + 1 + Blue Component + 0.937254901961 + Color Space + Calibrated + Green Component + 0.686274509804 + Red Component + 0.380392156863 + + Ansi 5 Color + + Alpha Component + 1 + Blue Component + 0.866666666667 + Color Space + Calibrated + Green Component + 0.470588235294 + Red Component + 0.776470588235 + + Ansi 6 Color + + Alpha Component + 1 + Blue Component + 0.760784313725 + Color Space + Calibrated + Green Component + 0.713725490196 + Red Component + 0.337254901961 + + Ansi 7 Color + + Alpha Component + 1 + Blue Component + 0.894117647059 + Color Space + Calibrated + Green Component + 0.874509803922 + Red Component + 0.862745098039 + + Ansi 8 Color + + Alpha Component + 1 + Blue Component + 0.4549019607843137 + Color Space + Calibrated + Green Component + 0.38823529411764707 + Red Component + 0.35294117647058826 + + Ansi 9 Color + + Alpha Component + 1 + Blue Component + 0.458823529412 + Color Space + Calibrated + Green Component + 0.423529411765 + Red Component + 0.878431372549 + + Background Color + + Alpha Component + 1 + Blue Component + 0.203921568627 + Color Space + Calibrated + Green Component + 0.172549019608 + Red Component + 0.156862745098 + + Badge Color + + Alpha Component + 0.5 + Blue Component + 0.0 + Color Space + Calibrated + Green Component + 0.0 + Red Component + 1 + + Bold Color + + Alpha Component + 1 + Blue Component + 0.74901962280273438 + Color Space + Calibrated + Green Component + 0.69803923368453979 + Red Component + 0.67058825492858887 + + Cursor Color + + Alpha Component + 1 + Blue Component + 0.8 + Color Space + Calibrated + Green Component + 0.701960784314 + Red Component + 0.639215686275 + + Cursor Guide Color + + Alpha Component + 0.25 + Blue Component + 0.250980392157 + Color Space + Calibrated + Green Component + 0.211764705882 + Red Component + 0.192156862745 + + Cursor Text Color + + Alpha Component + 1 + Blue Component + 0.894117647059 + Color Space + Calibrated + Green Component + 0.874509803922 + Red Component + 0.862745098039 + + Foreground Color + + Alpha Component + 1 + Blue Component + 0.894117647059 + Color Space + Calibrated + Green Component + 0.874509803922 + Red Component + 0.862745098039 + + Link Color + + Alpha Component + 1 + Blue Component + 0.937254901961 + Color Space + Calibrated + Green Component + 0.686274509804 + Red Component + 0.380392156863 + + Selected Text Color + + Alpha Component + 1 + Blue Component + 0.894117647059 + Color Space + Calibrated + Green Component + 0.874509803922 + Red Component + 0.862745098039 + + Selection Color + + Alpha Component + 1 + Blue Component + 0.364705882353 + Color Space + Calibrated + Green Component + 0.305882352941 + Red Component + 0.278431372549 + + \ No newline at end of file diff --git a/src/tools/ColorTool/schemes/OneHalfLight.itermcolors b/src/tools/ColorTool/schemes/OneHalfLight.itermcolors index 50391960272..893bb4600e8 100644 --- a/src/tools/ColorTool/schemes/OneHalfLight.itermcolors +++ b/src/tools/ColorTool/schemes/OneHalfLight.itermcolors @@ -1,376 +1,376 @@ - - - - - - Ansi 0 Color - - Alpha Component - 1 - Blue Component - 0.258823529412 - Color Space - Calibrated - Green Component - 0.227450980392 - Red Component - 0.219607843137 - - Ansi 1 Color - - Alpha Component - 1 - Blue Component - 0.286274509804 - Color Space - Calibrated - Green Component - 0.337254901961 - Red Component - 0.894117647059 - - Ansi 10 Color - - Alpha Component - 1 - Blue Component - 0.474509803922 - Color Space - Calibrated - Green Component - 0.764705882353 - Red Component - 0.596078431373 - - Ansi 11 Color - - Alpha Component - 1 - Blue Component - 0.482352941176 - Color Space - Calibrated - Green Component - 0.752941176471 - Red Component - 0.898039215686 - - Ansi 12 Color - - Alpha Component - 1 - Blue Component - 0.937254901961 - Color Space - Calibrated - Green Component - 0.686274509804 - Red Component - 0.380392156863 - - Ansi 13 Color - - Alpha Component - 1 - Blue Component - 0.866666666667 - Color Space - Calibrated - Green Component - 0.470588235294 - Red Component - 0.776470588235 - - Ansi 14 Color - - Alpha Component - 1 - Blue Component - 0.760784313725 - Color Space - Calibrated - Green Component - 0.713725490196 - Red Component - 0.337254901961 - - Ansi 15 Color - - Alpha Component - 1 - Blue Component - 1.0 - Color Space - Calibrated - Green Component - 1.0 - Red Component - 1.0 - - Ansi 2 Color - - Alpha Component - 1 - Blue Component - 0.309803921569 - Color Space - Calibrated - Green Component - 0.63137254902 - Red Component - 0.313725490196 - - Ansi 3 Color - - Alpha Component - 1 - Blue Component - 0.00392156862745 - Color Space - Calibrated - Green Component - 0.517647058824 - Red Component - 0.756862745098 - - Ansi 4 Color - - Alpha Component - 1 - Blue Component - 0.737254901961 - Color Space - Calibrated - Green Component - 0.517647058824 - Red Component - 0.00392156862745 - - Ansi 5 Color - - Alpha Component - 1 - Blue Component - 0.643137254902 - Color Space - Calibrated - Green Component - 0.149019607843 - Red Component - 0.650980392157 - - Ansi 6 Color - - Alpha Component - 1 - Blue Component - 0.701960784314 - Color Space - Calibrated - Green Component - 0.592156862745 - Red Component - 0.0352941176471 - - Ansi 7 Color - - Alpha Component - 1 - Blue Component - 0.980392156863 - Color Space - Calibrated - Green Component - 0.980392156863 - Red Component - 0.980392156863 - - Ansi 8 Color - - Alpha Component - 1 - Blue Component - 0.36862745098 - Color Space - Calibrated - Green Component - 0.321568627451 - Red Component - 0.309803921569 - - Ansi 9 Color - - Alpha Component - 1 - Blue Component - 0.458823529412 - Color Space - Calibrated - Green Component - 0.423529411765 - Red Component - 0.878431372549 - - Background Color - - Alpha Component - 1 - Blue Component - 0.980392156863 - Color Space - Calibrated - Green Component - 0.980392156863 - Red Component - 0.980392156863 - - Badge Color - - Alpha Component - 0.5 - Blue Component - 0.0 - Color Space - Calibrated - Green Component - 0.0 - Red Component - 1 - - Bold Color - - Alpha Component - 1 - Blue Component - 0.74901962280273438 - Color Space - Calibrated - Green Component - 0.69803923368453979 - Red Component - 0.67058825492858887 - - Cursor Color - - Alpha Component - 1 - Blue Component - 1.0 - Color Space - Calibrated - Green Component - 0.807843137255 - Red Component - 0.749019607843 - - Cursor Guide Color - - Alpha Component - 0.25 - Blue Component - 0.941176470588 - Color Space - Calibrated - Green Component - 0.941176470588 - Red Component - 0.941176470588 - - Cursor Text Color - - Alpha Component - 1 - Blue Component - 0.258823529412 - Color Space - Calibrated - Green Component - 0.227450980392 - Red Component - 0.219607843137 - - Foreground Color - - Alpha Component - 1 - Blue Component - 0.258823529412 - Color Space - Calibrated - Green Component - 0.227450980392 - Red Component - 0.219607843137 - - Link Color - - Alpha Component - 1 - Blue Component - 0.737254901961 - Color Space - Calibrated - Green Component - 0.517647058824 - Red Component - 0.00392156862745 - - Selected Text Color - - Alpha Component - 1 - Blue Component - 0.258823529412 - Color Space - Calibrated - Green Component - 0.227450980392 - Red Component - 0.219607843137 - - Selection Color - - Alpha Component - 1 - Blue Component - 1.0 - Color Space - Calibrated - Green Component - 0.807843137255 - Red Component - 0.749019607843 - - + + + + + + Ansi 0 Color + + Alpha Component + 1 + Blue Component + 0.258823529412 + Color Space + Calibrated + Green Component + 0.227450980392 + Red Component + 0.219607843137 + + Ansi 1 Color + + Alpha Component + 1 + Blue Component + 0.286274509804 + Color Space + Calibrated + Green Component + 0.337254901961 + Red Component + 0.894117647059 + + Ansi 10 Color + + Alpha Component + 1 + Blue Component + 0.474509803922 + Color Space + Calibrated + Green Component + 0.764705882353 + Red Component + 0.596078431373 + + Ansi 11 Color + + Alpha Component + 1 + Blue Component + 0.482352941176 + Color Space + Calibrated + Green Component + 0.752941176471 + Red Component + 0.898039215686 + + Ansi 12 Color + + Alpha Component + 1 + Blue Component + 0.937254901961 + Color Space + Calibrated + Green Component + 0.686274509804 + Red Component + 0.380392156863 + + Ansi 13 Color + + Alpha Component + 1 + Blue Component + 0.866666666667 + Color Space + Calibrated + Green Component + 0.470588235294 + Red Component + 0.776470588235 + + Ansi 14 Color + + Alpha Component + 1 + Blue Component + 0.760784313725 + Color Space + Calibrated + Green Component + 0.713725490196 + Red Component + 0.337254901961 + + Ansi 15 Color + + Alpha Component + 1 + Blue Component + 1.0 + Color Space + Calibrated + Green Component + 1.0 + Red Component + 1.0 + + Ansi 2 Color + + Alpha Component + 1 + Blue Component + 0.309803921569 + Color Space + Calibrated + Green Component + 0.63137254902 + Red Component + 0.313725490196 + + Ansi 3 Color + + Alpha Component + 1 + Blue Component + 0.00392156862745 + Color Space + Calibrated + Green Component + 0.517647058824 + Red Component + 0.756862745098 + + Ansi 4 Color + + Alpha Component + 1 + Blue Component + 0.737254901961 + Color Space + Calibrated + Green Component + 0.517647058824 + Red Component + 0.00392156862745 + + Ansi 5 Color + + Alpha Component + 1 + Blue Component + 0.643137254902 + Color Space + Calibrated + Green Component + 0.149019607843 + Red Component + 0.650980392157 + + Ansi 6 Color + + Alpha Component + 1 + Blue Component + 0.701960784314 + Color Space + Calibrated + Green Component + 0.592156862745 + Red Component + 0.0352941176471 + + Ansi 7 Color + + Alpha Component + 1 + Blue Component + 0.980392156863 + Color Space + Calibrated + Green Component + 0.980392156863 + Red Component + 0.980392156863 + + Ansi 8 Color + + Alpha Component + 1 + Blue Component + 0.36862745098 + Color Space + Calibrated + Green Component + 0.321568627451 + Red Component + 0.309803921569 + + Ansi 9 Color + + Alpha Component + 1 + Blue Component + 0.458823529412 + Color Space + Calibrated + Green Component + 0.423529411765 + Red Component + 0.878431372549 + + Background Color + + Alpha Component + 1 + Blue Component + 0.980392156863 + Color Space + Calibrated + Green Component + 0.980392156863 + Red Component + 0.980392156863 + + Badge Color + + Alpha Component + 0.5 + Blue Component + 0.0 + Color Space + Calibrated + Green Component + 0.0 + Red Component + 1 + + Bold Color + + Alpha Component + 1 + Blue Component + 0.74901962280273438 + Color Space + Calibrated + Green Component + 0.69803923368453979 + Red Component + 0.67058825492858887 + + Cursor Color + + Alpha Component + 1 + Blue Component + 1.0 + Color Space + Calibrated + Green Component + 0.807843137255 + Red Component + 0.749019607843 + + Cursor Guide Color + + Alpha Component + 0.25 + Blue Component + 0.941176470588 + Color Space + Calibrated + Green Component + 0.941176470588 + Red Component + 0.941176470588 + + Cursor Text Color + + Alpha Component + 1 + Blue Component + 0.258823529412 + Color Space + Calibrated + Green Component + 0.227450980392 + Red Component + 0.219607843137 + + Foreground Color + + Alpha Component + 1 + Blue Component + 0.258823529412 + Color Space + Calibrated + Green Component + 0.227450980392 + Red Component + 0.219607843137 + + Link Color + + Alpha Component + 1 + Blue Component + 0.737254901961 + Color Space + Calibrated + Green Component + 0.517647058824 + Red Component + 0.00392156862745 + + Selected Text Color + + Alpha Component + 1 + Blue Component + 0.258823529412 + Color Space + Calibrated + Green Component + 0.227450980392 + Red Component + 0.219607843137 + + Selection Color + + Alpha Component + 1 + Blue Component + 1.0 + Color Space + Calibrated + Green Component + 0.807843137255 + Red Component + 0.749019607843 + + \ No newline at end of file diff --git a/src/tools/ColorTool/schemes/campbell-absolute.ini b/src/tools/ColorTool/schemes/campbell-absolute.ini new file mode 100644 index 00000000000..3b6dc177af6 --- /dev/null +++ b/src/tools/ColorTool/schemes/campbell-absolute.ini @@ -0,0 +1,29 @@ +[table] +DARK_BLACK = 0,0,0 +DARK_BLUE = 0,55,218 +DARK_GREEN = 19,161,14 +DARK_CYAN = 58,150,221 +DARK_RED = 197,15,31 +DARK_MAGENTA = 136,23,152 +DARK_YELLOW = 193,156,0 +DARK_WHITE = 204,204,204 +BRIGHT_BLACK = 118,118,118 +BRIGHT_BLUE = 59,120,255 +BRIGHT_GREEN = 22,198,12 +BRIGHT_CYAN = 97,214,214 +BRIGHT_RED = 231,72,86 +BRIGHT_MAGENTA = 180,0,158 +BRIGHT_YELLOW = 249,241,165 +BRIGHT_WHITE = 255,255,255 + +[screen] +FOREGROUND = DARK_WHITE +BACKGROUND = DARK_BLACK + +[popup] +FOREGROUND = DARK_MAGENTA +BACKGROUND = BRIGHT_WHITE + +[info] +name = Campbell +author = crloew diff --git a/src/tools/ColorTool/schemes/campbell-legacy.ini b/src/tools/ColorTool/schemes/campbell-legacy.ini index 6f384025c62..d6f06b542ea 100644 --- a/src/tools/ColorTool/schemes/campbell-legacy.ini +++ b/src/tools/ColorTool/schemes/campbell-legacy.ini @@ -1,29 +1,29 @@ -[table] -DARK_BLACK = 19,17,23 -DARK_BLUE = 6,54,222 -DARK_GREEN = 57,151,50 -DARK_CYAN = 48,151,168 -DARK_RED = 185,0,5 -DARK_MAGENTA = 141,2,180 -DARK_YELLOW = 187,182,0 -DARK_WHITE = 192,190,197 -BRIGHT_BLACK = 85,82,92 -BRIGHT_BLUE = 62,109,253 -BRIGHT_GREEN = 11,213,0 -BRIGHT_CYAN = 128,205,253 -BRIGHT_RED = 237,57,96 -BRIGHT_MAGENTA = 198,2,253 -BRIGHT_YELLOW = 255,247,149 -BRIGHT_WHITE = 240,239,241 - -[screen] -FOREGROUND = DARK_WHITE -BACKGROUND = DARK_BLACK - -[popup] -FOREGROUND = DARK_MAGENTA -BACKGROUND = BRIGHT_WHITE - -[info] -name = Campbell-Legacy -author = paulcam +[table] +DARK_BLACK = 19,17,23 +DARK_BLUE = 6,54,222 +DARK_GREEN = 57,151,50 +DARK_CYAN = 48,151,168 +DARK_RED = 185,0,5 +DARK_MAGENTA = 141,2,180 +DARK_YELLOW = 187,182,0 +DARK_WHITE = 192,190,197 +BRIGHT_BLACK = 85,82,92 +BRIGHT_BLUE = 62,109,253 +BRIGHT_GREEN = 11,213,0 +BRIGHT_CYAN = 128,205,253 +BRIGHT_RED = 237,57,96 +BRIGHT_MAGENTA = 198,2,253 +BRIGHT_YELLOW = 255,247,149 +BRIGHT_WHITE = 240,239,241 + +[screen] +FOREGROUND = DARK_WHITE +BACKGROUND = DARK_BLACK + +[popup] +FOREGROUND = DARK_MAGENTA +BACKGROUND = BRIGHT_WHITE + +[info] +name = Campbell-Legacy +author = paulcam diff --git a/src/tools/ColorTool/schemes/campbell.ini b/src/tools/ColorTool/schemes/campbell.ini index 92357ed4e13..f7356c74278 100644 --- a/src/tools/ColorTool/schemes/campbell.ini +++ b/src/tools/ColorTool/schemes/campbell.ini @@ -1,29 +1,29 @@ -[table] -DARK_BLACK = 12,12,12 -DARK_BLUE = 0,55,218 -DARK_GREEN = 19,161,14 -DARK_CYAN = 58,150,221 -DARK_RED = 197,15,31 -DARK_MAGENTA = 136,23,152 -DARK_YELLOW = 193,156,0 -DARK_WHITE = 204,204,204 -BRIGHT_BLACK = 118,118,118 -BRIGHT_BLUE = 59,120,255 -BRIGHT_GREEN = 22,198,12 -BRIGHT_CYAN = 97,214,214 -BRIGHT_RED = 231,72,86 -BRIGHT_MAGENTA = 180,0,158 -BRIGHT_YELLOW = 249,241,165 -BRIGHT_WHITE = 242,242,242 - -[screen] -FOREGROUND = DARK_WHITE -BACKGROUND = DARK_BLACK - -[popup] -FOREGROUND = DARK_MAGENTA -BACKGROUND = BRIGHT_WHITE - -[info] -name = Campbell -author = crloew +[table] +DARK_BLACK = 12,12,12 +DARK_BLUE = 0,55,218 +DARK_GREEN = 19,161,14 +DARK_CYAN = 58,150,221 +DARK_RED = 197,15,31 +DARK_MAGENTA = 136,23,152 +DARK_YELLOW = 193,156,0 +DARK_WHITE = 204,204,204 +BRIGHT_BLACK = 118,118,118 +BRIGHT_BLUE = 59,120,255 +BRIGHT_GREEN = 22,198,12 +BRIGHT_CYAN = 97,214,214 +BRIGHT_RED = 231,72,86 +BRIGHT_MAGENTA = 180,0,158 +BRIGHT_YELLOW = 249,241,165 +BRIGHT_WHITE = 242,242,242 + +[screen] +FOREGROUND = DARK_WHITE +BACKGROUND = DARK_BLACK + +[popup] +FOREGROUND = DARK_MAGENTA +BACKGROUND = BRIGHT_WHITE + +[info] +name = Campbell +author = crloew diff --git a/src/tools/ColorTool/schemes/cmd-legacy.ini b/src/tools/ColorTool/schemes/cmd-legacy.ini index 0173da0bd3e..232350aad55 100644 --- a/src/tools/ColorTool/schemes/cmd-legacy.ini +++ b/src/tools/ColorTool/schemes/cmd-legacy.ini @@ -1,29 +1,29 @@ -[table] -DARK_BLACK = 0, 0, 0 -DARK_BLUE = 0, 0, 128 -DARK_GREEN = 0, 128, 0 -DARK_CYAN = 0, 128, 128 -DARK_RED = 128, 0, 0 -DARK_MAGENTA = 128, 0, 128 -DARK_YELLOW = 128, 128, 0 -DARK_WHITE = 192, 192, 192 -BRIGHT_BLACK = 128, 128, 128 -BRIGHT_BLUE = 0, 0, 255 -BRIGHT_GREEN = 0, 255, 0 -BRIGHT_CYAN = 0, 255, 255 -BRIGHT_RED = 255, 0, 0 -BRIGHT_MAGENTA = 255, 0, 255 -BRIGHT_YELLOW = 255, 255, 0 -BRIGHT_WHITE = 255, 255, 255 - -[screen] -FOREGROUND = DARK_WHITE -BACKGROUND = DARK_BLACK - -[popup] -FOREGROUND = DARK_MAGENTA -BACKGROUND = BRIGHT_WHITE - -[info] -name = cmd-legacy -author = unknown +[table] +DARK_BLACK = 0, 0, 0 +DARK_BLUE = 0, 0, 128 +DARK_GREEN = 0, 128, 0 +DARK_CYAN = 0, 128, 128 +DARK_RED = 128, 0, 0 +DARK_MAGENTA = 128, 0, 128 +DARK_YELLOW = 128, 128, 0 +DARK_WHITE = 192, 192, 192 +BRIGHT_BLACK = 128, 128, 128 +BRIGHT_BLUE = 0, 0, 255 +BRIGHT_GREEN = 0, 255, 0 +BRIGHT_CYAN = 0, 255, 255 +BRIGHT_RED = 255, 0, 0 +BRIGHT_MAGENTA = 255, 0, 255 +BRIGHT_YELLOW = 255, 255, 0 +BRIGHT_WHITE = 255, 255, 255 + +[screen] +FOREGROUND = DARK_WHITE +BACKGROUND = DARK_BLACK + +[popup] +FOREGROUND = DARK_MAGENTA +BACKGROUND = BRIGHT_WHITE + +[info] +name = cmd-legacy +author = unknown diff --git a/src/tools/ColorTool/schemes/deuteranopia.itermcolors b/src/tools/ColorTool/schemes/deuteranopia.itermcolors index 62e9251d205..3fec5451763 100644 --- a/src/tools/ColorTool/schemes/deuteranopia.itermcolors +++ b/src/tools/ColorTool/schemes/deuteranopia.itermcolors @@ -1,293 +1,293 @@ - - - - - - Ansi 0 Color - - Color Space - sRGB - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Ansi 1 Color - - Color Space - sRGB - Blue Component - 0 - Green Component - 0.6 - Red Component - 0.6 - - Ansi 10 Color - - Color Space - sRGB - Blue Component - 1 - Green Component - 0 - Red Component - 0 - - Ansi 11 Color - - Color Space - sRGB - Blue Component - 0.7490196078431373 - Green Component - 1 - Red Component - 1 - - Ansi 12 Color - - Color Space - sRGB - Blue Component - 1 - Green Component - 0.5019607843137255 - Red Component - 0.5019607843137255 - - Ansi 13 Color - - Color Space - sRGB - Blue Component - 0.5019607843137255 - Green Component - 1 - Red Component - 1 - - Ansi 14 Color - - Color Space - sRGB - Blue Component - 1 - Green Component - 0.7490196078431373 - Red Component - 0.7490196078431373 - - Ansi 15 Color - - Color Space - sRGB - Blue Component - 1 - Green Component - 1 - Red Component - 1 - - Ansi 2 Color - - Color Space - sRGB - Blue Component - 0.6 - Green Component - 0 - Red Component - 0 - - Ansi 3 Color - - Color Space - sRGB - Blue Component - 0.45098039215686275 - Green Component - 0.6 - Red Component - 0.6 - - Ansi 4 Color - - Color Space - sRGB - Blue Component - 0.6 - Green Component - 0.30196078431372547 - Red Component - 0.30196078431372547 - - Ansi 5 Color - - Color Space - sRGB - Blue Component - 0.30196078431372547 - Green Component - 0.6 - Red Component - 0.6 - - Ansi 6 Color - - Color Space - sRGB - Blue Component - 0.6 - Green Component - 0.45098039215686275 - Red Component - 0.45098039215686275 - - Ansi 7 Color - - Color Space - sRGB - Blue Component - 0.7686274509803922 - Green Component - 0.7686274509803922 - Red Component - 0.7686274509803922 - - Ansi 8 Color - - Color Space - sRGB - Blue Component - 0.4235294117647059 - Green Component - 0.4235294117647059 - Red Component - 0.4235294117647059 - - Ansi 9 Color - - Color Space - sRGB - Blue Component - 0 - Green Component - 1 - Red Component - 1 - - Background Color - - Color Space - sRGB - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Bold Color - - Color Space - sRGB - Blue Component - 0.7686274509803922 - Green Component - 0.7686274509803922 - Red Component - 0.7686274509803922 - - Cursor Color - - Color Space - sRGB - Blue Component - 0.7686274509803922 - Green Component - 0.7686274509803922 - Red Component - 0.7686274509803922 - - Cursor Text Color - - Color Space - sRGB - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Foreground Color - - Color Space - sRGB - Blue Component - 0.7686274509803922 - Green Component - 0.7686274509803922 - Red Component - 0.7686274509803922 - - Selected Text Color - - Color Space - sRGB - Blue Component - 0.7686274509803922 - Green Component - 0.7686274509803922 - Red Component - 0.7686274509803922 - - Selection Color - - Color Space - sRGB - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - - + + + + + + Ansi 0 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Ansi 1 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0.6 + Red Component + 0.6 + + Ansi 10 Color + + Color Space + sRGB + Blue Component + 1 + Green Component + 0 + Red Component + 0 + + Ansi 11 Color + + Color Space + sRGB + Blue Component + 0.7490196078431373 + Green Component + 1 + Red Component + 1 + + Ansi 12 Color + + Color Space + sRGB + Blue Component + 1 + Green Component + 0.5019607843137255 + Red Component + 0.5019607843137255 + + Ansi 13 Color + + Color Space + sRGB + Blue Component + 0.5019607843137255 + Green Component + 1 + Red Component + 1 + + Ansi 14 Color + + Color Space + sRGB + Blue Component + 1 + Green Component + 0.7490196078431373 + Red Component + 0.7490196078431373 + + Ansi 15 Color + + Color Space + sRGB + Blue Component + 1 + Green Component + 1 + Red Component + 1 + + Ansi 2 Color + + Color Space + sRGB + Blue Component + 0.6 + Green Component + 0 + Red Component + 0 + + Ansi 3 Color + + Color Space + sRGB + Blue Component + 0.45098039215686275 + Green Component + 0.6 + Red Component + 0.6 + + Ansi 4 Color + + Color Space + sRGB + Blue Component + 0.6 + Green Component + 0.30196078431372547 + Red Component + 0.30196078431372547 + + Ansi 5 Color + + Color Space + sRGB + Blue Component + 0.30196078431372547 + Green Component + 0.6 + Red Component + 0.6 + + Ansi 6 Color + + Color Space + sRGB + Blue Component + 0.6 + Green Component + 0.45098039215686275 + Red Component + 0.45098039215686275 + + Ansi 7 Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.7686274509803922 + Red Component + 0.7686274509803922 + + Ansi 8 Color + + Color Space + sRGB + Blue Component + 0.4235294117647059 + Green Component + 0.4235294117647059 + Red Component + 0.4235294117647059 + + Ansi 9 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 1 + Red Component + 1 + + Background Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Bold Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.7686274509803922 + Red Component + 0.7686274509803922 + + Cursor Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.7686274509803922 + Red Component + 0.7686274509803922 + + Cursor Text Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Foreground Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.7686274509803922 + Red Component + 0.7686274509803922 + + Selected Text Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.7686274509803922 + Red Component + 0.7686274509803922 + + Selection Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + + diff --git a/src/tools/ColorTool/schemes/solarized_dark.itermcolors b/src/tools/ColorTool/schemes/solarized_dark.itermcolors index 24c76f58199..6555c793f44 100644 --- a/src/tools/ColorTool/schemes/solarized_dark.itermcolors +++ b/src/tools/ColorTool/schemes/solarized_dark.itermcolors @@ -1,243 +1,243 @@ - - - - - - Ansi 0 Color - - Blue Component - 0.19370138645172119 - Green Component - 0.15575926005840302 - Red Component - 0.0 - - Ansi 1 Color - - Blue Component - 0.14145714044570923 - Green Component - 0.10840655118227005 - Red Component - 0.81926977634429932 - - Ansi 10 Color - - Blue Component - 0.38298487663269043 - Green Component - 0.35665956139564514 - Red Component - 0.27671992778778076 - - Ansi 11 Color - - Blue Component - 0.43850564956665039 - Green Component - 0.40717673301696777 - Red Component - 0.32436618208885193 - - Ansi 12 Color - - Blue Component - 0.51685798168182373 - Green Component - 0.50962930917739868 - Red Component - 0.44058024883270264 - - Ansi 13 Color - - Blue Component - 0.72908437252044678 - Green Component - 0.33896297216415405 - Red Component - 0.34798634052276611 - - Ansi 14 Color - - Blue Component - 0.56363654136657715 - Green Component - 0.56485837697982788 - Red Component - 0.50599193572998047 - - Ansi 15 Color - - Blue Component - 0.86405980587005615 - Green Component - 0.95794391632080078 - Red Component - 0.98943418264389038 - - Ansi 2 Color - - Blue Component - 0.020208755508065224 - Green Component - 0.54115492105484009 - Red Component - 0.44977453351020813 - - Ansi 3 Color - - Blue Component - 0.023484811186790466 - Green Component - 0.46751424670219421 - Red Component - 0.64746475219726562 - - Ansi 4 Color - - Blue Component - 0.78231418132781982 - Green Component - 0.46265947818756104 - Red Component - 0.12754884362220764 - - Ansi 5 Color - - Blue Component - 0.43516635894775391 - Green Component - 0.10802463442087173 - Red Component - 0.77738940715789795 - - Ansi 6 Color - - Blue Component - 0.52502274513244629 - Green Component - 0.57082360982894897 - Red Component - 0.14679534733295441 - - Ansi 7 Color - - Blue Component - 0.79781103134155273 - Green Component - 0.89001238346099854 - Red Component - 0.91611063480377197 - - Ansi 8 Color - - Blue Component - 0.39215686274509803 - Green Component - 0.30196078431372547 - Red Component - 0.0 - - Ansi 9 Color - - Blue Component - 0.073530435562133789 - Green Component - 0.21325300633907318 - Red Component - 0.74176257848739624 - - Background Color - - Blue Component - 0.15170273184776306 - Green Component - 0.11783610284328461 - Red Component - 0.0 - - Bold Color - - Blue Component - 0.56363654136657715 - Green Component - 0.56485837697982788 - Red Component - 0.50599193572998047 - - Cursor Color - - Blue Component - 0.51685798168182373 - Green Component - 0.50962930917739868 - Red Component - 0.44058024883270264 - - Cursor Text Color - - Blue Component - 0.19370138645172119 - Green Component - 0.15575926005840302 - Red Component - 0.0 - - Foreground Color - - Blue Component - 0.51685798168182373 - Green Component - 0.50962930917739868 - Red Component - 0.44058024883270264 - - Selected Text Color - - Blue Component - 0.56363654136657715 - Green Component - 0.56485837697982788 - Red Component - 0.50599193572998047 - - Selection Color - - Blue Component - 0.19370138645172119 - Green Component - 0.15575926005840302 - Red Component - 0.0 - - + + + + + + Ansi 0 Color + + Blue Component + 0.19370138645172119 + Green Component + 0.15575926005840302 + Red Component + 0.0 + + Ansi 1 Color + + Blue Component + 0.14145714044570923 + Green Component + 0.10840655118227005 + Red Component + 0.81926977634429932 + + Ansi 10 Color + + Blue Component + 0.38298487663269043 + Green Component + 0.35665956139564514 + Red Component + 0.27671992778778076 + + Ansi 11 Color + + Blue Component + 0.43850564956665039 + Green Component + 0.40717673301696777 + Red Component + 0.32436618208885193 + + Ansi 12 Color + + Blue Component + 0.51685798168182373 + Green Component + 0.50962930917739868 + Red Component + 0.44058024883270264 + + Ansi 13 Color + + Blue Component + 0.72908437252044678 + Green Component + 0.33896297216415405 + Red Component + 0.34798634052276611 + + Ansi 14 Color + + Blue Component + 0.56363654136657715 + Green Component + 0.56485837697982788 + Red Component + 0.50599193572998047 + + Ansi 15 Color + + Blue Component + 0.86405980587005615 + Green Component + 0.95794391632080078 + Red Component + 0.98943418264389038 + + Ansi 2 Color + + Blue Component + 0.020208755508065224 + Green Component + 0.54115492105484009 + Red Component + 0.44977453351020813 + + Ansi 3 Color + + Blue Component + 0.023484811186790466 + Green Component + 0.46751424670219421 + Red Component + 0.64746475219726562 + + Ansi 4 Color + + Blue Component + 0.78231418132781982 + Green Component + 0.46265947818756104 + Red Component + 0.12754884362220764 + + Ansi 5 Color + + Blue Component + 0.43516635894775391 + Green Component + 0.10802463442087173 + Red Component + 0.77738940715789795 + + Ansi 6 Color + + Blue Component + 0.52502274513244629 + Green Component + 0.57082360982894897 + Red Component + 0.14679534733295441 + + Ansi 7 Color + + Blue Component + 0.79781103134155273 + Green Component + 0.89001238346099854 + Red Component + 0.91611063480377197 + + Ansi 8 Color + + Blue Component + 0.39215686274509803 + Green Component + 0.30196078431372547 + Red Component + 0.0 + + Ansi 9 Color + + Blue Component + 0.073530435562133789 + Green Component + 0.21325300633907318 + Red Component + 0.74176257848739624 + + Background Color + + Blue Component + 0.15170273184776306 + Green Component + 0.11783610284328461 + Red Component + 0.0 + + Bold Color + + Blue Component + 0.56363654136657715 + Green Component + 0.56485837697982788 + Red Component + 0.50599193572998047 + + Cursor Color + + Blue Component + 0.51685798168182373 + Green Component + 0.50962930917739868 + Red Component + 0.44058024883270264 + + Cursor Text Color + + Blue Component + 0.19370138645172119 + Green Component + 0.15575926005840302 + Red Component + 0.0 + + Foreground Color + + Blue Component + 0.51685798168182373 + Green Component + 0.50962930917739868 + Red Component + 0.44058024883270264 + + Selected Text Color + + Blue Component + 0.56363654136657715 + Green Component + 0.56485837697982788 + Red Component + 0.50599193572998047 + + Selection Color + + Blue Component + 0.19370138645172119 + Green Component + 0.15575926005840302 + Red Component + 0.0 + + \ No newline at end of file diff --git a/src/tools/ColorTool/schemes/solarized_light.itermcolors b/src/tools/ColorTool/schemes/solarized_light.itermcolors index b39cc7714cd..8dbdaacc527 100644 --- a/src/tools/ColorTool/schemes/solarized_light.itermcolors +++ b/src/tools/ColorTool/schemes/solarized_light.itermcolors @@ -1,289 +1,289 @@ - - - - - - Ansi 0 Color - - Color Space - sRGB - Blue Component - 0.8901960784313725 - Green Component - 0.9647058823529412 - Red Component - 0.9921568627450981 - - Ansi 1 Color - - Color Space - sRGB - Blue Component - 0.1843137254901961 - Green Component - 0.19607843137254902 - Red Component - 0.8627450980392157 - - Ansi 10 Color - - Color Space - sRGB - Blue Component - 0 - Green Component - 0.6 - Red Component - 0.5215686274509804 - - Ansi 11 Color - - Color Space - sRGB - Blue Component - 0 - Green Component - 0.5372549019607843 - Red Component - 0.7098039215686275 - - Ansi 12 Color - - Color Space - sRGB - Blue Component - 0.8235294117647058 - Green Component - 0.5450980392156862 - Red Component - 0.14901960784313725 - - Ansi 13 Color - - Color Space - sRGB - Blue Component - 0.7686274509803922 - Green Component - 0.44313725490196076 - Red Component - 0.4235294117647059 - - Ansi 14 Color - - Color Space - sRGB - Blue Component - 0.596078431372549 - Green Component - 0.6313725490196078 - Red Component - 0.16470588235294117 - - Ansi 15 Color - - Color Space - sRGB - Blue Component - 0.4588235294117647 - Green Component - 0.43137254901960786 - Red Component - 0.34509803921568627 - - Ansi 2 Color - - Color Space - sRGB - Blue Component - 0 - Green Component - 0.6 - Red Component - 0.5215686274509804 - - Ansi 3 Color - - Color Space - sRGB - Blue Component - 0 - Green Component - 0.5372549019607843 - Red Component - 0.7098039215686275 - - Ansi 4 Color - - Color Space - sRGB - Blue Component - 0.8235294117647058 - Green Component - 0.5450980392156862 - Red Component - 0.14901960784313725 - - Ansi 5 Color - - Color Space - sRGB - Blue Component - 0.7686274509803922 - Green Component - 0.44313725490196076 - Red Component - 0.4235294117647059 - - Ansi 6 Color - - Color Space - sRGB - Blue Component - 0.596078431372549 - Green Component - 0.6313725490196078 - Red Component - 0.16470588235294117 - - Ansi 7 Color - - Color Space - sRGB - Blue Component - 0.6313725490196078 - Green Component - 0.6313725490196078 - Red Component - 0.5764705882352941 - - Ansi 8 Color - - Color Space - sRGB - Blue Component - 0.5137254901960784 - Green Component - 0.4823529411764706 - Red Component - 0.396078431372549 - - Ansi 9 Color - - Color Space - sRGB - Blue Component - 0.1843137254901961 - Green Component - 0.19607843137254902 - Red Component - 0.8627450980392157 - - Background Color - - Color Space - sRGB - Blue Component - 0.8901960784313725 - Green Component - 0.9647058823529412 - Red Component - 0.9921568627450981 - - Bold Color - - Color Space - sRGB - Blue Component - 0.4588235294117647 - Green Component - 0.43137254901960786 - Red Component - 0.34509803921568627 - - Cursor Color - - Color Space - sRGB - Blue Component - 0.4588235294117647 - Green Component - 0.43137254901960786 - Red Component - 0.34509803921568627 - - Cursor Text Color - - Color Space - sRGB - Blue Component - 0.8901960784313725 - Green Component - 0.9647058823529412 - Red Component - 0.9921568627450981 - - Foreground Color - - Color Space - sRGB - Blue Component - 0.4588235294117647 - Green Component - 0.43137254901960786 - Red Component - 0.34509803921568627 - - Selected Text Color - - Color Space - sRGB - Blue Component - 0.4588235294117647 - Green Component - 0.43137254901960786 - Red Component - 0.34509803921568627 - - Selection Color - - Color Space - sRGB - Blue Component - 0.8901960784313725 - Green Component - 0.9647058823529412 - Red Component - 0.9921568627450981 - - - + + + + + + Ansi 0 Color + + Color Space + sRGB + Blue Component + 0.8901960784313725 + Green Component + 0.9647058823529412 + Red Component + 0.9921568627450981 + + Ansi 1 Color + + Color Space + sRGB + Blue Component + 0.1843137254901961 + Green Component + 0.19607843137254902 + Red Component + 0.8627450980392157 + + Ansi 10 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0.6 + Red Component + 0.5215686274509804 + + Ansi 11 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0.5372549019607843 + Red Component + 0.7098039215686275 + + Ansi 12 Color + + Color Space + sRGB + Blue Component + 0.8235294117647058 + Green Component + 0.5450980392156862 + Red Component + 0.14901960784313725 + + Ansi 13 Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.44313725490196076 + Red Component + 0.4235294117647059 + + Ansi 14 Color + + Color Space + sRGB + Blue Component + 0.596078431372549 + Green Component + 0.6313725490196078 + Red Component + 0.16470588235294117 + + Ansi 15 Color + + Color Space + sRGB + Blue Component + 0.4588235294117647 + Green Component + 0.43137254901960786 + Red Component + 0.34509803921568627 + + Ansi 2 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0.6 + Red Component + 0.5215686274509804 + + Ansi 3 Color + + Color Space + sRGB + Blue Component + 0 + Green Component + 0.5372549019607843 + Red Component + 0.7098039215686275 + + Ansi 4 Color + + Color Space + sRGB + Blue Component + 0.8235294117647058 + Green Component + 0.5450980392156862 + Red Component + 0.14901960784313725 + + Ansi 5 Color + + Color Space + sRGB + Blue Component + 0.7686274509803922 + Green Component + 0.44313725490196076 + Red Component + 0.4235294117647059 + + Ansi 6 Color + + Color Space + sRGB + Blue Component + 0.596078431372549 + Green Component + 0.6313725490196078 + Red Component + 0.16470588235294117 + + Ansi 7 Color + + Color Space + sRGB + Blue Component + 0.6313725490196078 + Green Component + 0.6313725490196078 + Red Component + 0.5764705882352941 + + Ansi 8 Color + + Color Space + sRGB + Blue Component + 0.5137254901960784 + Green Component + 0.4823529411764706 + Red Component + 0.396078431372549 + + Ansi 9 Color + + Color Space + sRGB + Blue Component + 0.1843137254901961 + Green Component + 0.19607843137254902 + Red Component + 0.8627450980392157 + + Background Color + + Color Space + sRGB + Blue Component + 0.8901960784313725 + Green Component + 0.9647058823529412 + Red Component + 0.9921568627450981 + + Bold Color + + Color Space + sRGB + Blue Component + 0.4588235294117647 + Green Component + 0.43137254901960786 + Red Component + 0.34509803921568627 + + Cursor Color + + Color Space + sRGB + Blue Component + 0.4588235294117647 + Green Component + 0.43137254901960786 + Red Component + 0.34509803921568627 + + Cursor Text Color + + Color Space + sRGB + Blue Component + 0.8901960784313725 + Green Component + 0.9647058823529412 + Red Component + 0.9921568627450981 + + Foreground Color + + Color Space + sRGB + Blue Component + 0.4588235294117647 + Green Component + 0.43137254901960786 + Red Component + 0.34509803921568627 + + Selected Text Color + + Color Space + sRGB + Blue Component + 0.4588235294117647 + Green Component + 0.43137254901960786 + Red Component + 0.34509803921568627 + + Selection Color + + Color Space + sRGB + Blue Component + 0.8901960784313725 + Green Component + 0.9647058823529412 + Red Component + 0.9921568627450981 + + + diff --git a/src/tools/ColorTool/schemes/tango_dark.itermcolors b/src/tools/ColorTool/schemes/tango_dark.itermcolors index 8405cf8f109..2a5080bc8ff 100644 --- a/src/tools/ColorTool/schemes/tango_dark.itermcolors +++ b/src/tools/ColorTool/schemes/tango_dark.itermcolors @@ -1,224 +1,224 @@ - - - - - - Ansi 0 Color - - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Ansi 1 Color - - Blue Component - 0 - Green Component - 0 - Red Component - 0.8 - - Ansi 10 Color - - Blue Component - 0.2039216 - Green Component - 0.8862745 - Red Component - 0.5411764999999999 - - Ansi 11 Color - - Blue Component - 0.3098039 - Green Component - 0.9137255 - Red Component - 0.9882353 - - Ansi 12 Color - - Blue Component - 0.8117647 - Green Component - 0.6235294 - Red Component - 0.4470588 - - Ansi 13 Color - - Blue Component - 0.6588235 - Green Component - 0.4980392 - Red Component - 0.6784314 - - Ansi 14 Color - - Blue Component - 0.8862745 - Green Component - 0.8862745 - Red Component - 0.2039216 - - Ansi 15 Color - - Blue Component - 0.9254902 - Green Component - 0.9333333 - Red Component - 0.9333333 - - Ansi 2 Color - - Blue Component - 0.02352941 - Green Component - 0.6039215999999999 - Red Component - 0.3058824 - - Ansi 3 Color - - Blue Component - 0 - Green Component - 0.627451 - Red Component - 0.7686275 - - Ansi 4 Color - - Blue Component - 0.6431373 - Green Component - 0.3960784 - Red Component - 0.2039216 - - Ansi 5 Color - - Blue Component - 0.4823529 - Green Component - 0.3137255 - Red Component - 0.4588235 - - Ansi 6 Color - - Blue Component - 0.6039215999999999 - Green Component - 0.5960785 - Red Component - 0.02352941 - - Ansi 7 Color - - Blue Component - 0.8117647 - Green Component - 0.8431373 - Red Component - 0.827451 - - Ansi 8 Color - - Blue Component - 0.3254902 - Green Component - 0.3411765 - Red Component - 0.3333333 - - Ansi 9 Color - - Blue Component - 0.1607843 - Green Component - 0.1607843 - Red Component - 0.9372549 - - Background Color - - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Bold Color - - Blue Component - 1 - Green Component - 1 - Red Component - 1 - - Cursor Color - - Blue Component - 1 - Green Component - 1 - Red Component - 1 - - Cursor Text Color - - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Foreground Color - - Blue Component - 0.8117647 - Green Component - 0.8431373 - Red Component - 0.827451 - - Selected Text Color - - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Selection Color - - Blue Component - 1 - Green Component - 0.8353 - Red Component - 0.7098 - - - + + + + + + Ansi 0 Color + + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Ansi 1 Color + + Blue Component + 0 + Green Component + 0 + Red Component + 0.8 + + Ansi 10 Color + + Blue Component + 0.2039216 + Green Component + 0.8862745 + Red Component + 0.5411764999999999 + + Ansi 11 Color + + Blue Component + 0.3098039 + Green Component + 0.9137255 + Red Component + 0.9882353 + + Ansi 12 Color + + Blue Component + 0.8117647 + Green Component + 0.6235294 + Red Component + 0.4470588 + + Ansi 13 Color + + Blue Component + 0.6588235 + Green Component + 0.4980392 + Red Component + 0.6784314 + + Ansi 14 Color + + Blue Component + 0.8862745 + Green Component + 0.8862745 + Red Component + 0.2039216 + + Ansi 15 Color + + Blue Component + 0.9254902 + Green Component + 0.9333333 + Red Component + 0.9333333 + + Ansi 2 Color + + Blue Component + 0.02352941 + Green Component + 0.6039215999999999 + Red Component + 0.3058824 + + Ansi 3 Color + + Blue Component + 0 + Green Component + 0.627451 + Red Component + 0.7686275 + + Ansi 4 Color + + Blue Component + 0.6431373 + Green Component + 0.3960784 + Red Component + 0.2039216 + + Ansi 5 Color + + Blue Component + 0.4823529 + Green Component + 0.3137255 + Red Component + 0.4588235 + + Ansi 6 Color + + Blue Component + 0.6039215999999999 + Green Component + 0.5960785 + Red Component + 0.02352941 + + Ansi 7 Color + + Blue Component + 0.8117647 + Green Component + 0.8431373 + Red Component + 0.827451 + + Ansi 8 Color + + Blue Component + 0.3254902 + Green Component + 0.3411765 + Red Component + 0.3333333 + + Ansi 9 Color + + Blue Component + 0.1607843 + Green Component + 0.1607843 + Red Component + 0.9372549 + + Background Color + + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Bold Color + + Blue Component + 1 + Green Component + 1 + Red Component + 1 + + Cursor Color + + Blue Component + 1 + Green Component + 1 + Red Component + 1 + + Cursor Text Color + + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Foreground Color + + Blue Component + 0.8117647 + Green Component + 0.8431373 + Red Component + 0.827451 + + Selected Text Color + + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Selection Color + + Blue Component + 1 + Green Component + 0.8353 + Red Component + 0.7098 + + + diff --git a/src/tools/ColorTool/schemes/tango_light.itermcolors b/src/tools/ColorTool/schemes/tango_light.itermcolors index df041982a10..dc7a7d5140a 100644 --- a/src/tools/ColorTool/schemes/tango_light.itermcolors +++ b/src/tools/ColorTool/schemes/tango_light.itermcolors @@ -1,224 +1,224 @@ - - - - - - Ansi 0 Color - - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Ansi 1 Color - - Blue Component - 0 - Green Component - 0 - Red Component - 0.8 - - Ansi 10 Color - - Blue Component - 0.2039216 - Green Component - 0.8862745 - Red Component - 0.5411764999999999 - - Ansi 11 Color - - Blue Component - 0.3098039 - Green Component - 0.9137255 - Red Component - 0.9882353 - - Ansi 12 Color - - Blue Component - 0.8117647 - Green Component - 0.6235294 - Red Component - 0.4470588 - - Ansi 13 Color - - Blue Component - 0.6588235 - Green Component - 0.4980392 - Red Component - 0.6784314 - - Ansi 14 Color - - Blue Component - 0.8862745 - Green Component - 0.8862745 - Red Component - 0.2039216 - - Ansi 15 Color - - Blue Component - 0.9254902 - Green Component - 0.9333333 - Red Component - 0.9333333 - - Ansi 2 Color - - Blue Component - 0.02352941 - Green Component - 0.6039215999999999 - Red Component - 0.3058824 - - Ansi 3 Color - - Blue Component - 0 - Green Component - 0.627451 - Red Component - 0.7686275 - - Ansi 4 Color - - Blue Component - 0.6431373 - Green Component - 0.3960784 - Red Component - 0.2039216 - - Ansi 5 Color - - Blue Component - 0.4823529 - Green Component - 0.3137255 - Red Component - 0.4588235 - - Ansi 6 Color - - Blue Component - 0.6039215999999999 - Green Component - 0.5960785 - Red Component - 0.02352941 - - Ansi 7 Color - - Blue Component - 0.8117647 - Green Component - 0.8431373 - Red Component - 0.827451 - - Ansi 8 Color - - Blue Component - 0.3254902 - Green Component - 0.3411765 - Red Component - 0.3333333 - - Ansi 9 Color - - Blue Component - 0.1607843 - Green Component - 0.1607843 - Red Component - 0.9372549 - - Background Color - - Blue Component - 1 - Green Component - 1 - Red Component - 1 - - Bold Color - - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Cursor Color - - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Cursor Text Color - - Blue Component - 1 - Green Component - 1 - Red Component - 1 - - Foreground Color - - Blue Component - 0.3254902 - Green Component - 0.3411765 - Red Component - 0.3333333 - - Selected Text Color - - Blue Component - 0 - Green Component - 0 - Red Component - 0 - - Selection Color - - Blue Component - 1 - Green Component - 0.8353 - Red Component - 0.7098 - - - + + + + + + Ansi 0 Color + + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Ansi 1 Color + + Blue Component + 0 + Green Component + 0 + Red Component + 0.8 + + Ansi 10 Color + + Blue Component + 0.2039216 + Green Component + 0.8862745 + Red Component + 0.5411764999999999 + + Ansi 11 Color + + Blue Component + 0.3098039 + Green Component + 0.9137255 + Red Component + 0.9882353 + + Ansi 12 Color + + Blue Component + 0.8117647 + Green Component + 0.6235294 + Red Component + 0.4470588 + + Ansi 13 Color + + Blue Component + 0.6588235 + Green Component + 0.4980392 + Red Component + 0.6784314 + + Ansi 14 Color + + Blue Component + 0.8862745 + Green Component + 0.8862745 + Red Component + 0.2039216 + + Ansi 15 Color + + Blue Component + 0.9254902 + Green Component + 0.9333333 + Red Component + 0.9333333 + + Ansi 2 Color + + Blue Component + 0.02352941 + Green Component + 0.6039215999999999 + Red Component + 0.3058824 + + Ansi 3 Color + + Blue Component + 0 + Green Component + 0.627451 + Red Component + 0.7686275 + + Ansi 4 Color + + Blue Component + 0.6431373 + Green Component + 0.3960784 + Red Component + 0.2039216 + + Ansi 5 Color + + Blue Component + 0.4823529 + Green Component + 0.3137255 + Red Component + 0.4588235 + + Ansi 6 Color + + Blue Component + 0.6039215999999999 + Green Component + 0.5960785 + Red Component + 0.02352941 + + Ansi 7 Color + + Blue Component + 0.8117647 + Green Component + 0.8431373 + Red Component + 0.827451 + + Ansi 8 Color + + Blue Component + 0.3254902 + Green Component + 0.3411765 + Red Component + 0.3333333 + + Ansi 9 Color + + Blue Component + 0.1607843 + Green Component + 0.1607843 + Red Component + 0.9372549 + + Background Color + + Blue Component + 1 + Green Component + 1 + Red Component + 1 + + Bold Color + + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Cursor Color + + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Cursor Text Color + + Blue Component + 1 + Green Component + 1 + Red Component + 1 + + Foreground Color + + Blue Component + 0.3254902 + Green Component + 0.3411765 + Red Component + 0.3333333 + + Selected Text Color + + Blue Component + 0 + Green Component + 0 + Red Component + 0 + + Selection Color + + Blue Component + 1 + Green Component + 0.8353 + Red Component + 0.7098 + + + diff --git a/src/tools/RenderingTests/main.cpp b/src/tools/RenderingTests/main.cpp index a82e38bb818..45dfcf66ea7 100644 --- a/src/tools/RenderingTests/main.cpp +++ b/src/tools/RenderingTests/main.cpp @@ -5,7 +5,39 @@ #include #include -#include +#include + +// The following list of colors is only used as a debug aid and not part of the final product. +// They're licensed under: +// +// Apache-Style Software License for ColorBrewer software and ColorBrewer Color Schemes +// +// Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State University. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +namespace colorbrewer +{ + inline constexpr uint32_t pastel1[]{ + 0xfbb4ae, + 0xb3cde3, + 0xccebc5, + 0xdecbe4, + 0xfed9a6, + 0xffffcc, + 0xe5d8bd, + 0xfddaec, + 0xf2f2f2, + }; +} // Another variant of "defer" for C++. namespace @@ -110,64 +142,93 @@ int main() }; { - struct ConsoleAttributeTest + struct AttributeTest { const wchar_t* text = nullptr; WORD attribute = 0; }; - static constexpr ConsoleAttributeTest consoleAttributeTests[]{ - { L"Console attributes:", 0 }, + + { + static constexpr AttributeTest consoleAttributeTests[]{ + { L"Console attributes:", 0 }, #define MAKE_TEST_FOR_ATTRIBUTE(attr) { L## #attr, attr } - MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_HORIZONTAL), - MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_LVERTICAL), - MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_RVERTICAL), - MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_REVERSE_VIDEO), - MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_UNDERSCORE), + MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_HORIZONTAL), + MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_LVERTICAL), + MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_RVERTICAL), + MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_REVERSE_VIDEO), + MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_UNDERSCORE), #undef MAKE_TEST_FOR_ATTRIBUTE - { L"all gridlines", COMMON_LVB_GRID_HORIZONTAL | COMMON_LVB_GRID_LVERTICAL | COMMON_LVB_GRID_RVERTICAL | COMMON_LVB_UNDERSCORE }, - { L"all attributes", COMMON_LVB_GRID_HORIZONTAL | COMMON_LVB_GRID_LVERTICAL | COMMON_LVB_GRID_RVERTICAL | COMMON_LVB_REVERSE_VIDEO | COMMON_LVB_UNDERSCORE }, - }; + { L"all gridlines", COMMON_LVB_GRID_HORIZONTAL | COMMON_LVB_GRID_LVERTICAL | COMMON_LVB_GRID_RVERTICAL | COMMON_LVB_UNDERSCORE }, + { L"all attributes", COMMON_LVB_GRID_HORIZONTAL | COMMON_LVB_GRID_LVERTICAL | COMMON_LVB_GRID_RVERTICAL | COMMON_LVB_REVERSE_VIDEO | COMMON_LVB_UNDERSCORE }, + }; - SHORT row = 2; - for (const auto& t : consoleAttributeTests) - { - const auto length = static_cast(wcslen(t.text)); - printfUTF16(L"\x1B[%d;5H%s", row + 1, t.text); + SHORT row = 2; + for (const auto& t : consoleAttributeTests) + { + const auto length = static_cast(wcslen(t.text)); + printfUTF16(L"\x1B[%d;5H%s", row + 1, t.text); - WORD attributes[32]; - std::fill_n(&attributes[0], length, static_cast(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | t.attribute)); + WORD attributes[32]; + std::fill_n(&attributes[0], length, static_cast(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | t.attribute)); - DWORD numberOfAttrsWritten; - WriteConsoleOutputAttribute(outputHandle, attributes, length, { 4, row }, &numberOfAttrsWritten); + DWORD numberOfAttrsWritten; + WriteConsoleOutputAttribute(outputHandle, attributes, length, { 4, row }, &numberOfAttrsWritten); - row += 2; + row += 2; + } } - struct VTAttributeTest { - const wchar_t* text = nullptr; - int sgr = 0; - }; - static constexpr VTAttributeTest vtAttributeTests[]{ - { L"ANSI escape SGR:", 0 }, - { L"bold", 1 }, - { L"faint", 2 }, - { L"italic", 3 }, - { L"underline", 4 }, - { L"reverse", 7 }, - { L"strikethrough", 9 }, - { L"double underline", 21 }, - { L"overlined", 53 }, - }; + static constexpr AttributeTest basicSGR[]{ + { L"bold", 1 }, + { L"faint", 2 }, + { L"italic", 3 }, + { L"underline", 4 }, + { L"reverse", 7 }, + { L"strikethrough", 9 }, + { L"double underline", 21 }, + { L"overlined", 53 }, + }; + + printfUTF16(L"\x1B[3;39HANSI escape SGR:"); + + int row = 5; + for (const auto& t : basicSGR) + { + printfUTF16(L"\x1B[%d;39H\x1b[%dm%s\x1b[m", row, t.attribute, t.text); + row += 2; + } - row = 3; - for (const auto& t : vtAttributeTests) - { - printfUTF16(L"\x1B[%d;45H\x1b[%dm%s\x1b[m", row, t.sgr, t.text); - row += 2; + printfUTF16(L"\x1B[%d;39H\x1b]8;;https://example.com\x1b\\hyperlink\x1b]8;;\x1b\\", row); } - printfUTF16(L"\x1B[%d;45H\x1b]8;;https://example.com\x1b\\hyperlink\x1b]8;;\x1b\\", row); + { + static constexpr AttributeTest styledUnderlines[]{ + { L"straight", 1 }, + { L"double", 2 }, + { L"curly", 3 }, + { L"dotted", 4 }, + { L"dashed", 5 }, + }; + + printfUTF16(L"\x1B[3;63HStyled Underlines:"); + + int row = 5; + for (const auto& t : styledUnderlines) + { + printfUTF16(L"\x1B[%d;63H\x1b[4:%dm", row, t.attribute); + + const auto len = wcslen(t.text); + for (size_t i = 0; i < len; ++i) + { + const auto color = colorbrewer::pastel1[i % std::size(colorbrewer::pastel1)]; + printfUTF16(L"\x1B[58:2::%d:%d:%dm%c", (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff, t.text[i]); + } + + printfUTF16(L"\x1b[m"); + row += 2; + } + } wait(); clear(); diff --git a/src/tools/integrity/lib/util.cpp b/src/tools/integrity/lib/util.cpp index 3ea8e4c6e9f..557ce8061b0 100644 --- a/src/tools/integrity/lib/util.cpp +++ b/src/tools/integrity/lib/util.cpp @@ -14,7 +14,7 @@ PCWSTR GetIntegrityLevel() DWORD dwIntegrityLevel = 0; // Get the Integrity level. - wistd::unique_ptr tokenLabel; + wil::unique_tokeninfo_ptr tokenLabel; THROW_IF_FAILED(wil::GetTokenInformationNoThrow(tokenLabel, GetCurrentProcessToken())); dwIntegrityLevel = *GetSidSubAuthority(tokenLabel->Label.Sid, diff --git a/src/tools/vtapp/sources.dep b/src/tools/vtapp/sources.dep deleted file mode 100644 index 5aef2e2c5c6..00000000000 --- a/src/tools/vtapp/sources.dep +++ /dev/null @@ -1,3 +0,0 @@ -PUBLIC_PASS0_CONSUMES= \ - onecore\redist\mspartners\netfx45\core\binary_release|PASS0 \ - diff --git a/src/tsf/sources b/src/tsf/sources index d18e015d2c9..54c79162097 100644 --- a/src/tsf/sources +++ b/src/tsf/sources @@ -53,7 +53,6 @@ SOURCES = \ INCLUDES = \ $(INCLUDES); \ ..\inc; \ - $(ONECORE_PRIV_SDK_INC_PATH); \ $(MINWIN_INTERNAL_PRIV_SDK_INC_PATH_L); \ $(SDK_INC_PATH)\atl30; \ $(ONECORE_EXTERNAL_SDK_INC_PATH)\atl30; \ diff --git a/src/types/UiaTextRangeBase.cpp b/src/types/UiaTextRangeBase.cpp index af5ebb431ea..788386fa8df 100644 --- a/src/types/UiaTextRangeBase.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -405,16 +405,22 @@ std::optional UiaTextRangeBase::_verifyAttr(TEXTATTRIBUTEID attributeId, V THROW_HR_IF(E_INVALIDARG, val.vt != VT_I4); // The underline style is stored as a TextDecorationLineStyle. - // However, The text buffer doesn't have that many different styles for being underlined. - // Instead, we only have single and double underlined. + // However, The text buffer doesn't have all the different styles for being underlined. + // Instead, we only use a subset of them. switch (val.lVal) { case TextDecorationLineStyle_None: return !attr.IsUnderlined(); + case TextDecorationLineStyle_Single: + return attr.GetUnderlineStyle() == UnderlineStyle::SinglyUnderlined; case TextDecorationLineStyle_Double: return attr.GetUnderlineStyle() == UnderlineStyle::DoublyUnderlined; - case TextDecorationLineStyle_Single: // singly underlined and extended styles are treated the same - return attr.IsUnderlined() && attr.GetUnderlineStyle() != UnderlineStyle::DoublyUnderlined; + case TextDecorationLineStyle_Wavy: + return attr.GetUnderlineStyle() == UnderlineStyle::CurlyUnderlined; + case TextDecorationLineStyle_Dot: + return attr.GetUnderlineStyle() == UnderlineStyle::DottedUnderlined; + case TextDecorationLineStyle_Dash: + return attr.GetUnderlineStyle() == UnderlineStyle::DashedUnderlined; default: return std::nullopt; } @@ -697,18 +703,26 @@ bool UiaTextRangeBase::_initializeAttrQuery(TEXTATTRIBUTEID attributeId, VARIANT const auto style = attr.GetUnderlineStyle(); switch (style) { + case UnderlineStyle::NoUnderline: + pRetVal->lVal = TextDecorationLineStyle_None; + return true; case UnderlineStyle::SinglyUnderlined: pRetVal->lVal = TextDecorationLineStyle_Single; return true; case UnderlineStyle::DoublyUnderlined: pRetVal->lVal = TextDecorationLineStyle_Double; return true; - case UnderlineStyle::NoUnderline: - pRetVal->lVal = TextDecorationLineStyle_None; + case UnderlineStyle::CurlyUnderlined: + pRetVal->lVal = TextDecorationLineStyle_Wavy; + return true; + case UnderlineStyle::DottedUnderlined: + pRetVal->lVal = TextDecorationLineStyle_Dot; return true; + case UnderlineStyle::DashedUnderlined: + pRetVal->lVal = TextDecorationLineStyle_Dash; + return true; + // Out of range styles are treated as singly underlined. default: - // TODO: Handle other underline styles once they're supported in the graphic renderer. - // For now, extended styles are treated (and rendered) as single underline. pRetVal->lVal = TextDecorationLineStyle_Single; return true; } @@ -971,29 +985,15 @@ std::wstring UiaTextRangeBase::_getTextValue(til::CoordType maxLength) const auto inclusiveEnd = _end; bufferSize.DecrementInBounds(inclusiveEnd, true); - // reserve size in accordance to extracted text - const auto textRects = buffer.GetTextRects(_start, inclusiveEnd, _blockRange, true); - const auto bufferData = buffer.GetText(true, - false, - textRects); - const size_t textDataSize = bufferData.text.size() * bufferSize.Width(); - textData.reserve(textDataSize); - for (const auto& text : bufferData.text) - { - if (textData.size() >= maxLengthAsSize) - { - // early exit; we're already at/past max length - break; - } - textData += text; - } + const auto req = TextBuffer::CopyRequest{ buffer, _start, inclusiveEnd, _blockRange, true, false, false, true }; + auto plainText = buffer.GetPlainText(req); - // only use maxLength to resize down. - // if maxLength > size, we don't want to resize and append unnecessary L'\0'. - if (textData.size() > maxLengthAsSize) + if (plainText.size() > maxLengthAsSize) { - textData.resize(maxLengthAsSize); + plainText.resize(maxLengthAsSize); } + + textData = std::move(plainText); } return textData; diff --git a/src/types/inc/utils.hpp b/src/types/inc/utils.hpp index 381e07b243c..477229c51f9 100644 --- a/src/types/inc/utils.hpp +++ b/src/types/inc/utils.hpp @@ -39,8 +39,10 @@ namespace Microsoft::Console::Utils static_cast(SHRT_MAX))); } - std::wstring GuidToString(const GUID guid); + std::wstring GuidToString(const GUID& guid); + std::wstring GuidToPlainString(const GUID& guid); GUID GuidFromString(_Null_terminated_ const wchar_t* str); + GUID GuidFromPlainString(_Null_terminated_ const wchar_t* str); GUID CreateGuid(); std::string ColorToHexString(const til::color color); diff --git a/src/types/sources.inc b/src/types/sources.inc index 6527fbed578..44f252a2a17 100644 --- a/src/types/sources.inc +++ b/src/types/sources.inc @@ -31,7 +31,6 @@ SOURCES= \ ..\CodepointWidthDetector.cpp \ ..\ColorFix.cpp \ ..\GlyphWidth.cpp \ - ..\ModifierKeyState.cpp \ ..\Viewport.cpp \ ..\convert.cpp \ ..\colorTable.cpp \ diff --git a/src/types/ut_types/UtilsTests.cpp b/src/types/ut_types/UtilsTests.cpp index f83b1b4cad7..fb83f0e875d 100644 --- a/src/types/ut_types/UtilsTests.cpp +++ b/src/types/ut_types/UtilsTests.cpp @@ -66,15 +66,23 @@ void UtilsTests::TestClampToShortMax() void UtilsTests::TestGuidToString() { - constexpr GUID constantGuid{ + static constexpr GUID constantGuid{ 0x01020304, 0x0506, 0x0708, { 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 } }; - constexpr std::wstring_view constantGuidString{ L"{01020304-0506-0708-090a-0b0c0d0e0f10}" }; - auto generatedGuid{ GuidToString(constantGuid) }; + { + const auto str = GuidToString(constantGuid); + const auto guid = GuidFromString(str.c_str()); + VERIFY_ARE_EQUAL(L"{01020304-0506-0708-090a-0b0c0d0e0f10}", str); + VERIFY_ARE_EQUAL(constantGuid, guid); + } - VERIFY_ARE_EQUAL(constantGuidString.size(), generatedGuid.size()); - VERIFY_ARE_EQUAL(constantGuidString, generatedGuid); + { + const auto str = GuidToPlainString(constantGuid); + const auto guid = GuidFromPlainString(str.c_str()); + VERIFY_ARE_EQUAL(L"01020304-0506-0708-090a-0b0c0d0e0f10", str); + VERIFY_ARE_EQUAL(constantGuid, guid); + } } void UtilsTests::TestSplitString() diff --git a/src/types/utils.cpp b/src/types/utils.cpp index 91fd21e2633..9cccf7e6040 100644 --- a/src/types/utils.cpp +++ b/src/types/utils.cpp @@ -3,6 +3,9 @@ #include "precomp.h" #include "inc/utils.hpp" + +#include + #include "inc/colorTable.hpp" #include @@ -21,27 +24,28 @@ static constexpr bool _isNumber(const wchar_t wch) noexcept return wch >= L'0' && wch <= L'9'; // 0x30 - 0x39 } -// Function Description: -// - Creates a String representation of a guid, in the format -// "{12345678-ABCD-EF12-3456-7890ABCDEF12}" -// Arguments: -// - guid: the GUID to create the string for -// Return Value: -// - a string representation of the GUID. On failure, throws E_INVALIDARG. -std::wstring Utils::GuidToString(const GUID guid) +[[gsl::suppress(bounds)]] static std::wstring guidToStringCommon(const GUID& guid, size_t offset, size_t length) { - return wil::str_printf(L"{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); + // This is just like StringFromGUID2 but with lowercase hexadecimal. + wchar_t buffer[39]; + swprintf_s(&buffer[0], 39, L"{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); + return { &buffer[offset], length }; } -// Method Description: -// - Parses a GUID from a string representation of the GUID. Throws an exception -// if it fails to parse the GUID. See documentation of IIDFromString for -// details. -// Arguments: -// - wstr: a string representation of the GUID to parse -// Return Value: -// - A GUID if the string could successfully be parsed. On failure, throws the -// failing HRESULT. +// Creates a string from the given GUID in the format "{12345678-abcd-ef12-3456-7890abcdef12}". +std::wstring Utils::GuidToString(const GUID& guid) +{ + return guidToStringCommon(guid, 0, 38); +} + +// Creates a string from the given GUID in the format "12345678-abcd-ef12-3456-7890abcdef12". +std::wstring Utils::GuidToPlainString(const GUID& guid) +{ + return guidToStringCommon(guid, 1, 36); +} + +// Creates a GUID from a string in the format "{12345678-abcd-ef12-3456-7890abcdef12}". +// Throws if the conversion failed. GUID Utils::GuidFromString(_Null_terminated_ const wchar_t* str) { GUID result; @@ -49,6 +53,25 @@ GUID Utils::GuidFromString(_Null_terminated_ const wchar_t* str) return result; } +// Creates a GUID from a string in the format "12345678-abcd-ef12-3456-7890abcdef12". +// Throws if the conversion failed. +// +// Side-note: An interesting quirk of this method is that the given string doesn't need to be null-terminated. +// This method could be combined with GuidFromString() so that it also doesn't require null-termination. +[[gsl::suppress(bounds)]] GUID Utils::GuidFromPlainString(_Null_terminated_ const wchar_t* str) +{ + // Add "{}" brackets around our string, as required by IIDFromString(). + wchar_t buffer[39]; + buffer[0] = L'{'; + // This wcscpy_s() copies 36 characters and 1 terminating null. + // The latter forces us to call this method before filling buffer[37] with '}'. + THROW_HR_IF(CO_E_CLASSSTRING, wcscpy_s(&buffer[1], 37, str)); + buffer[37] = L'}'; + buffer[38] = L'\0'; + + return GuidFromString(&buffer[0]); +} + // Method Description: // - Creates a GUID, but not via an out parameter. // Return Value: diff --git a/tools/Get-OSSConhostLog.ps1 b/tools/Get-OSSConhostLog.ps1 index 4c206effa8e..46cd8aca1f2 100644 --- a/tools/Get-OSSConhostLog.ps1 +++ b/tools/Get-OSSConhostLog.ps1 @@ -43,7 +43,7 @@ Function Get-Git2GitIgnoresAsExcludes() { $Excludes = Get-Git2GitIgnoresAsExcludes Write-Verbose "IGNORING: $Excludes" -$Entries = & git log $RevisionRange "--pretty=format:%an%x1C%ae%x1C%s" -- $Excludes | +$Entries = & git log $RevisionRange --first-parent "--pretty=format:%an%x1C%ae%x1C%s" -- $Excludes | ConvertFrom-CSV -Delimiter "`u{001C}" -Header Author,Email,Subject Write-Verbose ("{0} unfiltered log entries" -f $Entries.Count) diff --git a/tools/ReleaseEngineering/New-TerminalStackedChangelog.ps1 b/tools/ReleaseEngineering/New-TerminalStackedChangelog.ps1 index 7f442f2ccf7..4d48b18bbf0 100644 --- a/tools/ReleaseEngineering/New-TerminalStackedChangelog.ps1 +++ b/tools/ReleaseEngineering/New-TerminalStackedChangelog.ps1 @@ -62,7 +62,7 @@ ForEach ($RevisionRange in $RevisionRanges) { # - %ae: author email # - %x1C: another FS # - %s: subject, the title of the commit - $NewEntries = & git log $RevisionRange "--pretty=format:%an%x1C%ae%x1C%s" | + $NewEntries = & git log $RevisionRange --first-parent "--pretty=format:%an%x1C%ae%x1C%s" | ConvertFrom-CSV -Delimiter "`u{001C}" -Header Author,Email,Subject $Entries += $NewEntries | % { [PSCustomObject]@{ diff --git a/tools/ReleaseEngineering/ServicingPipeline.ps1 b/tools/ReleaseEngineering/ServicingPipeline.ps1 index d7eb2cfebc2..2adf3843a0a 100644 --- a/tools/ReleaseEngineering/ServicingPipeline.ps1 +++ b/tools/ReleaseEngineering/ServicingPipeline.ps1 @@ -89,7 +89,7 @@ $Cards = Get-GithubProjectCard -ColumnId $ToPickColumn.id & git fetch --all 2>&1 | Out-Null -$Entries = @(& git log $SourceBranch --grep "(#\($($Cards.Number -Join "\|")\))" "--pretty=format:%H%x1C%s" | +$Entries = @(& git log $SourceBranch --first-parent --grep "(#\($($Cards.Number -Join "\|")\))" "--pretty=format:%H%x1C%s" | ConvertFrom-CSV -Delimiter "`u{001C}" -Header CommitID,Subject) [Array]::Reverse($Entries)