diff --git a/README.md b/README.md index 512a14846ef..4f994bd5f67 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,15 @@ Related repositories include: > 👉 Note: Windows Terminal requires Windows 10 1903 (build 18362) or later -### Manually installing builds from this repository +### Microsoft Store [Recommended] + +Install the [Windows Terminal from the Microsoft Store][store-install-link]. This allows you to always be on the latest version when we release new builds with automatic upgrades. + +This is our preferred method. + +### Other install methods + +#### Via GitHub For users who are unable to install Terminal from the Microsoft Store, Terminal builds can be manually downloaded from this repository's [Releases page](https://github.com/microsoft/terminal/releases). @@ -26,7 +34,7 @@ For users who are unable to install Terminal from the Microsoft Store, Terminal > * Be sure to install the [Desktop Bridge VC++ v14 Redistributable Package](https://www.microsoft.com/en-us/download/details.aspx?id=53175) otherwise Terminal may not install and/or run and may crash at startup > * Terminal will not auto-update when new builds are released so you will need to regularly install the latest Terminal release to receive all the latest fixes and improvements! -### Install via Chocolatey (unofficial) +#### Via Chocolatey (unofficial) [Chocolatey](https://chocolatey.org) users can download and install the latest Terminal release by installing the `microsoft-windows-terminal` package: @@ -131,7 +139,7 @@ All project documentation is located in the `./doc` folder. If you would like to We are excited to work alongside you, our amazing community, to build and enhance Windows Terminal\! -***BEFORE you start work on a feature/fix***, please read & follow our [Contributor's Guide](https://github.com/microsoft/terminal/blob/master/contributing.md) to help avoid any wasted or duplicate effort. +***BEFORE you start work on a feature/fix***, please read & follow our [Contributor's Guide](https://github.com/microsoft/terminal/blob/master/CONTRIBUTING.md) to help avoid any wasted or duplicate effort. ## Communicating with the Team @@ -219,3 +227,4 @@ For more information see the [Code of Conduct FAQ][conduct-FAQ] or contact [open [conduct-code]: https://opensource.microsoft.com/codeofconduct/ [conduct-FAQ]: https://opensource.microsoft.com/codeofconduct/faq/ [conduct-email]: mailto:opencode@microsoft.com +[store-install-link]: https://aka.ms/windowsterminal diff --git a/custom.props b/custom.props index c1e9ebda542..55b6bee4b84 100644 --- a/custom.props +++ b/custom.props @@ -5,7 +5,7 @@ true 2020 0 - 9 + 10 Windows Terminal diff --git a/res/terminal/images-Dev/LargeTile.scale-100.png b/res/terminal/images-Dev/LargeTile.scale-100.png index cdbcbc705a6..4e6e8a25d05 100644 Binary files a/res/terminal/images-Dev/LargeTile.scale-100.png and b/res/terminal/images-Dev/LargeTile.scale-100.png differ diff --git a/res/terminal/images-Dev/LargeTile.scale-125.png b/res/terminal/images-Dev/LargeTile.scale-125.png index 00c77f7b337..96e62159f4a 100644 Binary files a/res/terminal/images-Dev/LargeTile.scale-125.png and b/res/terminal/images-Dev/LargeTile.scale-125.png differ diff --git a/res/terminal/images-Dev/LargeTile.scale-150.png b/res/terminal/images-Dev/LargeTile.scale-150.png index cf3f071853e..e37e7cbdfcf 100644 Binary files a/res/terminal/images-Dev/LargeTile.scale-150.png and b/res/terminal/images-Dev/LargeTile.scale-150.png differ diff --git a/res/terminal/images-Dev/LargeTile.scale-200.png b/res/terminal/images-Dev/LargeTile.scale-200.png index 1e01e2e349a..1da99f0dab2 100644 Binary files a/res/terminal/images-Dev/LargeTile.scale-200.png and b/res/terminal/images-Dev/LargeTile.scale-200.png differ diff --git a/res/terminal/images-Dev/LargeTile.scale-400.png b/res/terminal/images-Dev/LargeTile.scale-400.png index 684123cb251..7745809c91d 100644 Binary files a/res/terminal/images-Dev/LargeTile.scale-400.png and b/res/terminal/images-Dev/LargeTile.scale-400.png differ diff --git a/res/terminal/images-Dev/LockScreenLogo.scale-100.png b/res/terminal/images-Dev/LockScreenLogo.scale-100.png index 36f4fb56fb4..1b9c0cb0b8a 100644 Binary files a/res/terminal/images-Dev/LockScreenLogo.scale-100.png and b/res/terminal/images-Dev/LockScreenLogo.scale-100.png differ diff --git a/res/terminal/images-Dev/LockScreenLogo.scale-125.png b/res/terminal/images-Dev/LockScreenLogo.scale-125.png index 35164b236ab..fffa61a58cc 100644 Binary files a/res/terminal/images-Dev/LockScreenLogo.scale-125.png and b/res/terminal/images-Dev/LockScreenLogo.scale-125.png differ diff --git a/res/terminal/images-Dev/LockScreenLogo.scale-150.png b/res/terminal/images-Dev/LockScreenLogo.scale-150.png index fb176d1c2bc..7ff9fee6342 100644 Binary files a/res/terminal/images-Dev/LockScreenLogo.scale-150.png and b/res/terminal/images-Dev/LockScreenLogo.scale-150.png differ diff --git a/res/terminal/images-Dev/LockScreenLogo.scale-200.png b/res/terminal/images-Dev/LockScreenLogo.scale-200.png index cb31d4302b0..6da33a0b104 100644 Binary files a/res/terminal/images-Dev/LockScreenLogo.scale-200.png and b/res/terminal/images-Dev/LockScreenLogo.scale-200.png differ diff --git a/res/terminal/images-Dev/LockScreenLogo.scale-400.png b/res/terminal/images-Dev/LockScreenLogo.scale-400.png index 61463a6636e..c946c4178eb 100644 Binary files a/res/terminal/images-Dev/LockScreenLogo.scale-400.png and b/res/terminal/images-Dev/LockScreenLogo.scale-400.png differ diff --git a/res/terminal/images-Dev/SmallTile.scale-100.png b/res/terminal/images-Dev/SmallTile.scale-100.png index 463c44f611d..a1ac45dd0c5 100644 Binary files a/res/terminal/images-Dev/SmallTile.scale-100.png and b/res/terminal/images-Dev/SmallTile.scale-100.png differ diff --git a/res/terminal/images-Dev/SmallTile.scale-125.png b/res/terminal/images-Dev/SmallTile.scale-125.png index d81c4579c1a..4f5f8ce48ef 100644 Binary files a/res/terminal/images-Dev/SmallTile.scale-125.png and b/res/terminal/images-Dev/SmallTile.scale-125.png differ diff --git a/res/terminal/images-Dev/SmallTile.scale-150.png b/res/terminal/images-Dev/SmallTile.scale-150.png index 9a83a8d6dba..3496403219a 100644 Binary files a/res/terminal/images-Dev/SmallTile.scale-150.png and b/res/terminal/images-Dev/SmallTile.scale-150.png differ diff --git a/res/terminal/images-Dev/SmallTile.scale-200.png b/res/terminal/images-Dev/SmallTile.scale-200.png index 436a551b556..1e3e3051853 100644 Binary files a/res/terminal/images-Dev/SmallTile.scale-200.png and b/res/terminal/images-Dev/SmallTile.scale-200.png differ diff --git a/res/terminal/images-Dev/SmallTile.scale-400.png b/res/terminal/images-Dev/SmallTile.scale-400.png index 8f41f2b44ae..9902fe1cb09 100644 Binary files a/res/terminal/images-Dev/SmallTile.scale-400.png and b/res/terminal/images-Dev/SmallTile.scale-400.png differ diff --git a/res/terminal/images-Dev/SplashScreen.scale-100.png b/res/terminal/images-Dev/SplashScreen.scale-100.png index 23ca268d6df..0367bd61bda 100644 Binary files a/res/terminal/images-Dev/SplashScreen.scale-100.png and b/res/terminal/images-Dev/SplashScreen.scale-100.png differ diff --git a/res/terminal/images-Dev/SplashScreen.scale-125.png b/res/terminal/images-Dev/SplashScreen.scale-125.png index b9ef4209254..9f83e0bf13f 100644 Binary files a/res/terminal/images-Dev/SplashScreen.scale-125.png and b/res/terminal/images-Dev/SplashScreen.scale-125.png differ diff --git a/res/terminal/images-Dev/SplashScreen.scale-150.png b/res/terminal/images-Dev/SplashScreen.scale-150.png index 327a8abe314..b33f97c61c4 100644 Binary files a/res/terminal/images-Dev/SplashScreen.scale-150.png and b/res/terminal/images-Dev/SplashScreen.scale-150.png differ diff --git a/res/terminal/images-Dev/SplashScreen.scale-200.png b/res/terminal/images-Dev/SplashScreen.scale-200.png index 8f19b92913d..688cf4997d2 100644 Binary files a/res/terminal/images-Dev/SplashScreen.scale-200.png and b/res/terminal/images-Dev/SplashScreen.scale-200.png differ diff --git a/res/terminal/images-Dev/SplashScreen.scale-400.png b/res/terminal/images-Dev/SplashScreen.scale-400.png index 633baa5e1ce..a7d18e657d9 100644 Binary files a/res/terminal/images-Dev/SplashScreen.scale-400.png and b/res/terminal/images-Dev/SplashScreen.scale-400.png differ diff --git a/res/terminal/images-Dev/Square150x150Logo.scale-100.png b/res/terminal/images-Dev/Square150x150Logo.scale-100.png index 8558b35417b..a46950bf8ca 100644 Binary files a/res/terminal/images-Dev/Square150x150Logo.scale-100.png and b/res/terminal/images-Dev/Square150x150Logo.scale-100.png differ diff --git a/res/terminal/images-Dev/Square150x150Logo.scale-125.png b/res/terminal/images-Dev/Square150x150Logo.scale-125.png index 1b4d92b3005..172a4110414 100644 Binary files a/res/terminal/images-Dev/Square150x150Logo.scale-125.png and b/res/terminal/images-Dev/Square150x150Logo.scale-125.png differ diff --git a/res/terminal/images-Dev/Square150x150Logo.scale-150.png b/res/terminal/images-Dev/Square150x150Logo.scale-150.png index cc70c170638..276db24f40f 100644 Binary files a/res/terminal/images-Dev/Square150x150Logo.scale-150.png and b/res/terminal/images-Dev/Square150x150Logo.scale-150.png differ diff --git a/res/terminal/images-Dev/Square150x150Logo.scale-200.png b/res/terminal/images-Dev/Square150x150Logo.scale-200.png index 4d2cf4a17c5..82112ad0a87 100644 Binary files a/res/terminal/images-Dev/Square150x150Logo.scale-200.png and b/res/terminal/images-Dev/Square150x150Logo.scale-200.png differ diff --git a/res/terminal/images-Dev/Square150x150Logo.scale-400.png b/res/terminal/images-Dev/Square150x150Logo.scale-400.png index f7d426af6e1..0eb1ba9dba0 100644 Binary files a/res/terminal/images-Dev/Square150x150Logo.scale-400.png and b/res/terminal/images-Dev/Square150x150Logo.scale-400.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.scale-100.png b/res/terminal/images-Dev/Square44x44Logo.scale-100.png index 8af60bbd53c..39433f821d5 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.scale-100.png and b/res/terminal/images-Dev/Square44x44Logo.scale-100.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.scale-125.png b/res/terminal/images-Dev/Square44x44Logo.scale-125.png index aca4adf7cf9..6c39d362289 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.scale-125.png and b/res/terminal/images-Dev/Square44x44Logo.scale-125.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.scale-150.png b/res/terminal/images-Dev/Square44x44Logo.scale-150.png index fd3b8f8c541..670e5799090 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.scale-150.png and b/res/terminal/images-Dev/Square44x44Logo.scale-150.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.scale-200.png b/res/terminal/images-Dev/Square44x44Logo.scale-200.png index 7b64b5349c2..5fadd3fcaab 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.scale-200.png and b/res/terminal/images-Dev/Square44x44Logo.scale-200.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.scale-400.png b/res/terminal/images-Dev/Square44x44Logo.scale-400.png index 8d94e1f61e3..1ec34428864 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.scale-400.png and b/res/terminal/images-Dev/Square44x44Logo.scale-400.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-16.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-16.png index b501a7282ed..2cf1b295255 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-16.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-16.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-16_altform-unplated.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-16_altform-unplated.png index b501a7282ed..2cf1b295255 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-16_altform-unplated.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-16_altform-unplated.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-20.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-20.png index 7dc3ed6a449..288f3dcc3fc 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-20.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-20.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-20_altform-unplated.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-20_altform-unplated.png index 7dc3ed6a449..288f3dcc3fc 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-20_altform-unplated.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-20_altform-unplated.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-24.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-24.png index 36f4fb56fb4..1b9c0cb0b8a 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-24.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-24.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-24_altform-unplated.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-24_altform-unplated.png index 36f4fb56fb4..1b9c0cb0b8a 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-24_altform-unplated.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-24_altform-unplated.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-256.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-256.png index 7b728384b2c..af2414b4b81 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-256.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-256.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-256_altform-unplated.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-256_altform-unplated.png index 7b728384b2c..af2414b4b81 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-256_altform-unplated.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-256_altform-unplated.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-30.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-30.png index 35164b236ab..fffa61a58cc 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-30.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-30.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-30_altform-unplated.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-30_altform-unplated.png index 35164b236ab..fffa61a58cc 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-30_altform-unplated.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-30_altform-unplated.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-32.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-32.png index 5b3c3b30756..188e2fac5fa 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-32.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-32.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-32_altform-unplated.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-32_altform-unplated.png index 5b3c3b30756..188e2fac5fa 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-32_altform-unplated.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-32_altform-unplated.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-36.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-36.png index fb176d1c2bc..7ff9fee6342 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-36.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-36.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-36_altform-unplated.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-36_altform-unplated.png index fb176d1c2bc..7ff9fee6342 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-36_altform-unplated.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-36_altform-unplated.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-40.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-40.png index f89148b6353..491db14a82e 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-40.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-40.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-40_altform-unplated.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-40_altform-unplated.png index f89148b6353..491db14a82e 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-40_altform-unplated.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-40_altform-unplated.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-48.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-48.png index cb31d4302b0..6da33a0b104 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-48.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-48.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-48_altform-unplated.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-48_altform-unplated.png index cb31d4302b0..6da33a0b104 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-48_altform-unplated.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-48_altform-unplated.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-60.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-60.png index d79c03363a6..d65ab32d355 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-60.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-60.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-60_altform-unplated.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-60_altform-unplated.png index d79c03363a6..d65ab32d355 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-60_altform-unplated.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-60_altform-unplated.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-64.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-64.png index 9fad6857cf8..b12c3852d57 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-64.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-64.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-64_altform-unplated.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-64_altform-unplated.png index 9fad6857cf8..b12c3852d57 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-64_altform-unplated.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-64_altform-unplated.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-72.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-72.png index 1aa4b6cb1fb..c088faa649a 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-72.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-72.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-72_altform-unplated.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-72_altform-unplated.png index 1aa4b6cb1fb..c088faa649a 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-72_altform-unplated.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-72_altform-unplated.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-80.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-80.png index d4eedeca2dc..954b10a9b6f 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-80.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-80.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-80_altform-unplated.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-80_altform-unplated.png index d4eedeca2dc..954b10a9b6f 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-80_altform-unplated.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-80_altform-unplated.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-96.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-96.png index 61463a6636e..c946c4178eb 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-96.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-96.png differ diff --git a/res/terminal/images-Dev/Square44x44Logo.targetsize-96_altform-unplated.png b/res/terminal/images-Dev/Square44x44Logo.targetsize-96_altform-unplated.png index 61463a6636e..c946c4178eb 100644 Binary files a/res/terminal/images-Dev/Square44x44Logo.targetsize-96_altform-unplated.png and b/res/terminal/images-Dev/Square44x44Logo.targetsize-96_altform-unplated.png differ diff --git a/res/terminal/images-Dev/StoreLogo.scale-100.png b/res/terminal/images-Dev/StoreLogo.scale-100.png index a4278328def..1aad67feef0 100644 Binary files a/res/terminal/images-Dev/StoreLogo.scale-100.png and b/res/terminal/images-Dev/StoreLogo.scale-100.png differ diff --git a/res/terminal/images-Dev/StoreLogo.scale-125.png b/res/terminal/images-Dev/StoreLogo.scale-125.png index 8634415ec65..3777ac2769c 100644 Binary files a/res/terminal/images-Dev/StoreLogo.scale-125.png and b/res/terminal/images-Dev/StoreLogo.scale-125.png differ diff --git a/res/terminal/images-Dev/StoreLogo.scale-150.png b/res/terminal/images-Dev/StoreLogo.scale-150.png index da579e40543..e1afde34529 100644 Binary files a/res/terminal/images-Dev/StoreLogo.scale-150.png and b/res/terminal/images-Dev/StoreLogo.scale-150.png differ diff --git a/res/terminal/images-Dev/StoreLogo.scale-200.png b/res/terminal/images-Dev/StoreLogo.scale-200.png index 4d141ef9697..c4d700c8d1f 100644 Binary files a/res/terminal/images-Dev/StoreLogo.scale-200.png and b/res/terminal/images-Dev/StoreLogo.scale-200.png differ diff --git a/res/terminal/images-Dev/StoreLogo.scale-400.png b/res/terminal/images-Dev/StoreLogo.scale-400.png index bde22ffebc0..796260678f2 100644 Binary files a/res/terminal/images-Dev/StoreLogo.scale-400.png and b/res/terminal/images-Dev/StoreLogo.scale-400.png differ diff --git a/res/terminal/images-Dev/Wide310x150Logo.scale-100.png b/res/terminal/images-Dev/Wide310x150Logo.scale-100.png index 435a50803a6..1f747141cbb 100644 Binary files a/res/terminal/images-Dev/Wide310x150Logo.scale-100.png and b/res/terminal/images-Dev/Wide310x150Logo.scale-100.png differ diff --git a/res/terminal/images-Dev/Wide310x150Logo.scale-125.png b/res/terminal/images-Dev/Wide310x150Logo.scale-125.png index 80df5b9d87f..2d75d7d759c 100644 Binary files a/res/terminal/images-Dev/Wide310x150Logo.scale-125.png and b/res/terminal/images-Dev/Wide310x150Logo.scale-125.png differ diff --git a/res/terminal/images-Dev/Wide310x150Logo.scale-150.png b/res/terminal/images-Dev/Wide310x150Logo.scale-150.png index 765c3385eeb..9db6d710f1a 100644 Binary files a/res/terminal/images-Dev/Wide310x150Logo.scale-150.png and b/res/terminal/images-Dev/Wide310x150Logo.scale-150.png differ diff --git a/res/terminal/images-Dev/Wide310x150Logo.scale-200.png b/res/terminal/images-Dev/Wide310x150Logo.scale-200.png index 23ca268d6df..0367bd61bda 100644 Binary files a/res/terminal/images-Dev/Wide310x150Logo.scale-200.png and b/res/terminal/images-Dev/Wide310x150Logo.scale-200.png differ diff --git a/res/terminal/images-Dev/Wide310x150Logo.scale-400.png b/res/terminal/images-Dev/Wide310x150Logo.scale-400.png index 8f19b92913d..688cf4997d2 100644 Binary files a/res/terminal/images-Dev/Wide310x150Logo.scale-400.png and b/res/terminal/images-Dev/Wide310x150Logo.scale-400.png differ diff --git a/res/terminal/images-Universal/LargeTile.scale-100.png b/res/terminal/images-Universal/LargeTile.scale-100.png index 4a83a59a663..20d9f18169f 100644 Binary files a/res/terminal/images-Universal/LargeTile.scale-100.png and b/res/terminal/images-Universal/LargeTile.scale-100.png differ diff --git a/res/terminal/images-Universal/LargeTile.scale-125.png b/res/terminal/images-Universal/LargeTile.scale-125.png index 2df601ffd04..fd77a3192ec 100644 Binary files a/res/terminal/images-Universal/LargeTile.scale-125.png and b/res/terminal/images-Universal/LargeTile.scale-125.png differ diff --git a/res/terminal/images-Universal/LargeTile.scale-150.png b/res/terminal/images-Universal/LargeTile.scale-150.png index aee6e6a9968..57d81682890 100644 Binary files a/res/terminal/images-Universal/LargeTile.scale-150.png and b/res/terminal/images-Universal/LargeTile.scale-150.png differ diff --git a/res/terminal/images-Universal/LargeTile.scale-200.png b/res/terminal/images-Universal/LargeTile.scale-200.png index 1b8343b5f98..ed031de1ea2 100644 Binary files a/res/terminal/images-Universal/LargeTile.scale-200.png and b/res/terminal/images-Universal/LargeTile.scale-200.png differ diff --git a/res/terminal/images-Universal/LargeTile.scale-400.png b/res/terminal/images-Universal/LargeTile.scale-400.png index 78d582650f7..04e8afad1bd 100644 Binary files a/res/terminal/images-Universal/LargeTile.scale-400.png and b/res/terminal/images-Universal/LargeTile.scale-400.png differ diff --git a/res/terminal/images-Universal/LockScreenLogo.scale-100.png b/res/terminal/images-Universal/LockScreenLogo.scale-100.png index 6cfcd243266..257eec38d8b 100644 Binary files a/res/terminal/images-Universal/LockScreenLogo.scale-100.png and b/res/terminal/images-Universal/LockScreenLogo.scale-100.png differ diff --git a/res/terminal/images-Universal/LockScreenLogo.scale-125.png b/res/terminal/images-Universal/LockScreenLogo.scale-125.png index e69ea97723e..151e8f8915d 100644 Binary files a/res/terminal/images-Universal/LockScreenLogo.scale-125.png and b/res/terminal/images-Universal/LockScreenLogo.scale-125.png differ diff --git a/res/terminal/images-Universal/LockScreenLogo.scale-150.png b/res/terminal/images-Universal/LockScreenLogo.scale-150.png index 049f5bb8f84..5849dba6f02 100644 Binary files a/res/terminal/images-Universal/LockScreenLogo.scale-150.png and b/res/terminal/images-Universal/LockScreenLogo.scale-150.png differ diff --git a/res/terminal/images-Universal/LockScreenLogo.scale-200.png b/res/terminal/images-Universal/LockScreenLogo.scale-200.png index 903daa7ad00..2cf732e9064 100644 Binary files a/res/terminal/images-Universal/LockScreenLogo.scale-200.png and b/res/terminal/images-Universal/LockScreenLogo.scale-200.png differ diff --git a/res/terminal/images-Universal/LockScreenLogo.scale-400.png b/res/terminal/images-Universal/LockScreenLogo.scale-400.png index d5f2fe178e4..3ad06c9d2f4 100644 Binary files a/res/terminal/images-Universal/LockScreenLogo.scale-400.png and b/res/terminal/images-Universal/LockScreenLogo.scale-400.png differ diff --git a/res/terminal/images-Universal/SmallTile.scale-100.png b/res/terminal/images-Universal/SmallTile.scale-100.png index 4a816a187a9..cde85ebbac9 100644 Binary files a/res/terminal/images-Universal/SmallTile.scale-100.png and b/res/terminal/images-Universal/SmallTile.scale-100.png differ diff --git a/res/terminal/images-Universal/SmallTile.scale-125.png b/res/terminal/images-Universal/SmallTile.scale-125.png index eddac38ce9c..6394934ec3e 100644 Binary files a/res/terminal/images-Universal/SmallTile.scale-125.png and b/res/terminal/images-Universal/SmallTile.scale-125.png differ diff --git a/res/terminal/images-Universal/SmallTile.scale-150.png b/res/terminal/images-Universal/SmallTile.scale-150.png index 3386841195a..fd231248ff4 100644 Binary files a/res/terminal/images-Universal/SmallTile.scale-150.png and b/res/terminal/images-Universal/SmallTile.scale-150.png differ diff --git a/res/terminal/images-Universal/SmallTile.scale-200.png b/res/terminal/images-Universal/SmallTile.scale-200.png index caa52df8ff2..ff24b499048 100644 Binary files a/res/terminal/images-Universal/SmallTile.scale-200.png and b/res/terminal/images-Universal/SmallTile.scale-200.png differ diff --git a/res/terminal/images-Universal/SmallTile.scale-400.png b/res/terminal/images-Universal/SmallTile.scale-400.png index ad836ae4fe9..1f8c99b2c4c 100644 Binary files a/res/terminal/images-Universal/SmallTile.scale-400.png and b/res/terminal/images-Universal/SmallTile.scale-400.png differ diff --git a/res/terminal/images-Universal/SplashScreen.scale-100.png b/res/terminal/images-Universal/SplashScreen.scale-100.png index 95ab759e0d3..4cb4d9f457e 100644 Binary files a/res/terminal/images-Universal/SplashScreen.scale-100.png and b/res/terminal/images-Universal/SplashScreen.scale-100.png differ diff --git a/res/terminal/images-Universal/SplashScreen.scale-125.png b/res/terminal/images-Universal/SplashScreen.scale-125.png index 2cfe557b56a..226a590769e 100644 Binary files a/res/terminal/images-Universal/SplashScreen.scale-125.png and b/res/terminal/images-Universal/SplashScreen.scale-125.png differ diff --git a/res/terminal/images-Universal/SplashScreen.scale-150.png b/res/terminal/images-Universal/SplashScreen.scale-150.png index c94b7fe2511..225b1736625 100644 Binary files a/res/terminal/images-Universal/SplashScreen.scale-150.png and b/res/terminal/images-Universal/SplashScreen.scale-150.png differ diff --git a/res/terminal/images-Universal/SplashScreen.scale-200.png b/res/terminal/images-Universal/SplashScreen.scale-200.png index e7eac1555ae..71fffea6564 100644 Binary files a/res/terminal/images-Universal/SplashScreen.scale-200.png and b/res/terminal/images-Universal/SplashScreen.scale-200.png differ diff --git a/res/terminal/images-Universal/SplashScreen.scale-400.png b/res/terminal/images-Universal/SplashScreen.scale-400.png index d8629c26e9f..36e19921a3b 100644 Binary files a/res/terminal/images-Universal/SplashScreen.scale-400.png and b/res/terminal/images-Universal/SplashScreen.scale-400.png differ diff --git a/res/terminal/images-Universal/Square150x150Logo.scale-100.png b/res/terminal/images-Universal/Square150x150Logo.scale-100.png index 23da4910c5a..7807fe391e4 100644 Binary files a/res/terminal/images-Universal/Square150x150Logo.scale-100.png and b/res/terminal/images-Universal/Square150x150Logo.scale-100.png differ diff --git a/res/terminal/images-Universal/Square150x150Logo.scale-125.png b/res/terminal/images-Universal/Square150x150Logo.scale-125.png index 7490622f4e2..772069b0a8e 100644 Binary files a/res/terminal/images-Universal/Square150x150Logo.scale-125.png and b/res/terminal/images-Universal/Square150x150Logo.scale-125.png differ diff --git a/res/terminal/images-Universal/Square150x150Logo.scale-150.png b/res/terminal/images-Universal/Square150x150Logo.scale-150.png index 245964f37ca..e10142d18ef 100644 Binary files a/res/terminal/images-Universal/Square150x150Logo.scale-150.png and b/res/terminal/images-Universal/Square150x150Logo.scale-150.png differ diff --git a/res/terminal/images-Universal/Square150x150Logo.scale-200.png b/res/terminal/images-Universal/Square150x150Logo.scale-200.png index 942bc9ac408..3675e159767 100644 Binary files a/res/terminal/images-Universal/Square150x150Logo.scale-200.png and b/res/terminal/images-Universal/Square150x150Logo.scale-200.png differ diff --git a/res/terminal/images-Universal/Square150x150Logo.scale-400.png b/res/terminal/images-Universal/Square150x150Logo.scale-400.png index 4df70b8eccd..ab2fb81fa42 100644 Binary files a/res/terminal/images-Universal/Square150x150Logo.scale-400.png and b/res/terminal/images-Universal/Square150x150Logo.scale-400.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.scale-100.png b/res/terminal/images-Universal/Square44x44Logo.scale-100.png index eee18e1b10d..5add3816c1a 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.scale-100.png and b/res/terminal/images-Universal/Square44x44Logo.scale-100.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.scale-125.png b/res/terminal/images-Universal/Square44x44Logo.scale-125.png index d72f72fadba..09eaa01cb07 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.scale-125.png and b/res/terminal/images-Universal/Square44x44Logo.scale-125.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.scale-150.png b/res/terminal/images-Universal/Square44x44Logo.scale-150.png index dd87669492c..6fce68f3c26 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.scale-150.png and b/res/terminal/images-Universal/Square44x44Logo.scale-150.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.scale-200.png b/res/terminal/images-Universal/Square44x44Logo.scale-200.png index f197ab5fc6a..8fbe3f6aaf5 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.scale-200.png and b/res/terminal/images-Universal/Square44x44Logo.scale-200.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.scale-400.png b/res/terminal/images-Universal/Square44x44Logo.scale-400.png index dd14eedbcc2..630456e0b76 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.scale-400.png and b/res/terminal/images-Universal/Square44x44Logo.scale-400.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-16.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-16.png index fe4f7de90d5..5e2b3a609d7 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-16.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-16.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-16_altform-unplated.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-16_altform-unplated.png index fe4f7de90d5..5e2b3a609d7 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-16_altform-unplated.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-16_altform-unplated.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-20.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-20.png index 29ce742f575..8af06b0aab6 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-20.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-20.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-20_altform-unplated.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-20_altform-unplated.png index 29ce742f575..8af06b0aab6 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-20_altform-unplated.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-20_altform-unplated.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-24.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-24.png index 6cfcd243266..257eec38d8b 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-24.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-24.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-24_altform-unplated.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-24_altform-unplated.png index 6cfcd243266..257eec38d8b 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-24_altform-unplated.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-24_altform-unplated.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-256.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-256.png index 4184b1f0b3c..59f4296e235 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-256.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-256.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-256_altform-unplated.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-256_altform-unplated.png index 4184b1f0b3c..59f4296e235 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-256_altform-unplated.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-256_altform-unplated.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-30.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-30.png index e69ea97723e..151e8f8915d 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-30.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-30.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-30_altform-unplated.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-30_altform-unplated.png index e69ea97723e..151e8f8915d 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-30_altform-unplated.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-30_altform-unplated.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-32.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-32.png index e76f1a0b5f0..059461903f2 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-32.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-32.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-32_altform-unplated.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-32_altform-unplated.png index e76f1a0b5f0..059461903f2 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-32_altform-unplated.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-32_altform-unplated.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-36.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-36.png index 049f5bb8f84..5849dba6f02 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-36.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-36.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-36_altform-unplated.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-36_altform-unplated.png index 049f5bb8f84..5849dba6f02 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-36_altform-unplated.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-36_altform-unplated.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-40.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-40.png index 7c87c55038f..b366af89a69 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-40.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-40.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-40_altform-unplated.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-40_altform-unplated.png index 7c87c55038f..b366af89a69 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-40_altform-unplated.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-40_altform-unplated.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-48.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-48.png index 903daa7ad00..2cf732e9064 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-48.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-48.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-48_altform-unplated.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-48_altform-unplated.png index 903daa7ad00..2cf732e9064 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-48_altform-unplated.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-48_altform-unplated.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-60.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-60.png index b942eeaa8b1..a29fbc78ccd 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-60.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-60.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-60_altform-unplated.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-60_altform-unplated.png index b942eeaa8b1..a29fbc78ccd 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-60_altform-unplated.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-60_altform-unplated.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-64.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-64.png index 08066e0ff14..5cde15961f1 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-64.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-64.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-64_altform-unplated.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-64_altform-unplated.png index 08066e0ff14..5cde15961f1 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-64_altform-unplated.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-64_altform-unplated.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-72.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-72.png index 9372c45dac9..30a2cfa3902 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-72.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-72.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-72_altform-unplated.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-72_altform-unplated.png index 9372c45dac9..30a2cfa3902 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-72_altform-unplated.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-72_altform-unplated.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-80.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-80.png index 8036b319e66..4f0016125d1 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-80.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-80.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-80_altform-unplated.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-80_altform-unplated.png index 8036b319e66..4f0016125d1 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-80_altform-unplated.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-80_altform-unplated.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-96.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-96.png index d5f2fe178e4..3ad06c9d2f4 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-96.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-96.png differ diff --git a/res/terminal/images-Universal/Square44x44Logo.targetsize-96_altform-unplated.png b/res/terminal/images-Universal/Square44x44Logo.targetsize-96_altform-unplated.png index d5f2fe178e4..3ad06c9d2f4 100644 Binary files a/res/terminal/images-Universal/Square44x44Logo.targetsize-96_altform-unplated.png and b/res/terminal/images-Universal/Square44x44Logo.targetsize-96_altform-unplated.png differ diff --git a/res/terminal/images-Universal/StoreLogo.scale-100.png b/res/terminal/images-Universal/StoreLogo.scale-100.png index bc93b430d39..fa575eb48c7 100644 Binary files a/res/terminal/images-Universal/StoreLogo.scale-100.png and b/res/terminal/images-Universal/StoreLogo.scale-100.png differ diff --git a/res/terminal/images-Universal/StoreLogo.scale-125.png b/res/terminal/images-Universal/StoreLogo.scale-125.png index adf9c3947cd..4ab038b6aab 100644 Binary files a/res/terminal/images-Universal/StoreLogo.scale-125.png and b/res/terminal/images-Universal/StoreLogo.scale-125.png differ diff --git a/res/terminal/images-Universal/StoreLogo.scale-150.png b/res/terminal/images-Universal/StoreLogo.scale-150.png index abbf53e6ac8..5ea72b4b8fd 100644 Binary files a/res/terminal/images-Universal/StoreLogo.scale-150.png and b/res/terminal/images-Universal/StoreLogo.scale-150.png differ diff --git a/res/terminal/images-Universal/StoreLogo.scale-200.png b/res/terminal/images-Universal/StoreLogo.scale-200.png index de0081e8766..f9dce98de97 100644 Binary files a/res/terminal/images-Universal/StoreLogo.scale-200.png and b/res/terminal/images-Universal/StoreLogo.scale-200.png differ diff --git a/res/terminal/images-Universal/StoreLogo.scale-400.png b/res/terminal/images-Universal/StoreLogo.scale-400.png index a15234ce403..a51e51d618d 100644 Binary files a/res/terminal/images-Universal/StoreLogo.scale-400.png and b/res/terminal/images-Universal/StoreLogo.scale-400.png differ diff --git a/res/terminal/images-Universal/Wide310x150Logo.scale-100.png b/res/terminal/images-Universal/Wide310x150Logo.scale-100.png index c40de6eff71..172b7523bf7 100644 Binary files a/res/terminal/images-Universal/Wide310x150Logo.scale-100.png and b/res/terminal/images-Universal/Wide310x150Logo.scale-100.png differ diff --git a/res/terminal/images-Universal/Wide310x150Logo.scale-125.png b/res/terminal/images-Universal/Wide310x150Logo.scale-125.png index f88c5fa00a4..b17bd824365 100644 Binary files a/res/terminal/images-Universal/Wide310x150Logo.scale-125.png and b/res/terminal/images-Universal/Wide310x150Logo.scale-125.png differ diff --git a/res/terminal/images-Universal/Wide310x150Logo.scale-150.png b/res/terminal/images-Universal/Wide310x150Logo.scale-150.png index 30177aa006b..60798baec5f 100644 Binary files a/res/terminal/images-Universal/Wide310x150Logo.scale-150.png and b/res/terminal/images-Universal/Wide310x150Logo.scale-150.png differ diff --git a/res/terminal/images-Universal/Wide310x150Logo.scale-200.png b/res/terminal/images-Universal/Wide310x150Logo.scale-200.png index 95ab759e0d3..4cb4d9f457e 100644 Binary files a/res/terminal/images-Universal/Wide310x150Logo.scale-200.png and b/res/terminal/images-Universal/Wide310x150Logo.scale-200.png differ diff --git a/res/terminal/images-Universal/Wide310x150Logo.scale-400.png b/res/terminal/images-Universal/Wide310x150Logo.scale-400.png index e7eac1555ae..71fffea6564 100644 Binary files a/res/terminal/images-Universal/Wide310x150Logo.scale-400.png and b/res/terminal/images-Universal/Wide310x150Logo.scale-400.png differ diff --git a/res/terminal/images-UniversalDev/LargeTile.scale-100.png b/res/terminal/images-UniversalDev/LargeTile.scale-100.png index 759826e6d30..c4b6e2f0eb3 100644 Binary files a/res/terminal/images-UniversalDev/LargeTile.scale-100.png and b/res/terminal/images-UniversalDev/LargeTile.scale-100.png differ diff --git a/res/terminal/images-UniversalDev/LargeTile.scale-125.png b/res/terminal/images-UniversalDev/LargeTile.scale-125.png index 73fd5b2743f..e319702d763 100644 Binary files a/res/terminal/images-UniversalDev/LargeTile.scale-125.png and b/res/terminal/images-UniversalDev/LargeTile.scale-125.png differ diff --git a/res/terminal/images-UniversalDev/LargeTile.scale-150.png b/res/terminal/images-UniversalDev/LargeTile.scale-150.png index 9e80d5ef440..ca28d81bddb 100644 Binary files a/res/terminal/images-UniversalDev/LargeTile.scale-150.png and b/res/terminal/images-UniversalDev/LargeTile.scale-150.png differ diff --git a/res/terminal/images-UniversalDev/LargeTile.scale-200.png b/res/terminal/images-UniversalDev/LargeTile.scale-200.png index 1bfe9db04aa..db918a66451 100644 Binary files a/res/terminal/images-UniversalDev/LargeTile.scale-200.png and b/res/terminal/images-UniversalDev/LargeTile.scale-200.png differ diff --git a/res/terminal/images-UniversalDev/LargeTile.scale-400.png b/res/terminal/images-UniversalDev/LargeTile.scale-400.png index fb0a0046f1f..24ff15394e1 100644 Binary files a/res/terminal/images-UniversalDev/LargeTile.scale-400.png and b/res/terminal/images-UniversalDev/LargeTile.scale-400.png differ diff --git a/res/terminal/images-UniversalDev/LockScreenLogo.scale-100.png b/res/terminal/images-UniversalDev/LockScreenLogo.scale-100.png index 8d2d86a7034..a186bed5272 100644 Binary files a/res/terminal/images-UniversalDev/LockScreenLogo.scale-100.png and b/res/terminal/images-UniversalDev/LockScreenLogo.scale-100.png differ diff --git a/res/terminal/images-UniversalDev/LockScreenLogo.scale-125.png b/res/terminal/images-UniversalDev/LockScreenLogo.scale-125.png index 255e4f7a6b8..015e2edee75 100644 Binary files a/res/terminal/images-UniversalDev/LockScreenLogo.scale-125.png and b/res/terminal/images-UniversalDev/LockScreenLogo.scale-125.png differ diff --git a/res/terminal/images-UniversalDev/LockScreenLogo.scale-150.png b/res/terminal/images-UniversalDev/LockScreenLogo.scale-150.png index 90d1e3eebec..a1c76d2b696 100644 Binary files a/res/terminal/images-UniversalDev/LockScreenLogo.scale-150.png and b/res/terminal/images-UniversalDev/LockScreenLogo.scale-150.png differ diff --git a/res/terminal/images-UniversalDev/LockScreenLogo.scale-200.png b/res/terminal/images-UniversalDev/LockScreenLogo.scale-200.png index 459096a49f0..d93c4df8d04 100644 Binary files a/res/terminal/images-UniversalDev/LockScreenLogo.scale-200.png and b/res/terminal/images-UniversalDev/LockScreenLogo.scale-200.png differ diff --git a/res/terminal/images-UniversalDev/LockScreenLogo.scale-400.png b/res/terminal/images-UniversalDev/LockScreenLogo.scale-400.png index b8cec41e543..931051b650a 100644 Binary files a/res/terminal/images-UniversalDev/LockScreenLogo.scale-400.png and b/res/terminal/images-UniversalDev/LockScreenLogo.scale-400.png differ diff --git a/res/terminal/images-UniversalDev/SmallTile.scale-100.png b/res/terminal/images-UniversalDev/SmallTile.scale-100.png index ce5e50eebf0..6d9c851f2db 100644 Binary files a/res/terminal/images-UniversalDev/SmallTile.scale-100.png and b/res/terminal/images-UniversalDev/SmallTile.scale-100.png differ diff --git a/res/terminal/images-UniversalDev/SmallTile.scale-125.png b/res/terminal/images-UniversalDev/SmallTile.scale-125.png index e5e395110cf..9b7068355cd 100644 Binary files a/res/terminal/images-UniversalDev/SmallTile.scale-125.png and b/res/terminal/images-UniversalDev/SmallTile.scale-125.png differ diff --git a/res/terminal/images-UniversalDev/SmallTile.scale-150.png b/res/terminal/images-UniversalDev/SmallTile.scale-150.png index 9b175e4ef24..af51a9aa0a3 100644 Binary files a/res/terminal/images-UniversalDev/SmallTile.scale-150.png and b/res/terminal/images-UniversalDev/SmallTile.scale-150.png differ diff --git a/res/terminal/images-UniversalDev/SmallTile.scale-200.png b/res/terminal/images-UniversalDev/SmallTile.scale-200.png index 4ccf2f16a83..afa5b128723 100644 Binary files a/res/terminal/images-UniversalDev/SmallTile.scale-200.png and b/res/terminal/images-UniversalDev/SmallTile.scale-200.png differ diff --git a/res/terminal/images-UniversalDev/SmallTile.scale-400.png b/res/terminal/images-UniversalDev/SmallTile.scale-400.png index 90baaee69bc..1f5ca05e572 100644 Binary files a/res/terminal/images-UniversalDev/SmallTile.scale-400.png and b/res/terminal/images-UniversalDev/SmallTile.scale-400.png differ diff --git a/res/terminal/images-UniversalDev/SplashScreen.scale-100.png b/res/terminal/images-UniversalDev/SplashScreen.scale-100.png index 4abd56c47f2..ac502d7abd9 100644 Binary files a/res/terminal/images-UniversalDev/SplashScreen.scale-100.png and b/res/terminal/images-UniversalDev/SplashScreen.scale-100.png differ diff --git a/res/terminal/images-UniversalDev/SplashScreen.scale-125.png b/res/terminal/images-UniversalDev/SplashScreen.scale-125.png index 097db06d0d8..fa975cb3e5f 100644 Binary files a/res/terminal/images-UniversalDev/SplashScreen.scale-125.png and b/res/terminal/images-UniversalDev/SplashScreen.scale-125.png differ diff --git a/res/terminal/images-UniversalDev/SplashScreen.scale-150.png b/res/terminal/images-UniversalDev/SplashScreen.scale-150.png index 46adc93691f..e17c759f58e 100644 Binary files a/res/terminal/images-UniversalDev/SplashScreen.scale-150.png and b/res/terminal/images-UniversalDev/SplashScreen.scale-150.png differ diff --git a/res/terminal/images-UniversalDev/SplashScreen.scale-200.png b/res/terminal/images-UniversalDev/SplashScreen.scale-200.png index 4605ac22850..853ba2e553e 100644 Binary files a/res/terminal/images-UniversalDev/SplashScreen.scale-200.png and b/res/terminal/images-UniversalDev/SplashScreen.scale-200.png differ diff --git a/res/terminal/images-UniversalDev/SplashScreen.scale-400.png b/res/terminal/images-UniversalDev/SplashScreen.scale-400.png index ab798721fb1..659ae08735e 100644 Binary files a/res/terminal/images-UniversalDev/SplashScreen.scale-400.png and b/res/terminal/images-UniversalDev/SplashScreen.scale-400.png differ diff --git a/res/terminal/images-UniversalDev/Square150x150Logo.scale-100.png b/res/terminal/images-UniversalDev/Square150x150Logo.scale-100.png index 23cbe571936..3315504af23 100644 Binary files a/res/terminal/images-UniversalDev/Square150x150Logo.scale-100.png and b/res/terminal/images-UniversalDev/Square150x150Logo.scale-100.png differ diff --git a/res/terminal/images-UniversalDev/Square150x150Logo.scale-125.png b/res/terminal/images-UniversalDev/Square150x150Logo.scale-125.png index 7180fcea885..93512364414 100644 Binary files a/res/terminal/images-UniversalDev/Square150x150Logo.scale-125.png and b/res/terminal/images-UniversalDev/Square150x150Logo.scale-125.png differ diff --git a/res/terminal/images-UniversalDev/Square150x150Logo.scale-150.png b/res/terminal/images-UniversalDev/Square150x150Logo.scale-150.png index b5259cebb58..3b07d3ca2ae 100644 Binary files a/res/terminal/images-UniversalDev/Square150x150Logo.scale-150.png and b/res/terminal/images-UniversalDev/Square150x150Logo.scale-150.png differ diff --git a/res/terminal/images-UniversalDev/Square150x150Logo.scale-200.png b/res/terminal/images-UniversalDev/Square150x150Logo.scale-200.png index 96d33312c73..141f2154112 100644 Binary files a/res/terminal/images-UniversalDev/Square150x150Logo.scale-200.png and b/res/terminal/images-UniversalDev/Square150x150Logo.scale-200.png differ diff --git a/res/terminal/images-UniversalDev/Square150x150Logo.scale-400.png b/res/terminal/images-UniversalDev/Square150x150Logo.scale-400.png index 3dc9270e639..5e7b5c6327e 100644 Binary files a/res/terminal/images-UniversalDev/Square150x150Logo.scale-400.png and b/res/terminal/images-UniversalDev/Square150x150Logo.scale-400.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.scale-100.png b/res/terminal/images-UniversalDev/Square44x44Logo.scale-100.png index 6f52b106d05..132d5d38866 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.scale-100.png and b/res/terminal/images-UniversalDev/Square44x44Logo.scale-100.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.scale-125.png b/res/terminal/images-UniversalDev/Square44x44Logo.scale-125.png index c47dba3358a..44acfbf8744 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.scale-125.png and b/res/terminal/images-UniversalDev/Square44x44Logo.scale-125.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.scale-150.png b/res/terminal/images-UniversalDev/Square44x44Logo.scale-150.png index 0078e54d05b..bfda7012e59 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.scale-150.png and b/res/terminal/images-UniversalDev/Square44x44Logo.scale-150.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.scale-200.png b/res/terminal/images-UniversalDev/Square44x44Logo.scale-200.png index 027dc54c9f3..fab57281ba8 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.scale-200.png and b/res/terminal/images-UniversalDev/Square44x44Logo.scale-200.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.scale-400.png b/res/terminal/images-UniversalDev/Square44x44Logo.scale-400.png index ffb62b859dc..7814eab2a4c 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.scale-400.png and b/res/terminal/images-UniversalDev/Square44x44Logo.scale-400.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-16.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-16.png index 402e5b44ad2..b5aae1f2217 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-16.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-16.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-16_altform-unplated.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-16_altform-unplated.png index 402e5b44ad2..b5aae1f2217 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-16_altform-unplated.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-16_altform-unplated.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-20.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-20.png index a6702d49cd4..72073f06333 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-20.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-20.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-20_altform-unplated.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-20_altform-unplated.png index a6702d49cd4..72073f06333 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-20_altform-unplated.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-20_altform-unplated.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-24.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-24.png index 8d2d86a7034..a186bed5272 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-24.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-24.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-24_altform-unplated.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-24_altform-unplated.png index 8d2d86a7034..a186bed5272 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-24_altform-unplated.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-24_altform-unplated.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-256.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-256.png index 3ebef2e57a6..0a5942634fa 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-256.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-256.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-256_altform-unplated.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-256_altform-unplated.png index 3ebef2e57a6..0a5942634fa 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-256_altform-unplated.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-256_altform-unplated.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-30.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-30.png index 255e4f7a6b8..015e2edee75 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-30.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-30.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-30_altform-unplated.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-30_altform-unplated.png index 255e4f7a6b8..015e2edee75 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-30_altform-unplated.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-30_altform-unplated.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-32.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-32.png index 726178cba14..c3a159493d4 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-32.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-32.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-32_altform-unplated.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-32_altform-unplated.png index 726178cba14..c3a159493d4 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-32_altform-unplated.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-32_altform-unplated.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-36.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-36.png index 90d1e3eebec..a1c76d2b696 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-36.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-36.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-36_altform-unplated.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-36_altform-unplated.png index 90d1e3eebec..a1c76d2b696 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-36_altform-unplated.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-36_altform-unplated.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-40.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-40.png index 61065fe9915..ec461fb8b90 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-40.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-40.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-40_altform-unplated.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-40_altform-unplated.png index 61065fe9915..ec461fb8b90 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-40_altform-unplated.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-40_altform-unplated.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-48.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-48.png index 459096a49f0..d93c4df8d04 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-48.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-48.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-48_altform-unplated.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-48_altform-unplated.png index 459096a49f0..d93c4df8d04 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-48_altform-unplated.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-48_altform-unplated.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-60.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-60.png index 372ef0dadbe..36a3aa20de6 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-60.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-60.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-60_altform-unplated.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-60_altform-unplated.png index 372ef0dadbe..36a3aa20de6 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-60_altform-unplated.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-60_altform-unplated.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-64.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-64.png index 3605ee2f052..e2f49bbc31a 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-64.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-64.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-64_altform-unplated.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-64_altform-unplated.png index 3605ee2f052..e2f49bbc31a 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-64_altform-unplated.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-64_altform-unplated.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-72.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-72.png index 4b40d590035..1cc1e3bce9f 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-72.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-72.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-72_altform-unplated.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-72_altform-unplated.png index 4b40d590035..1cc1e3bce9f 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-72_altform-unplated.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-72_altform-unplated.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-80.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-80.png index 1cfdbc736c6..65cb25deb4b 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-80.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-80.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-80_altform-unplated.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-80_altform-unplated.png index 1cfdbc736c6..65cb25deb4b 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-80_altform-unplated.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-80_altform-unplated.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-96.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-96.png index b8cec41e543..931051b650a 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-96.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-96.png differ diff --git a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-96_altform-unplated.png b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-96_altform-unplated.png index b8cec41e543..931051b650a 100644 Binary files a/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-96_altform-unplated.png and b/res/terminal/images-UniversalDev/Square44x44Logo.targetsize-96_altform-unplated.png differ diff --git a/res/terminal/images-UniversalDev/StoreLogo.scale-100.png b/res/terminal/images-UniversalDev/StoreLogo.scale-100.png index 39c3bbb2693..f9656b5e1d9 100644 Binary files a/res/terminal/images-UniversalDev/StoreLogo.scale-100.png and b/res/terminal/images-UniversalDev/StoreLogo.scale-100.png differ diff --git a/res/terminal/images-UniversalDev/StoreLogo.scale-125.png b/res/terminal/images-UniversalDev/StoreLogo.scale-125.png index 67fbd0d10c5..3a032967e37 100644 Binary files a/res/terminal/images-UniversalDev/StoreLogo.scale-125.png and b/res/terminal/images-UniversalDev/StoreLogo.scale-125.png differ diff --git a/res/terminal/images-UniversalDev/StoreLogo.scale-150.png b/res/terminal/images-UniversalDev/StoreLogo.scale-150.png index 7de33e3bddb..2b614d6df74 100644 Binary files a/res/terminal/images-UniversalDev/StoreLogo.scale-150.png and b/res/terminal/images-UniversalDev/StoreLogo.scale-150.png differ diff --git a/res/terminal/images-UniversalDev/StoreLogo.scale-200.png b/res/terminal/images-UniversalDev/StoreLogo.scale-200.png index 555fb5aff0f..6f9cb96a685 100644 Binary files a/res/terminal/images-UniversalDev/StoreLogo.scale-200.png and b/res/terminal/images-UniversalDev/StoreLogo.scale-200.png differ diff --git a/res/terminal/images-UniversalDev/StoreLogo.scale-400.png b/res/terminal/images-UniversalDev/StoreLogo.scale-400.png index ab6858c9520..3c4887caafc 100644 Binary files a/res/terminal/images-UniversalDev/StoreLogo.scale-400.png and b/res/terminal/images-UniversalDev/StoreLogo.scale-400.png differ diff --git a/res/terminal/images-UniversalDev/Wide310x150Logo.scale-100.png b/res/terminal/images-UniversalDev/Wide310x150Logo.scale-100.png index 61e7d555afc..2003e0c724d 100644 Binary files a/res/terminal/images-UniversalDev/Wide310x150Logo.scale-100.png and b/res/terminal/images-UniversalDev/Wide310x150Logo.scale-100.png differ diff --git a/res/terminal/images-UniversalDev/Wide310x150Logo.scale-125.png b/res/terminal/images-UniversalDev/Wide310x150Logo.scale-125.png index 4a932a440e1..34a309ad210 100644 Binary files a/res/terminal/images-UniversalDev/Wide310x150Logo.scale-125.png and b/res/terminal/images-UniversalDev/Wide310x150Logo.scale-125.png differ diff --git a/res/terminal/images-UniversalDev/Wide310x150Logo.scale-150.png b/res/terminal/images-UniversalDev/Wide310x150Logo.scale-150.png index b7a1f6ecdf3..d389d1ae4cd 100644 Binary files a/res/terminal/images-UniversalDev/Wide310x150Logo.scale-150.png and b/res/terminal/images-UniversalDev/Wide310x150Logo.scale-150.png differ diff --git a/res/terminal/images-UniversalDev/Wide310x150Logo.scale-200.png b/res/terminal/images-UniversalDev/Wide310x150Logo.scale-200.png index 4abd56c47f2..ac502d7abd9 100644 Binary files a/res/terminal/images-UniversalDev/Wide310x150Logo.scale-200.png and b/res/terminal/images-UniversalDev/Wide310x150Logo.scale-200.png differ diff --git a/res/terminal/images-UniversalDev/Wide310x150Logo.scale-400.png b/res/terminal/images-UniversalDev/Wide310x150Logo.scale-400.png index 4605ac22850..853ba2e553e 100644 Binary files a/res/terminal/images-UniversalDev/Wide310x150Logo.scale-400.png and b/res/terminal/images-UniversalDev/Wide310x150Logo.scale-400.png differ diff --git a/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp b/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp index b733672fd18..69f64d41a93 100644 --- a/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp +++ b/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp @@ -341,7 +341,7 @@ static const std::mapAction maping. The created object is of schema: +// KeyBinding->Action maping. // { // keys:[String], // command:String diff --git a/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp index 9bbd6ab25ca..9abb3274317 100644 --- a/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp @@ -39,7 +39,6 @@ static constexpr std::string_view SchemesKey{ "schemes" }; static constexpr std::string_view DisabledProfileSourcesKey{ "disabledProfileSources" }; static constexpr std::string_view Utf8Bom{ u8"\uFEFF" }; -static constexpr std::string_view DefaultProfilesIndentation{ " " }; static constexpr std::string_view SettingsSchemaFragment{ "\n" R"( "$schema": "https://aka.ms/terminal-profiles-schema")" }; @@ -59,6 +58,11 @@ std::unique_ptr CascadiaSettings::LoadAll() { auto resultPtr = LoadDefaults(); + // GH 3588, we need this below to know if the user chose something that wasn't our default. + // Collect it up here in case it gets modified by any of the other layers between now and when + // the user's preferences are loaded and layered. + const auto hardcodedDefaultGuid = resultPtr->GlobalSettings().GetDefaultProfile(); + std::optional fileData = _ReadUserSettings(); const bool foundFile = fileData.has_value(); @@ -129,6 +133,57 @@ std::unique_ptr CascadiaSettings::LoadAll() // If this throws, the app will catch it and use the default settings resultPtr->_ValidateSettings(); + // GH 3855 - Gathering Data on custom profiles to inform better defaults + // Do it after everything else so it won't happen unless validation passed. + // Also, avoid processing unless someone's listening for measures. The keybindings work, at least, + // is a lot of computation we can skip if no one cares. + if (TraceLoggingProviderEnabled(g_hTerminalAppProvider, 0, MICROSOFT_KEYWORD_MEASURES)) + { + auto guid = resultPtr->GlobalSettings().GetDefaultProfile(); + + // Compare to the defaults.json one that we set on install. + // If it's different, log what the user chose. + if (hardcodedDefaultGuid != guid) + { + TraceLoggingWrite( + g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider + "CustomDefaultProfile", + TraceLoggingDescription("Event emitted when user has chosen a different default profile than hardcoded one on load/reload"), + TraceLoggingGuid(guid, "DefaultProfile", "ID of user-chosen default profile"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + } + + // If the user had keybinding settings preferences, we want to learn from them to make better defaults + auto userKeybindings = resultPtr->_userSettings[JsonKey(KeybindingsKey)]; + if (!userKeybindings.empty()) + { + // If there are custom key bindings, let's understand what they are because maybe the defaults aren't good enough + + // Run it through the object so we can parse it apart and then only serialize the fields we're interested in + // and avoid extraneous data. + auto akb = winrt::make_self(); + akb->LayerJson(userKeybindings); + auto value = akb->ToJson(); + + // Reserialize the keybindings + Json::StreamWriterBuilder wbuilder; + // Use 4 spaces to indent instead of \t + wbuilder.settings_["indentation"] = " "; + wbuilder.settings_["enableYAMLCompatibility"] = true; // suppress spaces around colons + + const auto keybindingsString = Json::writeString(wbuilder, value); + + TraceLoggingWrite( + g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider + "CustomKeybindings", + TraceLoggingDescription("Event emitted when custom keybindings are idenfitied on load/reload"), + TraceLoggingUtf8String(keybindingsString.c_str(), "Keybindings", "Keybindings as JSON"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + } + } + return resultPtr; } @@ -358,6 +413,11 @@ bool CascadiaSettings::_AppendDynamicProfilesToUserSettings() const auto numProfiles = userProfilesObj.size(); const auto lastProfile = userProfilesObj[numProfiles - 1]; size_t currentInsertIndex = lastProfile.getOffsetLimit(); + // Find the position of the first non-tab/space character before the last profile... + const auto lastProfileIndentStartsAt{ _userSettingsString.find_last_not_of(" \t", lastProfile.getOffsetStart() - 1) }; + // ... and impute the user's preferred indentation. + // (we're taking a copy because a string_view into a string we mutate is a no-no.) + const std::string indentation{ _userSettingsString, lastProfileIndentStartsAt + 1, lastProfile.getOffsetStart() - lastProfileIndentStartsAt - 1 }; bool changedFile = false; @@ -388,17 +448,17 @@ bool CascadiaSettings::_AppendDynamicProfilesToUserSettings() const auto diff = profile.GenerateStub(); auto profileSerialization = Json::writeString(wbuilder, diff); - // Add 8 spaces to the start of each line - profileSerialization.insert(0, DefaultProfilesIndentation); + // Add the user's indent to the start of each line + profileSerialization.insert(0, indentation); // Get the first newline size_t pos = profileSerialization.find("\n"); // for each newline... while (pos != std::string::npos) { // Insert 8 spaces immediately following the current newline - profileSerialization.insert(pos + 1, DefaultProfilesIndentation); + profileSerialization.insert(pos + 1, indentation); // Get the next newline - pos = profileSerialization.find("\n", pos + 9); + pos = profileSerialization.find("\n", pos + indentation.size() + 1); } // Write a comma, newline to the file diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index fd5f304cb39..19fb2bfe87e 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -481,9 +481,13 @@ namespace winrt::TerminalApp::implementation g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider "TabInformation", TraceLoggingDescription("Event emitted upon new tab creation in TerminalApp"), + TraceLoggingUInt32(1u, "EventVer", "Version of this event"), TraceLoggingUInt32(tabCount, "TabCount", "Count of tabs currently opened in TerminalApp"), TraceLoggingBool(usedManualProfile, "ProfileSpecified", "Whether the new tab specified a profile explicitly"), TraceLoggingGuid(profileGuid, "ProfileGuid", "The GUID of the profile spawned in the new tab"), + TraceLoggingBool(settings.UseAcrylic(), "UseAcrylic", "The acrylic preference from the settings"), + TraceLoggingFloat64(settings.TintOpacity(), "TintOpacity", "Opacity preference from the settings"), + TraceLoggingWideString(settings.FontFace().c_str(), "FontFace", "Font face chosen in the settings"), TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); } @@ -838,25 +842,6 @@ namespace winrt::TerminalApp::implementation { _lastTabClosedHandlers(*this, nullptr); } - - if (auto indexOpt{ _GetFocusedTabIndex() }) - { - auto focusedTabIndex = *indexOpt; - if (tabIndex == focusedTabIndex) - { - uint32_t tabCount = _tabs.Size(); - if (focusedTabIndex >= tabCount) - { - focusedTabIndex = tabCount - 1; - } - else if (focusedTabIndex < 0) - { - focusedTabIndex = 0; - } - - _SelectTab(focusedTabIndex); - } - } } // Method Description: diff --git a/src/cascadia/TerminalApp/defaults.json b/src/cascadia/TerminalApp/defaults.json index ce9463a0c75..8ddf2a38fa8 100644 --- a/src/cascadia/TerminalApp/defaults.json +++ b/src/cascadia/TerminalApp/defaults.json @@ -8,7 +8,7 @@ "showTabsInTitlebar": true, "showTerminalTitleInTitlebar": true, "tabWidthMode": "equal", - "snapToGridOnResize": false, + "snapToGridOnResize": true, "wordDelimiters": " /\\()\"'-.,:;<>~!@#$%^&*|+=[]{}~?\u2502", "confirmCloseAllTabs": true, diff --git a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj index e2d49d6b9ba..618b41a0484 100644 --- a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj @@ -286,7 +286,7 @@ pch.h - ..;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\jsoncpp\json;%(AdditionalIncludeDirectories); + ..;$(OpenConsoleDir)\dep\jsoncpp\json;%(AdditionalIncludeDirectories); 4702;%(DisableSpecificWarnings) diff --git a/src/cascadia/TerminalApp/userDefaults.json b/src/cascadia/TerminalApp/userDefaults.json index 1f3c5a1ad6b..e5b325a1d86 100644 --- a/src/cascadia/TerminalApp/userDefaults.json +++ b/src/cascadia/TerminalApp/userDefaults.json @@ -7,22 +7,29 @@ "defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}", "profiles": - [ + { + "defaults": { - // Make changes here to the powershell.exe profile - "guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}", - "name": "Windows PowerShell", - "commandline": "powershell.exe", - "hidden": false + // Put settings here that you want to apply to all profiles }, - { - // Make changes here to the cmd.exe profile - "guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}", - "name": "cmd", - "commandline": "cmd.exe", - "hidden": false - } - ], + "list": + [ + { + // Make changes here to the powershell.exe profile + "guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}", + "name": "Windows PowerShell", + "commandline": "powershell.exe", + "hidden": false + }, + { + // Make changes here to the cmd.exe profile + "guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}", + "name": "cmd", + "commandline": "cmd.exe", + "hidden": false + } + ] + }, // Add custom color schemes to this array "schemes": [], diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 4f352a6340d..a42d34ff6e2 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -153,6 +153,19 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation DeleteProcThreadAttributeList(siEx.lpAttributeList); + const std::filesystem::path processName = wil::GetModuleFileNameExW(_piClient.hProcess, nullptr); + _clientName = processName.filename().wstring(); + +#pragma warning(suppress : 26477 26485 26494 26482 26446) // We don't control TraceLoggingWrite + TraceLoggingWrite( + g_hTerminalConnectionProvider, + "ConPtyConnected", + TraceLoggingDescription("Event emitted when ConPTY connection is started"), + TraceLoggingGuid(_guid, "SessionGuid", "The WT_SESSION's GUID"), + TraceLoggingWideString(_clientName.c_str(), "Client", "The attached client process"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); + return S_OK; } CATCH_RETURN(); diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h index c9888ab44d2..b450f657ee1 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.h +++ b/src/cascadia/TerminalConnection/ConptyConnection.h @@ -41,6 +41,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation hstring _startingDirectory; hstring _startingTitle; 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 }; std::chrono::high_resolution_clock::time_point _startTime{}; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 773e18efedb..550f397b042 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -1,2259 +1,2278 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "pch.h" -#include "TermControl.h" -#include -#include -#include -#include -#include -#include -#include -#include "..\..\types\inc\GlyphWidth.hpp" - -#include "TermControl.g.cpp" -#include "TermControlAutomationPeer.h" - -using namespace ::Microsoft::Console::Types; -using namespace ::Microsoft::Terminal::Core; -using namespace winrt::Windows::UI::Xaml; -using namespace winrt::Windows::UI::Xaml::Automation::Peers; -using namespace winrt::Windows::UI::Core; -using namespace winrt::Windows::System; -using namespace winrt::Microsoft::Terminal::Settings; -using namespace winrt::Windows::ApplicationModel::DataTransfer; - -namespace winrt::Microsoft::Terminal::TerminalControl::implementation -{ - // Helper static function to ensure that all ambiguous-width glyphs are reported as narrow. - // See microsoft/terminal#2066 for more info. - static bool _IsGlyphWideForceNarrowFallback(const std::wstring_view /* glyph */) - { - return false; // glyph is not wide. - } - - static bool _EnsureStaticInitialization() - { - // use C++11 magic statics to make sure we only do this once. - static bool initialized = []() { - // *** THIS IS A SINGLETON *** - SetGlyphWidthFallback(_IsGlyphWideForceNarrowFallback); - - return true; - }(); - return initialized; - } - - TermControl::TermControl() : - TermControl(Settings::TerminalSettings{}, TerminalConnection::ITerminalConnection{ nullptr }) - { - } - - TermControl::TermControl(Settings::IControlSettings settings, TerminalConnection::ITerminalConnection connection) : - _connection{ connection }, - _initializedTerminal{ false }, - _root{ nullptr }, - _swapChainPanel{ nullptr }, - _settings{ settings }, - _closing{ false }, - _isTerminalInitiatedScroll{ false }, - _autoScrollVelocity{ 0 }, - _autoScrollingPointerPoint{ std::nullopt }, - _autoScrollTimer{}, - _lastAutoScrollUpdateTime{ std::nullopt }, - _desiredFont{ DEFAULT_FONT_FACE, 0, 10, { 0, DEFAULT_FONT_SIZE }, CP_UTF8 }, - _actualFont{ DEFAULT_FONT_FACE, 0, 10, { 0, DEFAULT_FONT_SIZE }, CP_UTF8, false }, - _touchAnchor{ std::nullopt }, - _cursorTimer{}, - _lastMouseClick{}, - _lastMouseClickPos{}, - _searchBox{ nullptr }, - _tsfInputControl{ nullptr } - { - _EnsureStaticInitialization(); - _Create(); - } - - void TermControl::_Create() - { - Controls::Grid container; - - Controls::ColumnDefinition contentColumn{}; - Controls::ColumnDefinition scrollbarColumn{}; - contentColumn.Width(GridLength{ 1.0, GridUnitType::Star }); - scrollbarColumn.Width(GridLength{ 1.0, GridUnitType::Auto }); - - container.ColumnDefinitions().Append(contentColumn); - container.ColumnDefinitions().Append(scrollbarColumn); - - _scrollBar = Controls::Primitives::ScrollBar{}; - _scrollBar.Orientation(Controls::Orientation::Vertical); - _scrollBar.IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::MouseIndicator); - _scrollBar.HorizontalAlignment(HorizontalAlignment::Right); - _scrollBar.VerticalAlignment(VerticalAlignment::Stretch); - - // Initialize the scrollbar with some placeholder values. - // The scrollbar will be updated with real values on _Initialize - _scrollBar.Maximum(1); - _scrollBar.ViewportSize(10); - _scrollBar.IsTabStop(false); - _scrollBar.SmallChange(1); - _scrollBar.LargeChange(4); - _scrollBar.Visibility(Visibility::Visible); - - _tsfInputControl = TSFInputControl(); - _tsfInputControl.CompositionCompleted({ this, &TermControl::_CompositionCompleted }); - _tsfInputControl.CurrentCursorPosition({ this, &TermControl::_CurrentCursorPositionHandler }); - _tsfInputControl.CurrentFontInfo({ this, &TermControl::_FontInfoHandler }); - container.Children().Append(_tsfInputControl); - - // Create the SwapChainPanel that will display our content - Controls::SwapChainPanel swapChainPanel; - - _sizeChangedRevoker = swapChainPanel.SizeChanged(winrt::auto_revoke, { this, &TermControl::_SwapChainSizeChanged }); - _compositionScaleChangedRevoker = swapChainPanel.CompositionScaleChanged(winrt::auto_revoke, { this, &TermControl::_SwapChainScaleChanged }); - - // Initialize the terminal only once the swapchainpanel is loaded - that - // way, we'll be able to query the real pixel size it got on layout - _layoutUpdatedRevoker = swapChainPanel.LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) { - // This event fires every time the layout changes, but it is always the last one to fire - // in any layout change chain. That gives us great flexibility in finding the right point - // at which to initialize our renderer (and our terminal). - // Any earlier than the last layout update and we may not know the terminal's starting size. - - if (_InitializeTerminal()) - { - // Only let this succeed once. - _layoutUpdatedRevoker.revoke(); - } - }); - - container.Children().Append(swapChainPanel); - container.Children().Append(_scrollBar); - Controls::Grid::SetColumn(swapChainPanel, 0); - Controls::Grid::SetColumn(_scrollBar, 1); - - Controls::Grid root{}; - Controls::Image bgImageLayer{}; - root.Children().Append(bgImageLayer); - root.Children().Append(container); - - _root = root; - _bgImageLayer = bgImageLayer; - - _swapChainPanel = swapChainPanel; - this->Content(_root); - - _ApplyUISettings(); - - // These are important: - // 1. When we get tapped, focus us - _tappedRevoker = this->Tapped(winrt::auto_revoke, [this](auto&, auto& e) { - Focus(FocusState::Pointer); - e.Handled(true); - }); - // 2. Make sure we can be focused (why this isn't `Focusable` I'll never know) - this->IsTabStop(true); - // 3. Actually not sure about this one. Maybe it isn't necessary either. - this->AllowFocusOnInteraction(true); - - // DON'T CALL _InitializeTerminal here - wait until the swap chain is loaded to do that. - - // Subscribe to the connection's disconnected event and call our connection closed handlers. - _connectionStateChangedRevoker = _connection.StateChanged(winrt::auto_revoke, [this](auto&& /*s*/, auto&& /*v*/) { - _ConnectionStateChangedHandlers(*this, nullptr); - }); - - _root.AllowDrop(true); - _root.Drop({ get_weak(), &TermControl::_DragDropHandler }); - _root.DragOver({ get_weak(), &TermControl::_DragOverHandler }); - } - - // Method Description: - // - Create the SearchBoxControl object, and attach it - // to the Terminal Control root - // Arguments: - // - - // Return Value: - // - - void TermControl::CreateSearchBoxControl() - { - if (!_searchBox) - { - _searchBox = winrt::make_self(); - _searchBox->HorizontalAlignment(HorizontalAlignment::Right); - _searchBox->VerticalAlignment(VerticalAlignment::Top); - // We need to make sure the searchbox does not overlap - // with the scroll bar - Thickness searchBoxPadding = { 0, 0, _scrollBar.ActualWidth(), 0 }; - _searchBox->Margin(searchBoxPadding); - - _root.Children().Append(*_searchBox); - - // Event handlers - _searchBox->Search({ get_weak(), &TermControl::_Search }); - _searchBox->Closed({ get_weak(), &TermControl::_CloseSearchBoxControl }); - } - - _searchBox->SetFocusOnTextbox(); - } - - // Method Description: - // - Search text in text buffer. This is triggered if the user click - // search button or press enter. - // Arguments: - // - text: the text to search - // - goForward: boolean that represents if the current search direction is forward - // - caseSensitive: boolean that represents if the current search is case sensitive - // Return Value: - // - - void TermControl::_Search(const winrt::hstring& text, const bool goForward, const bool caseSensitive) - { - if (text.size() == 0) - { - return; - } - - const Search::Direction direction = goForward ? - Search::Direction::Forward : - Search::Direction::Backward; - - const Search::Sensitivity sensitivity = caseSensitive ? - Search::Sensitivity::CaseSensitive : - Search::Sensitivity::CaseInsensitive; - - Search search(*GetUiaData(), text.c_str(), direction, sensitivity); - auto lock = _terminal->LockForWriting(); - if (search.FindNext()) - { - _terminal->SetBoxSelection(false); - search.Select(); - _renderer->TriggerSelection(); - } - } - - // Method Description: - // - The handler for the close button or pressing "Esc" when focusing on the - // search dialog. - // This removes the SearchBoxControl object from the XAML tree, - // reset smart pointer and set focus back to Terminal - // Arguments: - // - IInspectable: not used - // - RoutedEventArgs: not used - // Return Value: - // - - void TermControl::_CloseSearchBoxControl(const winrt::Windows::Foundation::IInspectable& /*sender*/, RoutedEventArgs const& /*args*/) - { - unsigned int idx; - _root.Children().IndexOf(*_searchBox, idx); - _root.Children().RemoveAt(idx); - - _searchBox = nullptr; - - // Set focus back to terminal control - this->Focus(FocusState::Programmatic); - } - - // Method Description: - // - Given new settings for this profile, applies the settings to the current terminal. - // Arguments: - // - newSettings: New settings values for the profile in this terminal. - // Return Value: - // - - winrt::fire_and_forget TermControl::UpdateSettings(Settings::IControlSettings newSettings) - { - _settings = newSettings; - auto weakThis{ get_weak() }; - - // Dispatch a call to the UI thread to apply the new settings to the - // terminal. - co_await winrt::resume_foreground(_root.Dispatcher()); - - // If 'weakThis' is locked, then we can safely work with 'this' - if (auto control{ weakThis.get() }) - { - // Update our control settings - _ApplyUISettings(); - - // Update DxEngine's SelectionBackground - _renderEngine->SetSelectionBackground(_settings.SelectionBackground()); - - // Update the terminal core with its new Core settings - _terminal->UpdateSettings(_settings); - - // Refresh our font with the renderer - _UpdateFont(); - - const auto width = _swapChainPanel.ActualWidth(); - const auto height = _swapChainPanel.ActualHeight(); - if (width != 0 && height != 0) - { - // If the font size changed, or the _swapchainPanel's size changed - // for any reason, we'll need to make sure to also resize the - // buffer. _DoResize will invalidate everything for us. - auto lock = _terminal->LockForWriting(); - _DoResize(width, height); - } - - // set TSF Foreground - Media::SolidColorBrush foregroundBrush{}; - foregroundBrush.Color(ColorRefToColor(_settings.DefaultForeground())); - _tsfInputControl.Foreground(foregroundBrush); - } - } - - // Method Description: - // - Style our UI elements based on the values in our _settings, and set up - // other control-specific settings. This method will be called whenever - // the settings are reloaded. - // * Calls _InitializeBackgroundBrush to set up the Xaml brush responsible - // for the control's background - // * Calls _BackgroundColorChanged to style the background of the control - // - Core settings will be passed to the terminal in _InitializeTerminal - // Arguments: - // - - // Return Value: - // - - void TermControl::_ApplyUISettings() - { - _InitializeBackgroundBrush(); - - uint32_t bg = _settings.DefaultBackground(); - _BackgroundColorChanged(bg); - - // Apply padding as swapChainPanel's margin - auto newMargin = _ParseThicknessFromPadding(_settings.Padding()); - auto existingMargin = _swapChainPanel.Margin(); - _swapChainPanel.Margin(newMargin); - - if (newMargin != existingMargin && newMargin != Thickness{ 0 }) - { - TraceLoggingWrite(g_hTerminalControlProvider, - "NonzeroPaddingApplied", - TraceLoggingDescription("An event emitted when a control has padding applied to it"), - TraceLoggingStruct(4, "Padding"), - TraceLoggingFloat64(newMargin.Left, "Left"), - TraceLoggingFloat64(newMargin.Top, "Top"), - TraceLoggingFloat64(newMargin.Right, "Right"), - TraceLoggingFloat64(newMargin.Bottom, "Bottom"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); - } - - // Initialize our font information. - const auto fontFace = _settings.FontFace(); - const short fontHeight = gsl::narrow(_settings.FontSize()); - // The font width doesn't terribly matter, we'll only be using the - // height to look it up - // The other params here also largely don't matter. - // The family is only used to determine if the font is truetype or - // not, but DX doesn't use that info at all. - // The Codepage is additionally not actually used by the DX engine at all. - _actualFont = { fontFace, 0, 10, { 0, fontHeight }, CP_UTF8, false }; - _desiredFont = { _actualFont }; - - // set TSF Foreground - Media::SolidColorBrush foregroundBrush{}; - foregroundBrush.Color(ColorRefToColor(_settings.DefaultForeground())); - _tsfInputControl.Foreground(foregroundBrush); - _tsfInputControl.Margin(newMargin); - - // set number of rows to scroll at a time - _rowsToScroll = _settings.RowsToScroll(); - } - - // Method Description: - // - Set up each layer's brush used to display the control's background. - // - Respects the settings for acrylic, background image and opacity from - // _settings. - // * If acrylic is not enabled, setup a solid color background, otherwise - // use bgcolor as acrylic's tint - // - Avoids image flickering and acrylic brush redraw if settings are changed - // but the appropriate brush is still in place. - // - Does not apply background color outside of acrylic mode; - // _BackgroundColorChanged must be called to do so. - // Arguments: - // - - // Return Value: - // - - void TermControl::_InitializeBackgroundBrush() - { - if (_settings.UseAcrylic()) - { - // See if we've already got an acrylic background brush - // to avoid the flicker when setting up a new one - auto acrylic = _root.Background().try_as(); - - // Instantiate a brush if there's not already one there - if (acrylic == nullptr) - { - acrylic = Media::AcrylicBrush{}; - acrylic.BackgroundSource(Media::AcrylicBackgroundSource::HostBackdrop); - } - - // see GH#1082: Initialize background color so we don't get a - // fade/flash when _BackgroundColorChanged is called - uint32_t color = _settings.DefaultBackground(); - winrt::Windows::UI::Color bgColor{}; - bgColor.R = GetRValue(color); - bgColor.G = GetGValue(color); - bgColor.B = GetBValue(color); - bgColor.A = 255; - - acrylic.FallbackColor(bgColor); - acrylic.TintColor(bgColor); - - // Apply brush settings - acrylic.TintOpacity(_settings.TintOpacity()); - - // Apply brush to control if it's not already there - if (_root.Background() != acrylic) - { - _root.Background(acrylic); - } - } - else - { - Media::SolidColorBrush solidColor{}; - _root.Background(solidColor); - } - - if (!_settings.BackgroundImage().empty()) - { - Windows::Foundation::Uri imageUri{ _settings.BackgroundImage() }; - - // Check if the image brush is already pointing to the image - // in the modified settings; if it isn't (or isn't there), - // set a new image source for the brush - auto imageSource = _bgImageLayer.Source().try_as(); - - if (imageSource == nullptr || - imageSource.UriSource() == nullptr || - imageSource.UriSource().RawUri() != imageUri.RawUri()) - { - // Note that BitmapImage handles the image load asynchronously, - // which is especially important since the image - // may well be both large and somewhere out on the - // internet. - Media::Imaging::BitmapImage image(imageUri); - _bgImageLayer.Source(image); - } - - // Apply stretch, opacity and alignment settings - _bgImageLayer.Stretch(_settings.BackgroundImageStretchMode()); - _bgImageLayer.Opacity(_settings.BackgroundImageOpacity()); - _bgImageLayer.HorizontalAlignment(_settings.BackgroundImageHorizontalAlignment()); - _bgImageLayer.VerticalAlignment(_settings.BackgroundImageVerticalAlignment()); - } - else - { - _bgImageLayer.Source(nullptr); - } - } - - // Method Description: - // - Style the background of the control with the provided background color - // Arguments: - // - color: The background color to use as a uint32 (aka DWORD COLORREF) - // Return Value: - // - - winrt::fire_and_forget TermControl::_BackgroundColorChanged(const uint32_t color) - { - auto weakThis{ get_weak() }; - - co_await winrt::resume_foreground(_root.Dispatcher()); - - if (auto control{ weakThis.get() }) - { - const auto R = GetRValue(color); - const auto G = GetGValue(color); - const auto B = GetBValue(color); - - winrt::Windows::UI::Color bgColor{}; - bgColor.R = R; - bgColor.G = G; - bgColor.B = B; - bgColor.A = 255; - - if (auto acrylic = _root.Background().try_as()) - { - acrylic.FallbackColor(bgColor); - acrylic.TintColor(bgColor); - } - else if (auto solidColor = _root.Background().try_as()) - { - solidColor.Color(bgColor); - } - - // Set the default background as transparent to prevent the - // DX layer from overwriting the background image or acrylic effect - _settings.DefaultBackground(ARGB(0, R, G, B)); - } - } - - TermControl::~TermControl() - { - Close(); - } - - // Method Description: - // - Creates an automation peer for the Terminal Control, enabling accessibility on our control. - // Arguments: - // - None - // Return Value: - // - The automation peer for our control - Windows::UI::Xaml::Automation::Peers::AutomationPeer TermControl::OnCreateAutomationPeer() - try - { - if (GetUiaData()) - { - // create a custom automation peer with this code pattern: - // (https://docs.microsoft.com/en-us/windows/uwp/design/accessibility/custom-automation-peers) - auto autoPeer = winrt::make_self(this); - - _uiaEngine = std::make_unique<::Microsoft::Console::Render::UiaEngine>(autoPeer.get()); - _renderer->AddRenderEngine(_uiaEngine.get()); - return *autoPeer; - } - return nullptr; - } - catch (...) - { - LOG_CAUGHT_EXCEPTION(); - return nullptr; - } - - ::Microsoft::Console::Types::IUiaData* TermControl::GetUiaData() const - { - return _terminal.get(); - } - - const FontInfo TermControl::GetActualFont() const - { - return _actualFont; - } - - const Windows::UI::Xaml::Thickness TermControl::GetPadding() const - { - return _swapChainPanel.Margin(); - } - - TerminalConnection::ConnectionState TermControl::ConnectionState() const - { - return _connection.State(); - } - - winrt::fire_and_forget TermControl::SwapChainChanged() - { - if (!_initializedTerminal) - { - return; - } - - auto chain = _renderEngine->GetSwapChain(); - auto weakThis{ get_weak() }; - - co_await winrt::resume_foreground(_swapChainPanel.Dispatcher()); - - // If 'weakThis' is locked, then we can safely work with 'this' - if (auto control{ weakThis.get() }) - { - auto lock = _terminal->LockForWriting(); - auto nativePanel = _swapChainPanel.as(); - nativePanel->SetSwapChain(chain.Get()); - } - } - - winrt::fire_and_forget TermControl::_SwapChainRoutine() - { - auto chain = _renderEngine->GetSwapChain(); - auto weakThis{ get_weak() }; - - co_await winrt::resume_foreground(_swapChainPanel.Dispatcher()); - - if (auto control{ weakThis.get() }) - { - _terminal->LockConsole(); - auto nativePanel = _swapChainPanel.as(); - nativePanel->SetSwapChain(chain.Get()); - _terminal->UnlockConsole(); - } - } - - bool TermControl::_InitializeTerminal() - { - if (_initializedTerminal) - { - return false; - } - - const auto windowWidth = _swapChainPanel.ActualWidth(); // Width() and Height() are NaN? - const auto windowHeight = _swapChainPanel.ActualHeight(); - - if (windowWidth == 0 || windowHeight == 0) - { - return false; - } - - _terminal = std::make_unique<::Microsoft::Terminal::Core::Terminal>(); - - // First create the render thread. - // Then stash a local pointer to the render thread so we can initialize it and enable it - // to paint itself *after* we hand off its ownership to the renderer. - // We split up construction and initialization of the render thread object this way - // because the renderer and render thread have circular references to each other. - auto renderThread = std::make_unique<::Microsoft::Console::Render::RenderThread>(); - auto* const localPointerToThread = renderThread.get(); - - // Now create the renderer and initialize the render thread. - _renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(_terminal.get(), nullptr, 0, std::move(renderThread)); - ::Microsoft::Console::Render::IRenderTarget& renderTarget = *_renderer; - - THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get())); - - // Set up the DX Engine - auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>(); - _renderer->AddRenderEngine(dxEngine.get()); - - // Initialize our font with the renderer - // We don't have to care about DPI. We'll get a change message immediately if it's not 96 - // and react accordingly. - _UpdateFont(true); - - const COORD windowSize{ static_cast(windowWidth), static_cast(windowHeight) }; - - // Fist set up the dx engine with the window size in pixels. - // Then, using the font, get the number of characters that can fit. - // Resize our terminal connection to match that size, and initialize the terminal with that size. - const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, windowSize); - THROW_IF_FAILED(dxEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() })); - - // Update DxEngine's SelectionBackground - dxEngine->SetSelectionBackground(_settings.SelectionBackground()); - - const auto vp = dxEngine->GetViewportInCharacters(viewInPixels); - const auto width = vp.Width(); - const auto height = vp.Height(); - _connection.Resize(height, width); - - // Override the default width and height to match the size of the swapChainPanel - _settings.InitialCols(width); - _settings.InitialRows(height); - - _terminal->CreateFromSettings(_settings, renderTarget); - - // Tell the DX Engine to notify us when the swap chain changes. - dxEngine->SetCallback(std::bind(&TermControl::SwapChainChanged, this)); - - // TODO:GH#3927 - Make it possible to hot-reload this setting. Right - // here, the setting will only be used when the Terminal is initialized. - dxEngine->SetRetroTerminalEffects(_settings.RetroTerminalEffect()); - - THROW_IF_FAILED(dxEngine->Enable()); - _renderEngine = std::move(dxEngine); - - // This event is explicitly revoked in the destructor: does not need weak_ref - auto onReceiveOutputFn = [this](const hstring str) { - _terminal->Write(str); - }; - _connectionOutputEventToken = _connection.TerminalOutput(onReceiveOutputFn); - - auto inputFn = std::bind(&TermControl::_SendInputToConnection, this, std::placeholders::_1); - _terminal->SetWriteInputCallback(inputFn); - - _SwapChainRoutine(); - - // Set up the height of the ScrollViewer and the grid we're using to fake our scrolling height - auto bottom = _terminal->GetViewport().BottomExclusive(); - auto bufferHeight = bottom; - - const auto originalMaximum = _scrollBar.Maximum(); - const auto originalMinimum = _scrollBar.Minimum(); - const auto originalValue = _scrollBar.Value(); - const auto originalViewportSize = _scrollBar.ViewportSize(); - - _scrollBar.Maximum(bufferHeight - bufferHeight); - _scrollBar.Minimum(0); - _scrollBar.Value(0); - _scrollBar.ViewportSize(bufferHeight); - _scrollBar.ValueChanged({ this, &TermControl::_ScrollbarChangeHandler }); - _scrollBar.PointerPressed({ this, &TermControl::_CapturePointer }); - _scrollBar.PointerReleased({ this, &TermControl::_ReleasePointerCapture }); - - // Apply settings for scrollbar - if (_settings.ScrollState() == ScrollbarState::Visible) - { - _scrollBar.IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::MouseIndicator); - } - else if (_settings.ScrollState() == ScrollbarState::Hidden) - { - _scrollBar.IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::None); - - // In the scenario where the user has turned off the OS setting to automatically hide scollbars, the - // Terminal scrollbar would still be visible; so, we need to set the control's visibility accordingly to - // achieve the intended effect. - _scrollBar.Visibility(Visibility::Collapsed); - } - else - { - // Default behavior - _scrollBar.IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::MouseIndicator); - } - - _root.PointerWheelChanged({ this, &TermControl::_MouseWheelHandler }); - - // These need to be hooked up to the SwapChainPanel because we don't want the scrollbar to respond to pointer events (GitHub #950) - _swapChainPanel.PointerPressed({ this, &TermControl::_PointerPressedHandler }); - _swapChainPanel.PointerMoved({ this, &TermControl::_PointerMovedHandler }); - _swapChainPanel.PointerReleased({ this, &TermControl::_PointerReleasedHandler }); - - localPointerToThread->EnablePainting(); - - // No matter what order these guys are in, The KeyDown's will fire - // before the CharacterReceived, so we can't easily get characters - // first, then fallback to getting keys from vkeys. - // TODO: This apparently handles keys and characters correctly, though - // I'd keep an eye on it, and test more. - // I presume that the characters that aren't translated by terminalInput - // just end up getting ignored, and the rest of the input comes - // through CharacterReceived. - // I don't believe there's a difference between KeyDown and - // PreviewKeyDown for our purposes - // These two handlers _must_ be on this, not _root. - this->PreviewKeyDown({ this, &TermControl::_KeyDownHandler }); - this->CharacterReceived({ this, &TermControl::_CharacterHandler }); - - auto pfnTitleChanged = std::bind(&TermControl::_TerminalTitleChanged, this, std::placeholders::_1); - _terminal->SetTitleChangedCallback(pfnTitleChanged); - - auto pfnBackgroundColorChanged = std::bind(&TermControl::_BackgroundColorChanged, this, std::placeholders::_1); - _terminal->SetBackgroundCallback(pfnBackgroundColorChanged); - - auto pfnScrollPositionChanged = std::bind(&TermControl::_TerminalScrollPositionChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); - _terminal->SetScrollPositionChangedCallback(pfnScrollPositionChanged); - - static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast(1.0 / 30.0 * 1000000)); - _autoScrollTimer.Interval(AutoScrollUpdateInterval); - _autoScrollTimer.Tick({ get_weak(), &TermControl::_UpdateAutoScroll }); - - // Set up blinking cursor - int blinkTime = GetCaretBlinkTime(); - if (blinkTime != INFINITE) - { - // Create a timer - _cursorTimer = std::make_optional(DispatcherTimer()); - _cursorTimer.value().Interval(std::chrono::milliseconds(blinkTime)); - _cursorTimer.value().Tick({ get_weak(), &TermControl::_BlinkCursor }); - _cursorTimer.value().Start(); - } - else - { - // The user has disabled cursor blinking - _cursorTimer = std::nullopt; - } - - // import value from WinUser (convert from milli-seconds to micro-seconds) - _multiClickTimer = GetDoubleClickTime() * 1000; - - _gotFocusRevoker = this->GotFocus(winrt::auto_revoke, { this, &TermControl::_GotFocusHandler }); - _lostFocusRevoker = this->LostFocus(winrt::auto_revoke, { this, &TermControl::_LostFocusHandler }); - - // Focus the control here. If we do it up above (in _Create_), then the - // focus won't actually get passed to us. I believe this is because - // we're not technically a part of the UI tree yet, so focusing us - // becomes a no-op. - this->Focus(FocusState::Programmatic); - - _connection.Start(); - _initializedTerminal = true; - _InitializedHandlers(*this, nullptr); - return true; - } - - void TermControl::_CharacterHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/, - Input::CharacterReceivedRoutedEventArgs const& e) - { - if (_closing) - { - return; - } - - const auto ch = e.Character(); - - const bool handled = _terminal->SendCharEvent(ch); - e.Handled(handled); - } - - void TermControl::_KeyDownHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/, - Input::KeyRoutedEventArgs const& e) - { - // If the current focused element is a child element of searchbox, - // we do not send this event up to terminal - if (_searchBox && _searchBox->ContainsFocus()) - { - return; - } - - // mark event as handled and do nothing if... - // - closing - // - key modifier is pressed - // NOTE: for key combos like CTRL + C, two events are fired (one for CTRL, one for 'C'). We care about the 'C' event and then check for key modifiers below. - if (_closing || - e.OriginalKey() == VirtualKey::Control || - e.OriginalKey() == VirtualKey::Shift || - e.OriginalKey() == VirtualKey::Menu || - e.OriginalKey() == VirtualKey::LeftWindows || - e.OriginalKey() == VirtualKey::RightWindows) - - { - e.Handled(true); - return; - } - - const auto modifiers = _GetPressedModifierKeys(); - const auto vkey = gsl::narrow_cast(e.OriginalKey()); - const auto scanCode = gsl::narrow_cast(e.KeyStatus().ScanCode); - bool handled = false; - - // GH#2235: Terminal::Settings hasn't been modified to differentiate between AltGr and Ctrl+Alt yet. - // -> Don't check for key bindings if this is an AltGr key combination. - if (!modifiers.IsAltGrPressed()) - { - auto bindings = _settings.KeyBindings(); - if (bindings) - { - handled = bindings.TryKeyChord({ - modifiers.IsCtrlPressed(), - modifiers.IsAltPressed(), - modifiers.IsShiftPressed(), - vkey, - }); - } - } - - if (!handled) - { - handled = _TrySendKeyEvent(vkey, scanCode, modifiers); - } - - // Manually prevent keyboard navigation with tab. We want to send tab to - // the terminal, and we don't want to be able to escape focus of the - // control with tab. - if (e.OriginalKey() == VirtualKey::Tab) - { - handled = true; - } - - e.Handled(handled); - } - - // Method Description: - // - Send this particular key event to the terminal. - // See Terminal::SendKeyEvent for more information. - // - Clears the current selection. - // - Makes the cursor briefly visible during typing. - // Arguments: - // - vkey: The vkey of the key pressed. - // - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states. - bool TermControl::_TrySendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates modifiers) - { - // 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 (_terminal->IsSelectionActive()) - { - _terminal->ClearSelection(); - - 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. - const auto handled = vkey ? _terminal->SendKeyEvent(vkey, scanCode, modifiers) : true; - - if (_cursorTimer.has_value()) - { - // Manually show the cursor when a key is pressed. Restarting - // the timer prevents flickering. - _terminal->SetCursorVisible(true); - _cursorTimer.value().Start(); - } - - return handled; - } - - // Method Description: - // - handle a mouse click event. Begin selection process. - // Arguments: - // - sender: the XAML element responding to the pointer input - // - args: event data - void TermControl::_PointerPressedHandler(Windows::Foundation::IInspectable const& sender, - Input::PointerRoutedEventArgs const& args) - { - _CapturePointer(sender, args); - - const auto ptr = args.Pointer(); - const auto point = args.GetCurrentPoint(_root); - - if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse || ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Pen) - { - // Ignore mouse events while the terminal does not have focus. - // This prevents the user from selecting and copying text if they - // click inside the current tab to refocus the terminal window. - if (!_focused) - { - args.Handled(true); - return; - } - - const auto modifiers = static_cast(args.KeyModifiers()); - // static_cast to a uint32_t because we can't use the WI_IsFlagSet - // macro directly with a VirtualKeyModifiers - const auto altEnabled = WI_IsFlagSet(modifiers, static_cast(VirtualKeyModifiers::Menu)); - const auto shiftEnabled = WI_IsFlagSet(modifiers, static_cast(VirtualKeyModifiers::Shift)); - - if (point.Properties().IsLeftButtonPressed()) - { - const auto cursorPosition = point.Position(); - const auto terminalPosition = _GetTerminalPosition(cursorPosition); - - // handle ALT key - _terminal->SetBoxSelection(altEnabled); - - auto clickCount = _NumberOfClicks(cursorPosition, point.Timestamp()); - - // This formula enables the number of clicks to cycle properly between single-, double-, and triple-click. - // To increase the number of acceptable click states, simply increment MAX_CLICK_COUNT and add another if-statement - const unsigned int MAX_CLICK_COUNT = 3; - const auto multiClickMapper = clickCount > MAX_CLICK_COUNT ? ((clickCount + MAX_CLICK_COUNT - 1) % MAX_CLICK_COUNT) + 1 : clickCount; - - if (multiClickMapper == 3) - { - _terminal->TripleClickSelection(terminalPosition); - } - else if (multiClickMapper == 2) - { - _terminal->DoubleClickSelection(terminalPosition); - } - else - { - if (shiftEnabled && _terminal->IsSelectionActive()) - { - _terminal->SetEndSelectionPosition(terminalPosition); - } - else - { - // save location before rendering - _terminal->SetSelectionAnchor(terminalPosition); - } - - _lastMouseClick = point.Timestamp(); - _lastMouseClickPos = cursorPosition; - } - _renderer->TriggerSelection(); - } - else if (point.Properties().IsRightButtonPressed()) - { - // copyOnSelect causes right-click to always paste - if (_terminal->IsCopyOnSelectActive() || !_terminal->IsSelectionActive()) - { - PasteTextFromClipboard(); - } - else - { - CopySelectionToClipboard(!shiftEnabled); - } - } - } - else if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Touch) - { - const auto contactRect = point.Properties().ContactRect(); - // Set our touch rect, to start a pan. - _touchAnchor = winrt::Windows::Foundation::Point{ contactRect.X, contactRect.Y }; - } - - args.Handled(true); - } - - // Method Description: - // - handle a mouse moved event. Specifically handling mouse drag to update selection process. - // Arguments: - // - sender: not used - // - args: event data - void TermControl::_PointerMovedHandler(Windows::Foundation::IInspectable const& /*sender*/, - Input::PointerRoutedEventArgs const& args) - { - const auto ptr = args.Pointer(); - const auto point = args.GetCurrentPoint(_root); - - if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse || ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Pen) - { - if (point.Properties().IsLeftButtonPressed()) - { - const auto cursorPosition = point.Position(); - _SetEndSelectionPointAtCursor(cursorPosition); - - const double cursorBelowBottomDist = cursorPosition.Y - _swapChainPanel.Margin().Top - _swapChainPanel.ActualHeight(); - const double cursorAboveTopDist = -1 * cursorPosition.Y + _swapChainPanel.Margin().Top; - - constexpr double MinAutoScrollDist = 2.0; // Arbitrary value - double newAutoScrollVelocity = 0.0; - if (cursorBelowBottomDist > MinAutoScrollDist) - { - newAutoScrollVelocity = _GetAutoScrollSpeed(cursorBelowBottomDist); - } - else if (cursorAboveTopDist > MinAutoScrollDist) - { - newAutoScrollVelocity = -1.0 * _GetAutoScrollSpeed(cursorAboveTopDist); - } - - if (newAutoScrollVelocity != 0) - { - _TryStartAutoScroll(point, newAutoScrollVelocity); - } - else - { - _TryStopAutoScroll(ptr.PointerId()); - } - } - } - else if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Touch && _touchAnchor) - { - const auto contactRect = point.Properties().ContactRect(); - winrt::Windows::Foundation::Point newTouchPoint{ contactRect.X, contactRect.Y }; - const auto anchor = _touchAnchor.value(); - - // Get the difference between the point we've dragged to and the start of the touch. - const float fontHeight = float(_actualFont.GetSize().Y); - - const float dy = newTouchPoint.Y - anchor.Y; - - // If we've moved more than one row of text, we'll want to scroll the viewport - if (std::abs(dy) > fontHeight) - { - // Multiply by -1, because moving the touch point down will - // create a positive delta, but we want the viewport to move up, - // so we'll need a negative scroll amount (and the inverse for - // panning down) - const float numRows = -1.0f * (dy / fontHeight); - - const auto currentOffset = this->GetScrollOffset(); - const double newValue = (numRows) + (currentOffset); - - _scrollBar.Value(static_cast(newValue)); - - // Use this point as our new scroll anchor. - _touchAnchor = newTouchPoint; - } - } - args.Handled(true); - } - - // Method Description: - // - Event handler for the PointerReleased event. We use this to de-anchor - // touch events, to stop scrolling via touch. - // Arguments: - // - sender: the XAML element responding to the pointer input - // - args: event data - void TermControl::_PointerReleasedHandler(Windows::Foundation::IInspectable const& sender, - Input::PointerRoutedEventArgs const& args) - { - _ReleasePointerCapture(sender, args); - - const auto ptr = args.Pointer(); - - if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse || ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Pen) - { - const auto modifiers = static_cast(args.KeyModifiers()); - // static_cast to a uint32_t because we can't use the WI_IsFlagSet - // macro directly with a VirtualKeyModifiers - const auto shiftEnabled = WI_IsFlagSet(modifiers, static_cast(VirtualKeyModifiers::Shift)); - - if (_terminal->IsCopyOnSelectActive()) - { - CopySelectionToClipboard(!shiftEnabled); - } - } - else if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Touch) - { - _touchAnchor = std::nullopt; - } - - _TryStopAutoScroll(ptr.PointerId()); - - args.Handled(true); - } - - // Method Description: - // - Event handler for the PointerWheelChanged event. This is raised in - // response to mouse wheel changes. Depending upon what modifier keys are - // pressed, different actions will take place. - // Arguments: - // - args: the event args containing information about t`he mouse wheel event. - void TermControl::_MouseWheelHandler(Windows::Foundation::IInspectable const& /*sender*/, - Input::PointerRoutedEventArgs const& args) - { - const auto point = args.GetCurrentPoint(_root); - const auto delta = point.Properties().MouseWheelDelta(); - - // Get the state of the Ctrl & Shift keys - // static_cast to a uint32_t because we can't use the WI_IsFlagSet macro - // directly with a VirtualKeyModifiers - const auto modifiers = static_cast(args.KeyModifiers()); - const auto ctrlPressed = WI_IsFlagSet(modifiers, static_cast(VirtualKeyModifiers::Control)); - const auto shiftPressed = WI_IsFlagSet(modifiers, static_cast(VirtualKeyModifiers::Shift)); - - if (ctrlPressed && shiftPressed) - { - _MouseTransparencyHandler(delta); - } - else if (ctrlPressed) - { - _MouseZoomHandler(delta); - } - else - { - _MouseScrollHandler(delta, point); - } - } - - // Method Description: - // - Adjust the opacity of the acrylic background in response to a mouse - // scrolling event. - // Arguments: - // - mouseDelta: the mouse wheel delta that triggered this event. - void TermControl::_MouseTransparencyHandler(const double mouseDelta) - { - // Transparency is on a scale of [0.0,1.0], so only increment by .01. - const auto effectiveDelta = mouseDelta < 0 ? -.01 : .01; - - if (_settings.UseAcrylic()) - { - try - { - auto acrylicBrush = _root.Background().as(); - acrylicBrush.TintOpacity(acrylicBrush.TintOpacity() + effectiveDelta); - } - CATCH_LOG(); - } - } - - // Method Description: - // - Adjust the font size of the terminal in response to a mouse scrolling - // event. - // Arguments: - // - mouseDelta: the mouse wheel delta that triggered this event. - void TermControl::_MouseZoomHandler(const double mouseDelta) - { - const auto fontDelta = mouseDelta < 0 ? -1 : 1; - AdjustFontSize(fontDelta); - } - - // Method Description: - // - Reset the font size of the terminal to its default size. - // Arguments: - // - none - void TermControl::ResetFontSize() - { - _SetFontSize(_settings.FontSize()); - } - - // Method Description: - // - Adjust the font size of the terminal control. - // Arguments: - // - fontSizeDelta: The amount to increase or decrease the font size by. - void TermControl::AdjustFontSize(int fontSizeDelta) - { - const auto newSize = _desiredFont.GetEngineSize().Y + fontSizeDelta; - _SetFontSize(newSize); - } - - // Method Description: - // - Scroll the visible viewport in response to a mouse wheel event. - // Arguments: - // - mouseDelta: the mouse wheel delta that triggered this event. - void TermControl::_MouseScrollHandler(const double mouseDelta, Windows::UI::Input::PointerPoint const& pointerPoint) - { - const auto currentOffset = this->GetScrollOffset(); - - // negative = down, positive = up - // However, for us, the signs are flipped. - const auto rowDelta = mouseDelta < 0 ? 1.0 : -1.0; - - // With one of the precision mouses, one click is always a multiple of 120, - // but the "smooth scrolling" mode results in non-int values - - double newValue = (_rowsToScroll * rowDelta) + (currentOffset); - - // The scroll bar's ValueChanged handler will actually move the viewport - // for us. - _scrollBar.Value(static_cast(newValue)); - - if (_terminal->IsSelectionActive() && pointerPoint.Properties().IsLeftButtonPressed()) - { - // If user is mouse selecting and scrolls, they then point at new character. - // Make sure selection reflects that immediately. - _SetEndSelectionPointAtCursor(pointerPoint.Position()); - } - } - - void TermControl::_ScrollbarChangeHandler(Windows::Foundation::IInspectable const& /*sender*/, - Controls::Primitives::RangeBaseValueChangedEventArgs const& args) - { - if (_isTerminalInitiatedScroll) - { - return; - } - - const auto newValue = static_cast(args.NewValue()); - - // This is a scroll event that wasn't initiated by the terminal - // itself - it was initiated by the mouse wheel, or the scrollbar. - _terminal->UserScrollViewport(newValue); - - // We've just told the terminal to update its viewport to reflect the - // new scroll value so the scroll bar matches the viewport now. - _willUpdateScrollBarToMatchViewport.store(false); - } - - // Method Description: - // - captures the pointer so that none of the other XAML elements respond to pointer events - // Arguments: - // - sender: XAML element that is interacting with pointer - // - args: pointer data (i.e.: mouse, touch) - // Return Value: - // - true if we successfully capture the pointer, false otherwise. - bool TermControl::_CapturePointer(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& args) - { - IUIElement uielem; - if (sender.try_as(uielem)) - { - uielem.CapturePointer(args.Pointer()); - return true; - } - return false; - } - - // Method Description: - // - releases the captured pointer because we're done responding to XAML pointer events - // Arguments: - // - sender: XAML element that is interacting with pointer - // - args: pointer data (i.e.: mouse, touch) - // Return Value: - // - true if we release capture of the pointer, false otherwise. - bool TermControl::_ReleasePointerCapture(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& args) - { - IUIElement uielem; - if (sender.try_as(uielem)) - { - uielem.ReleasePointerCapture(args.Pointer()); - return true; - } - return false; - } - - // Method Description: - // - Starts new pointer related auto scroll behavior, or continues existing one. - // Does nothing when there is already auto scroll associated with another pointer. - // Arguments: - // - pointerPoint: info about pointer that causes auto scroll. Pointer's position - // is later used to update selection. - // - scrollVelocity: target velocity of scrolling in characters / sec - void TermControl::_TryStartAutoScroll(Windows::UI::Input::PointerPoint const& pointerPoint, const double scrollVelocity) - { - // Allow only one pointer at the time - if (!_autoScrollingPointerPoint.has_value() || _autoScrollingPointerPoint.value().PointerId() == pointerPoint.PointerId()) - { - _autoScrollingPointerPoint = pointerPoint; - _autoScrollVelocity = scrollVelocity; - - // If this is first time the auto scroll update is about to be called, - // kick-start it by initializing its time delta as if it started now - if (!_lastAutoScrollUpdateTime.has_value()) - { - _lastAutoScrollUpdateTime = std::chrono::high_resolution_clock::now(); - } - - // Apparently this check is not necessary but greatly improves performance - if (!_autoScrollTimer.IsEnabled()) - { - _autoScrollTimer.Start(); - } - } - } - - // Method Description: - // - Stops auto scroll if it's active and is associated with supplied pointer id. - // Arguments: - // - pointerId: id of pointer for which to stop auto scroll - void TermControl::_TryStopAutoScroll(const uint32_t pointerId) - { - if (_autoScrollingPointerPoint.has_value() && pointerId == _autoScrollingPointerPoint.value().PointerId()) - { - _autoScrollingPointerPoint = std::nullopt; - _autoScrollVelocity = 0; - _lastAutoScrollUpdateTime = std::nullopt; - - // Apparently this check is not necessary but greatly improves performance - if (_autoScrollTimer.IsEnabled()) - { - _autoScrollTimer.Stop(); - } - } - } - - // Method Description: - // - Called continuously to gradually scroll viewport when user is - // mouse selecting outside it (to 'follow' the cursor). - // Arguments: - // - none - void TermControl::_UpdateAutoScroll(Windows::Foundation::IInspectable const& /* sender */, - Windows::Foundation::IInspectable const& /* e */) - { - if (_autoScrollVelocity != 0) - { - const auto timeNow = std::chrono::high_resolution_clock::now(); - - if (_lastAutoScrollUpdateTime.has_value()) - { - static constexpr double microSecPerSec = 1000000.0; - const double deltaTime = std::chrono::duration_cast(timeNow - _lastAutoScrollUpdateTime.value()).count() / microSecPerSec; - _scrollBar.Value(_scrollBar.Value() + _autoScrollVelocity * deltaTime); - - if (_autoScrollingPointerPoint.has_value()) - { - _SetEndSelectionPointAtCursor(_autoScrollingPointerPoint.value().Position()); - } - } - - _lastAutoScrollUpdateTime = timeNow; - } - } - - // Method Description: - // - Event handler for the GotFocus event. This is used to... - // - enable accessibility notifications for this TermControl - // - start blinking the cursor when the window is focused - // - update the number of lines to scroll to the value set in the system - void TermControl::_GotFocusHandler(Windows::Foundation::IInspectable const& /* sender */, - RoutedEventArgs const& /* args */) - { - if (_closing) - { - return; - } - _focused = true; - - if (_uiaEngine.get()) - { - THROW_IF_FAILED(_uiaEngine->Enable()); - } - - if (_tsfInputControl != nullptr) - { - _tsfInputControl.NotifyFocusEnter(); - } - - if (_cursorTimer.has_value()) - { - // When the terminal focuses, show the cursor immediately - _terminal->SetCursorVisible(true); - _cursorTimer.value().Start(); - } - _rowsToScroll = _settings.RowsToScroll(); - } - - // Method Description: - // - Event handler for the LostFocus event. This is used to... - // - disable accessibility notifications for this TermControl - // - hide and stop blinking the cursor when the window loses focus. - void TermControl::_LostFocusHandler(Windows::Foundation::IInspectable const& /* sender */, - RoutedEventArgs const& /* args */) - { - if (_closing) - { - return; - } - _focused = false; - - if (_uiaEngine.get()) - { - THROW_IF_FAILED(_uiaEngine->Disable()); - } - - if (_tsfInputControl != nullptr) - { - _tsfInputControl.NotifyFocusLeave(); - } - - if (_cursorTimer.has_value()) - { - _cursorTimer.value().Stop(); - _terminal->SetCursorVisible(false); - } - } - - // Method Description: - // - Writes the given sequence as input to the active terminal connection, - // Arguments: - // - wstr: the string of characters to write to the terminal connection. - // Return Value: - // - - void TermControl::_SendInputToConnection(const std::wstring& wstr) - { - _connection.WriteInput(wstr); - } - - // Method Description: - // - Pre-process text pasted (presumably from the clipboard) - // before sending it over the terminal's connection, converting - // Windows-space \r\n line-endings to \r line-endings - void TermControl::_SendPastedTextToConnection(const std::wstring& wstr) - { - // Some notes on this implementation: - // - // - std::regex can do this in a single line, but is somewhat - // overkill for a simple search/replace operation (and its - // performance guarantees aren't exactly stellar) - // - The STL doesn't have a simple string search/replace method. - // This fact is lamentable. - // - This line-ending conversion is intentionally fairly - // conservative, to avoid stripping out lone \n characters - // where they could conceivably be intentional. - - std::wstring stripped{ wstr }; - - std::wstring::size_type pos = 0; - - while ((pos = stripped.find(L"\r\n", pos)) != std::wstring::npos) - { - stripped.replace(pos, 2, L"\r"); - } - - _connection.WriteInput(stripped); - _terminal->TrySnapOnInput(); - } - - // Method Description: - // - Update the font with the renderer. This will be called either when the - // font changes or the DPI changes, as DPI changes will necessitate a - // font change. This method will *not* change the buffer/viewport size - // to account for the new glyph dimensions. Callers should make sure to - // appropriately call _DoResize after this method is called. - // Arguments: - // - initialUpdate: whether this font update should be considered as being - // concerned with initialization process. Value forwarded to event handler. - void TermControl::_UpdateFont(const bool initialUpdate) - { - auto lock = _terminal->LockForWriting(); - - const int newDpi = static_cast(static_cast(USER_DEFAULT_SCREEN_DPI) * _swapChainPanel.CompositionScaleX()); - - // TODO: MSFT:20895307 If the font doesn't exist, this doesn't - // actually fail. We need a way to gracefully fallback. - _renderer->TriggerFontChange(newDpi, _desiredFont, _actualFont); - - const auto actualNewSize = _actualFont.GetSize(); - _fontSizeChangedHandlers(actualNewSize.X, actualNewSize.Y, initialUpdate); - } - - // Method Description: - // - Set the font size of the terminal control. - // Arguments: - // - fontSize: The size of the font. - void TermControl::_SetFontSize(int fontSize) - { - try - { - // Make sure we have a non-zero font size - const auto newSize = std::max(gsl::narrow(fontSize), static_cast(1)); - const auto fontFace = _settings.FontFace(); - _actualFont = { fontFace, 0, 10, { 0, newSize }, CP_UTF8, false }; - _desiredFont = { _actualFont }; - - // Refresh our font with the renderer - _UpdateFont(); - // Resize the terminal's BUFFER to match the new font size. This does - // NOT change the size of the window, because that can lead to more - // problems (like what happens when you change the font size while the - // window is maximized?) - auto lock = _terminal->LockForWriting(); - _DoResize(_swapChainPanel.ActualWidth(), _swapChainPanel.ActualHeight()); - } - CATCH_LOG(); - } - - // Method Description: - // - Triggered when the swapchain changes size. We use this to resize the - // terminal buffers to match the new visible size. - // Arguments: - // - e: a SizeChangedEventArgs with the new dimensions of the SwapChainPanel - void TermControl::_SwapChainSizeChanged(winrt::Windows::Foundation::IInspectable const& /*sender*/, - SizeChangedEventArgs const& e) - { - if (!_initializedTerminal) - { - return; - } - - auto lock = _terminal->LockForWriting(); - - const auto foundationSize = e.NewSize(); - - _DoResize(foundationSize.Width, foundationSize.Height); - } - - void TermControl::_SwapChainScaleChanged(Windows::UI::Xaml::Controls::SwapChainPanel const& sender, - Windows::Foundation::IInspectable const& /*args*/) - { - const auto scale = sender.CompositionScaleX(); - const auto dpi = (int)(scale * USER_DEFAULT_SCREEN_DPI); - - // TODO: MSFT: 21169071 - Shouldn't this all happen through _renderer and trigger the invalidate automatically on DPI change? - THROW_IF_FAILED(_renderEngine->UpdateDpi(dpi)); - _renderer->TriggerRedrawAll(); - } - - // Method Description: - // - Toggle the cursor on and off when called by the cursor blink timer. - // Arguments: - // - sender: not used - // - e: not used - void TermControl::_BlinkCursor(Windows::Foundation::IInspectable const& /* sender */, - Windows::Foundation::IInspectable const& /* e */) - { - if ((_closing) || (!_terminal->IsCursorBlinkingAllowed() && _terminal->IsCursorVisible())) - { - return; - } - _terminal->SetCursorVisible(!_terminal->IsCursorVisible()); - } - - // Method Description: - // - Sets selection's end position to match supplied cursor position, e.g. while mouse dragging. - // Arguments: - // - cursorPosition: in pixels, relative to the origin of the control - void TermControl::_SetEndSelectionPointAtCursor(Windows::Foundation::Point const& cursorPosition) - { - auto terminalPosition = _GetTerminalPosition(cursorPosition); - - const short lastVisibleRow = std::max(_terminal->GetViewport().Height() - 1, 0); - const short lastVisibleCol = std::max(_terminal->GetViewport().Width() - 1, 0); - - terminalPosition.Y = std::clamp(terminalPosition.Y, 0, lastVisibleRow); - terminalPosition.X = std::clamp(terminalPosition.X, 0, lastVisibleCol); - - // save location (for rendering) + render - _terminal->SetEndSelectionPosition(terminalPosition); - _renderer->TriggerSelection(); - } - - // Method Description: - // - Process a resize event that was initiated by the user. This can either - // be due to the user resizing the window (causing the swapchain to - // resize) or due to the DPI changing (causing us to need to resize the - // buffer to match) - // Arguments: - // - newWidth: the new width of the swapchain, in pixels. - // - newHeight: the new height of the swapchain, in pixels. - void TermControl::_DoResize(const double newWidth, const double newHeight) - { - SIZE size; - size.cx = static_cast(newWidth); - size.cy = static_cast(newHeight); - - // Don't actually resize so small that a single character wouldn't fit - // in either dimension. The buffer really doesn't like being size 0. - if (size.cx < _actualFont.GetSize().X || size.cy < _actualFont.GetSize().Y) - { - return; - } - - // Tell the dx engine that our window is now the new size. - THROW_IF_FAILED(_renderEngine->SetWindowSize(size)); - - // Invalidate everything - _renderer->TriggerRedrawAll(); - - // Convert our new dimensions to characters - const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, - { static_cast(size.cx), static_cast(size.cy) }); - const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels); - - // If this function succeeds with S_FALSE, then the terminal didn't - // actually change size. No need to notify the connection of this - // no-op. - // TODO: MSFT:20642295 Resizing the buffer will corrupt it - // I believe we'll need support for CSI 2J, and additionally I think - // we're resetting the viewport to the top - const HRESULT hr = _terminal->UserResize({ vp.Width(), vp.Height() }); - if (SUCCEEDED(hr) && hr != S_FALSE) - { - _connection.Resize(vp.Height(), vp.Width()); - } - } - - void TermControl::_TerminalTitleChanged(const std::wstring_view& wstr) - { - _titleChangedHandlers(winrt::hstring{ wstr }); - } - - // Method Description: - // - Update the position and size of the scrollbar to match the given - // viewport top, viewport height, and buffer size. - // The change will be actually handled in _ScrollbarChangeHandler. - // This should be done on the UI thread. Make sure the caller is calling - // us in a RunAsync block. - // Arguments: - // - viewTop: the top of the visible viewport, in rows. 0 indicates the top - // of the buffer. - // - viewHeight: the height of the viewport in rows. - // - bufferSize: the length of the buffer, in rows - void TermControl::_ScrollbarUpdater(Controls::Primitives::ScrollBar scrollBar, - const int viewTop, - const int viewHeight, - const int bufferSize) - { - // The terminal is already in the scroll position it wants, so no need - // to tell it to scroll. - _isTerminalInitiatedScroll = true; - - const auto hiddenContent = bufferSize - viewHeight; - scrollBar.Maximum(hiddenContent); - scrollBar.Minimum(0); - scrollBar.ViewportSize(viewHeight); - scrollBar.Value(viewTop); - - _isTerminalInitiatedScroll = false; - } - - // Method Description: - // - Update the position and size of the scrollbar to match the given - // viewport top, viewport height, and buffer size. - // Additionally fires a ScrollPositionChanged event for anyone who's - // registered an event handler for us. - // Arguments: - // - viewTop: the top of the visible viewport, in rows. 0 indicates the top - // of the buffer. - // - viewHeight: the height of the viewport in rows. - // - bufferSize: the length of the buffer, in rows - winrt::fire_and_forget TermControl::_TerminalScrollPositionChanged(const int viewTop, - const int viewHeight, - const int bufferSize) - { - // Since this callback fires from non-UI thread, we might be already - // closed/closing. - if (_closing.load()) - { - return; - } - - _scrollPositionChangedHandlers(viewTop, viewHeight, bufferSize); - - auto weakThis{ get_weak() }; - - co_await winrt::resume_foreground(_scrollBar.Dispatcher()); - - // Even if we weren't closed/closing few lines above, we might be - // while waiting for this block of code to be dispatched. - // If 'weakThis' is locked, then we can safely work with 'this' - if (auto control{ weakThis.get() }) - { - if (!_closing.load()) - { - // Update our scrollbar - _ScrollbarUpdater(_scrollBar, viewTop, viewHeight, bufferSize); - } - } - } - - hstring TermControl::Title() - { - if (!_initializedTerminal) - return L""; - - hstring hstr(_terminal->GetConsoleTitle()); - return hstr; - } - - // Method Description: - // - Given a copy-able selection, get the selected text from the buffer and send it to the - // Windows Clipboard (CascadiaWin32:main.cpp). - // - CopyOnSelect does NOT clear the selection - // Arguments: - // - trimTrailingWhitespace: enable removing any whitespace from copied selection - // and get text to appear on separate lines. - bool TermControl::CopySelectionToClipboard(bool trimTrailingWhitespace) - { - // no selection --> nothing to copy - if (_terminal == nullptr || !_terminal->IsSelectionActive()) - { - return false; - } - // extract text from buffer - const auto bufferData = _terminal->RetrieveSelectedTextFromBuffer(trimTrailingWhitespace); - - // convert text: vector --> string - std::wstring textData; - for (const auto& text : bufferData.text) - { - textData += text; - } - - // convert text to HTML format - const auto htmlData = TextBuffer::GenHTML(bufferData, - _actualFont.GetUnscaledSize().Y, - _actualFont.GetFaceName(), - _settings.DefaultBackground(), - "Windows Terminal"); - - // convert to RTF format - const auto rtfData = TextBuffer::GenRTF(bufferData, - _actualFont.GetUnscaledSize().Y, - _actualFont.GetFaceName(), - _settings.DefaultBackground()); - - if (!_terminal->IsCopyOnSelectActive()) - { - _terminal->ClearSelection(); - } - - // send data up for clipboard - auto copyArgs = winrt::make_self(winrt::hstring(textData), - winrt::to_hstring(htmlData), - winrt::to_hstring(rtfData)); - _clipboardCopyHandlers(*this, *copyArgs); - return true; - } - - // Method Description: - // - Initiate a paste operation. - void TermControl::PasteTextFromClipboard() - { - // attach TermControl::_SendInputToConnection() as the clipboardDataHandler. - // This is called when the clipboard data is loaded. - auto clipboardDataHandler = std::bind(&TermControl::_SendPastedTextToConnection, this, std::placeholders::_1); - auto pasteArgs = winrt::make_self(clipboardDataHandler); - - // send paste event up to TermApp - _clipboardPasteHandlers(*this, *pasteArgs); - } - - void TermControl::Close() - { - if (!_closing.exchange(true)) - { - // Stop accepting new output and state changes before we disconnect everything. - _connection.TerminalOutput(_connectionOutputEventToken); - _connectionStateChangedRevoker.revoke(); - - _tsfInputControl.Close(); // Disconnect the TSF input control so it doesn't receive EditContext events. - - if (auto localConnection{ std::exchange(_connection, nullptr) }) - { - localConnection.Close(); - // connection is destroyed. - } - - if (auto localRenderEngine{ std::exchange(_renderEngine, nullptr) }) - { - if (auto localRenderer{ std::exchange(_renderer, nullptr) }) - { - localRenderer->TriggerTeardown(); - // renderer is destroyed - } - // renderEngine is destroyed - } - - if (auto localTerminal{ std::exchange(_terminal, nullptr) }) - { - _initializedTerminal = false; - // terminal is destroyed. - } - } - } - - // Method Description: - // - Scrolls the viewport of the terminal and updates the scroll bar accordingly - // Arguments: - // - viewTop: the viewTop to scroll to - void TermControl::ScrollViewport(int viewTop) - { - _scrollBar.Value(viewTop); - } - - int TermControl::GetScrollOffset() - { - return _terminal->GetScrollOffset(); - } - - // Function Description: - // - Gets the height of the terminal in lines of text - // Return Value: - // - The height of the terminal in lines of text - int TermControl::GetViewHeight() const - { - const auto viewPort = _terminal->GetViewport(); - return viewPort.Height(); - } - - // Function Description: - // - Determines how much space (in pixels) an app would need to reserve to - // create a control with the settings stored in the settings param. This - // accounts for things like the font size and face, the initialRows and - // initialCols, and scrollbar visibility. The returned sized is based upon - // the provided DPI value - // Arguments: - // - settings: A IControlSettings with the settings to get the pixel size of. - // - dpi: The DPI we should create the terminal at. This affects things such - // as font size, scrollbar and other control scaling, etc. Make sure the - // caller knows what monitor the control is about to appear on. - // Return Value: - // - a point containing the requested dimensions in pixels. - winrt::Windows::Foundation::Point TermControl::GetProposedDimensions(IControlSettings const& settings, const uint32_t dpi) - { - // Initialize our font information. - const auto fontFace = settings.FontFace(); - const short fontHeight = gsl::narrow(settings.FontSize()); - // The font width doesn't terribly matter, we'll only be using the - // height to look it up - // The other params here also largely don't matter. - // The family is only used to determine if the font is truetype or - // not, but DX doesn't use that info at all. - // The Codepage is additionally not actually used by the DX engine at all. - FontInfo actualFont = { fontFace, 0, 10, { 0, fontHeight }, CP_UTF8, false }; - FontInfoDesired desiredFont = { actualFont }; - - // If the settings have negative or zero row or column counts, ignore those counts. - // (The lower TerminalCore layer also has upper bounds as well, but at this layer - // we may eventually impose different ones depending on how many pixels we can address.) - const auto cols = std::max(settings.InitialCols(), 1); - const auto rows = std::max(settings.InitialRows(), 1); - - // Create a DX engine and initialize it with our font and DPI. We'll - // then use it to measure how much space the requested rows and columns - // will take up. - // TODO: MSFT:21254947 - use a static function to do this instead of - // instantiating a DxEngine - auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>(); - THROW_IF_FAILED(dxEngine->UpdateDpi(dpi)); - THROW_IF_FAILED(dxEngine->UpdateFont(desiredFont, actualFont)); - - const float scale = dxEngine->GetScaling(); - const auto fontSize = actualFont.GetSize(); - - // Manually multiply by the scaling factor. The DX engine doesn't - // actually store the scaled font size in the fontInfo.GetSize() - // property when the DX engine is in Composition mode (which it is for - // the Terminal). At runtime, this is fine, as we'll transform - // everything by our scaling, so it'll work out. However, right now we - // need to get the exact pixel count. - const float fFontWidth = gsl::narrow(fontSize.X * scale); - const float fFontHeight = gsl::narrow(fontSize.Y * scale); - - // UWP XAML scrollbars aren't guaranteed to be the same size as the - // ComCtl scrollbars, but it's certainly close enough. - const auto scrollbarSize = GetSystemMetricsForDpi(SM_CXVSCROLL, dpi); - - double width = cols * fFontWidth; - - // Reserve additional space if scrollbar is intended to be visible - if (settings.ScrollState() == ScrollbarState::Visible) - { - width += scrollbarSize; - } - - double height = rows * fFontHeight; - auto thickness = _ParseThicknessFromPadding(settings.Padding()); - width += thickness.Left + thickness.Right; - height += thickness.Top + thickness.Bottom; - - return { gsl::narrow_cast(width), gsl::narrow_cast(height) }; - } - - // Method Description: - // - Get the size of a single character of this control. The size is in - // DIPs. If you need it in _pixels_, you'll need to multiply by the - // current display scaling. - // Arguments: - // - - // Return Value: - // - The dimensions of a single character of this control, in DIPs - winrt::Windows::Foundation::Size TermControl::CharacterDimensions() const - { - const auto fontSize = _actualFont.GetSize(); - return { gsl::narrow_cast(fontSize.X), gsl::narrow_cast(fontSize.Y) }; - } - - // Method Description: - // - Get the absolute minimum size that this control can be resized to and - // still have 1x1 character visible. This includes the space needed for - // the scrollbar and the padding. - // Arguments: - // - - // Return Value: - // - The minimum size that this terminal control can be resized to and still - // have a visible character. - winrt::Windows::Foundation::Size TermControl::MinimumSize() const - { - const auto fontSize = _actualFont.GetSize(); - double width = fontSize.X; - double height = fontSize.Y; - // Reserve additional space if scrollbar is intended to be visible - if (_settings.ScrollState() == ScrollbarState::Visible) - { - width += _scrollBar.ActualWidth(); - } - - // Account for the size of any padding - const auto padding = _swapChainPanel.Margin(); - width += padding.Left + padding.Right; - height += padding.Top + padding.Bottom; - - return { gsl::narrow_cast(width), gsl::narrow_cast(height) }; - } - - // Method Description: - // - Adjusts given dimension (width or height) so that it aligns to the character grid. - // The snap is always downward. - // Arguments: - // - widthOrHeight: if true operates on width, otherwise on height - // - dimension: a dimension (width or height) to be snapped - // Return Value: - // - A dimension that would be aligned to the character grid. - float TermControl::SnapDimensionToGrid(const bool widthOrHeight, const float dimension) const - { - const auto fontSize = _actualFont.GetSize(); - const auto fontDimension = widthOrHeight ? fontSize.X : fontSize.Y; - - const auto padding = _swapChainPanel.Margin(); - auto nonTerminalArea = gsl::narrow_cast(widthOrHeight ? - padding.Left + padding.Right : - padding.Top + padding.Bottom); - - if (widthOrHeight && _settings.ScrollState() == ScrollbarState::Visible) - { - nonTerminalArea += gsl::narrow_cast(_scrollBar.ActualWidth()); - } - - const auto gridSize = dimension - nonTerminalArea; - const int cells = static_cast(gridSize / fontDimension); - return cells * fontDimension + nonTerminalArea; - } - - // Method Description: - // - Create XAML Thickness object based on padding props provided. - // Used for controlling the TermControl XAML Grid container's Padding prop. - // Arguments: - // - padding: 2D padding values - // Single Double value provides uniform padding - // Two Double values provide isometric horizontal & vertical padding - // Four Double values provide independent padding for 4 sides of the bounding rectangle - // Return Value: - // - Windows::UI::Xaml::Thickness object - Windows::UI::Xaml::Thickness TermControl::_ParseThicknessFromPadding(const hstring padding) - { - const wchar_t singleCharDelim = L','; - std::wstringstream tokenStream(padding.c_str()); - std::wstring token; - uint8_t paddingPropIndex = 0; - std::array thicknessArr = {}; - size_t* idx = nullptr; - - // Get padding values till we run out of delimiter separated values in the stream - // or we hit max number of allowable values (= 4) for the bounding rectangle - // Non-numeral values detected will default to 0 - // std::getline will not throw exception unless flags are set on the wstringstream - // std::stod will throw invalid_argument exception if the input is an invalid double value - // std::stod will throw out_of_range exception if the input value is more than DBL_MAX - try - { - for (; std::getline(tokenStream, token, singleCharDelim) && (paddingPropIndex < thicknessArr.size()); paddingPropIndex++) - { - // std::stod internally calls wcstod which handles whitespace prefix (which is ignored) - // & stops the scan when first char outside the range of radix is encountered - // We'll be permissive till the extent that stod function allows us to be by default - // Ex. a value like 100.3#535w2 will be read as 100.3, but ;df25 will fail - thicknessArr[paddingPropIndex] = std::stod(token, idx); - } - } - catch (...) - { - // If something goes wrong, even if due to a single bad padding value, we'll reset the index & return default 0 padding - paddingPropIndex = 0; - LOG_CAUGHT_EXCEPTION(); - } - - switch (paddingPropIndex) - { - case 1: - return ThicknessHelper::FromUniformLength(thicknessArr[0]); - case 2: - return ThicknessHelper::FromLengths(thicknessArr[0], thicknessArr[1], thicknessArr[0], thicknessArr[1]); - // No case for paddingPropIndex = 3, since it's not a norm to provide just Left, Top & Right padding values leaving out Bottom - case 4: - return ThicknessHelper::FromLengths(thicknessArr[0], thicknessArr[1], thicknessArr[2], thicknessArr[3]); - default: - return Thickness(); - } - } - - // Method Description: - // - Get the modifier keys that are currently pressed. This can be used to - // find out which modifiers (ctrl, alt, shift) are pressed in events that - // don't necessarily include that state. - // Return Value: - // - The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states. - ControlKeyStates TermControl::_GetPressedModifierKeys() const - { - CoreWindow window = CoreWindow::GetForCurrentThread(); - // DONT USE - // != CoreVirtualKeyStates::None - // OR - // == CoreVirtualKeyStates::Down - // Sometimes with the key down, the state is Down | Locked. - // Sometimes with the key up, the state is Locked. - // IsFlagSet(Down) is the only correct solution. - - struct KeyModifier - { - VirtualKey vkey; - ControlKeyStates flags; - }; - - constexpr std::array modifiers{ { - { VirtualKey::RightMenu, ControlKeyStates::RightAltPressed }, - { VirtualKey::LeftMenu, ControlKeyStates::LeftAltPressed }, - { VirtualKey::RightControl, ControlKeyStates::RightCtrlPressed }, - { VirtualKey::LeftControl, ControlKeyStates::LeftCtrlPressed }, - { VirtualKey::Shift, ControlKeyStates::ShiftPressed }, - } }; - - ControlKeyStates flags; - - for (const auto& mod : modifiers) - { - const auto state = window.GetKeyState(mod.vkey); - const auto isDown = WI_IsFlagSet(state, CoreVirtualKeyStates::Down); - - if (isDown) - { - flags |= mod.flags; - } - } - - return flags; - } - - // Method Description: - // - Gets the corresponding viewport terminal position for the cursor - // by excluding the padding and normalizing with the font size. - // This is used for selection. - // Arguments: - // - cursorPosition: the (x,y) position of a given cursor (i.e.: mouse cursor). - // NOTE: origin (0,0) is top-left. - // Return Value: - // - the corresponding viewport terminal position for the given Point parameter - const COORD TermControl::_GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition) - { - // Exclude padding from cursor position calculation - COORD terminalPosition = { - static_cast(cursorPosition.X - _swapChainPanel.Margin().Left), - static_cast(cursorPosition.Y - _swapChainPanel.Margin().Top) - }; - - const auto fontSize = _actualFont.GetSize(); - FAIL_FAST_IF(fontSize.X == 0); - FAIL_FAST_IF(fontSize.Y == 0); - - // Normalize to terminal coordinates by using font size - terminalPosition.X /= fontSize.X; - terminalPosition.Y /= fontSize.Y; - - return terminalPosition; - } - - // Method Description: - // - Composition Completion handler for the TSFInputControl that - // handles writing text out to TerminalConnection - // Arguments: - // - text: the text to write to TerminalConnection - // Return Value: - // - - void TermControl::_CompositionCompleted(winrt::hstring text) - { - _connection.WriteInput(text); - } - - // Method Description: - // - CurrentCursorPosition handler for the TSFInputControl that - // handles returning current cursor position. - // Arguments: - // - eventArgs: event for storing the current cursor position - // Return Value: - // - - void TermControl::_CurrentCursorPositionHandler(const IInspectable& /*sender*/, const CursorPositionEventArgs& eventArgs) - { - const COORD cursorPos = _terminal->GetCursorPosition(); - Windows::Foundation::Point p = { gsl::narrow(cursorPos.X), gsl::narrow(cursorPos.Y) }; - eventArgs.CurrentPosition(p); - } - - // Method Description: - // - FontInfo handler for the TSFInputControl that - // handles returning current font information - // Arguments: - // - eventArgs: event for storing the current font information - // Return Value: - // - - void TermControl::_FontInfoHandler(const IInspectable& /*sender*/, const FontInfoEventArgs& eventArgs) - { - eventArgs.FontSize(CharacterDimensions()); - eventArgs.FontFace(_actualFont.GetFaceName()); - } - - // Method Description: - // - Returns the number of clicks that occurred (double and triple click support) - // Arguments: - // - clickPos: the (x,y) position of a given cursor (i.e.: mouse cursor). - // NOTE: origin (0,0) is top-left. - // - clickTime: the timestamp that the click occurred - // Return Value: - // - if the click is in the same position as the last click and within the timeout, the number of clicks within that time window - // - otherwise, 1 - const unsigned int TermControl::_NumberOfClicks(winrt::Windows::Foundation::Point clickPos, Timestamp clickTime) - { - // if click occurred at a different location or past the multiClickTimer... - Timestamp delta; - THROW_IF_FAILED(UInt64Sub(clickTime, _lastMouseClick, &delta)); - if (clickPos != _lastMouseClickPos || delta > _multiClickTimer) - { - // exit early. This is a single click. - _multiClickCounter = 1; - } - else - { - _multiClickCounter++; - } - return _multiClickCounter; - } - - // Method Description: - // - Calculates speed of single axis of auto scrolling. It has to allow for both - // fast and precise selection. - // Arguments: - // - cursorDistanceFromBorder: distance from viewport border to cursor, in pixels. Must be non-negative. - // Return Value: - // - positive speed in characters / sec - double TermControl::_GetAutoScrollSpeed(double cursorDistanceFromBorder) const - { - // The numbers below just feel well, feel free to change. - // TODO: Maybe account for space beyond border that user has available - return std::pow(cursorDistanceFromBorder, 2.0) / 25.0 + 2.0; - } - - // Method Description: - // - Async handler for the "Drop" event. If a file was dropped onto our - // root, we'll try to get the path of the file dropped onto us, and write - // the full path of the file to our terminal connection. Like conhost, if - // the path contains a space, we'll wrap the path in quotes. - // - Unlike conhost, if multiple files are dropped onto the terminal, we'll - // write all the paths to the terminal, separated by spaces. - // Arguments: - // - e: The DragEventArgs from the Drop event - // Return Value: - // - - winrt::fire_and_forget TermControl::_DoDragDrop(DragEventArgs const e) - { - if (e.DataView().Contains(StandardDataFormats::StorageItems())) - { - auto items = co_await e.DataView().GetStorageItemsAsync(); - if (items.Size() > 0) - { - std::wstring allPaths; - for (auto item : items) - { - // Join the paths with spaces - if (!allPaths.empty()) - { - allPaths += L" "; - } - - std::wstring fullPath{ item.Path() }; - const auto containsSpaces = std::find(fullPath.begin(), - fullPath.end(), - L' ') != fullPath.end(); - - auto lock = _terminal->LockForWriting(); - - if (containsSpaces) - { - fullPath.insert(0, L"\""); - fullPath += L"\""; - } - - allPaths += fullPath; - } - _SendInputToConnection(allPaths); - } - } - } - - // Method Description: - // - Synchronous handler for the "Drop" event. We'll dispatch the async - // _DoDragDrop method to handle this, because getting information about - // the file that was potentially dropped onto us must be done off the UI - // thread. - // Arguments: - // - e: The DragEventArgs from the Drop event - // Return Value: - // - - void TermControl::_DragDropHandler(Windows::Foundation::IInspectable const& /*sender*/, - DragEventArgs const& e) - { - // Dispatch an async method to handle the drop event. - _DoDragDrop(e); - } - - // Method Description: - // - Handle the DragOver event. We'll signal that the drag operation we - // support is the "copy" operation, and we'll also customize the - // appearance of the drag-drop UI, by removing the preview and setting a - // custom caption. For more information, see - // https://docs.microsoft.com/en-us/windows/uwp/design/input/drag-and-drop#customize-the-ui - // Arguments: - // - e: The DragEventArgs from the DragOver event - // Return Value: - // - - void TermControl::_DragOverHandler(Windows::Foundation::IInspectable const& /*sender*/, - DragEventArgs const& e) - { - // Make sure to set the AcceptedOperation, so that we can later receive the path in the Drop event - e.AcceptedOperation(winrt::Windows::ApplicationModel::DataTransfer::DataPackageOperation::Copy); - - // Sets custom UI text - e.DragUIOverride().Caption(RS_(L"DragFileCaption")); - // Sets if the caption is visible - e.DragUIOverride().IsCaptionVisible(true); - // Sets if the dragged content is visible - e.DragUIOverride().IsContentVisible(false); - // Sets if the glyph is visibile - e.DragUIOverride().IsGlyphVisible(false); - } - - // -------------------------------- WinRT Events --------------------------------- - // Winrt events need a method for adding a callback to the event and removing the callback. - // These macros will define them both for you. - DEFINE_EVENT(TermControl, TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs); - DEFINE_EVENT(TermControl, FontSizeChanged, _fontSizeChangedHandlers, TerminalControl::FontSizeChangedEventArgs); - DEFINE_EVENT(TermControl, ScrollPositionChanged, _scrollPositionChangedHandlers, TerminalControl::ScrollPositionChangedEventArgs); - - DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TermControl, PasteFromClipboard, _clipboardPasteHandlers, TerminalControl::TermControl, TerminalControl::PasteFromClipboardEventArgs); - DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TermControl, CopyToClipboard, _clipboardCopyHandlers, TerminalControl::TermControl, TerminalControl::CopyToClipboardEventArgs); - // clang-format on -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "TermControl.h" +#include +#include +#include +#include +#include +#include +#include +#include "..\..\types\inc\GlyphWidth.hpp" + +#include "TermControl.g.cpp" +#include "TermControlAutomationPeer.h" + +using namespace ::Microsoft::Console::Types; +using namespace ::Microsoft::Terminal::Core; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Xaml::Automation::Peers; +using namespace winrt::Windows::UI::Core; +using namespace winrt::Windows::System; +using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Windows::ApplicationModel::DataTransfer; + +namespace winrt::Microsoft::Terminal::TerminalControl::implementation +{ + // Helper static function to ensure that all ambiguous-width glyphs are reported as narrow. + // See microsoft/terminal#2066 for more info. + static bool _IsGlyphWideForceNarrowFallback(const std::wstring_view /* glyph */) + { + return false; // glyph is not wide. + } + + static bool _EnsureStaticInitialization() + { + // use C++11 magic statics to make sure we only do this once. + static bool initialized = []() { + // *** THIS IS A SINGLETON *** + SetGlyphWidthFallback(_IsGlyphWideForceNarrowFallback); + + return true; + }(); + return initialized; + } + + TermControl::TermControl() : + TermControl(Settings::TerminalSettings{}, TerminalConnection::ITerminalConnection{ nullptr }) + { + } + + TermControl::TermControl(Settings::IControlSettings settings, TerminalConnection::ITerminalConnection connection) : + _connection{ connection }, + _initializedTerminal{ false }, + _root{ nullptr }, + _swapChainPanel{ nullptr }, + _settings{ settings }, + _closing{ false }, + _isTerminalInitiatedScroll{ false }, + _autoScrollVelocity{ 0 }, + _autoScrollingPointerPoint{ std::nullopt }, + _autoScrollTimer{}, + _lastAutoScrollUpdateTime{ std::nullopt }, + _desiredFont{ DEFAULT_FONT_FACE, 0, 10, { 0, DEFAULT_FONT_SIZE }, CP_UTF8 }, + _actualFont{ DEFAULT_FONT_FACE, 0, 10, { 0, DEFAULT_FONT_SIZE }, CP_UTF8, false }, + _touchAnchor{ std::nullopt }, + _cursorTimer{}, + _lastMouseClick{}, + _lastMouseClickPos{}, + _searchBox{ nullptr }, + _tsfInputControl{ nullptr } + { + _EnsureStaticInitialization(); + _Create(); + } + + void TermControl::_Create() + { + Controls::Grid container; + + Controls::ColumnDefinition contentColumn{}; + Controls::ColumnDefinition scrollbarColumn{}; + contentColumn.Width(GridLength{ 1.0, GridUnitType::Star }); + scrollbarColumn.Width(GridLength{ 1.0, GridUnitType::Auto }); + + container.ColumnDefinitions().Append(contentColumn); + container.ColumnDefinitions().Append(scrollbarColumn); + + _scrollBar = Controls::Primitives::ScrollBar{}; + _scrollBar.Orientation(Controls::Orientation::Vertical); + _scrollBar.IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::MouseIndicator); + _scrollBar.HorizontalAlignment(HorizontalAlignment::Right); + _scrollBar.VerticalAlignment(VerticalAlignment::Stretch); + + // Initialize the scrollbar with some placeholder values. + // The scrollbar will be updated with real values on _Initialize + _scrollBar.Maximum(1); + _scrollBar.ViewportSize(10); + _scrollBar.IsTabStop(false); + _scrollBar.SmallChange(1); + _scrollBar.LargeChange(4); + _scrollBar.Visibility(Visibility::Visible); + + _tsfInputControl = TSFInputControl(); + _tsfInputControl.CompositionCompleted({ this, &TermControl::_CompositionCompleted }); + _tsfInputControl.CurrentCursorPosition({ this, &TermControl::_CurrentCursorPositionHandler }); + _tsfInputControl.CurrentFontInfo({ this, &TermControl::_FontInfoHandler }); + container.Children().Append(_tsfInputControl); + + // Create the SwapChainPanel that will display our content + Controls::SwapChainPanel swapChainPanel; + + _sizeChangedRevoker = swapChainPanel.SizeChanged(winrt::auto_revoke, { this, &TermControl::_SwapChainSizeChanged }); + _compositionScaleChangedRevoker = swapChainPanel.CompositionScaleChanged(winrt::auto_revoke, { this, &TermControl::_SwapChainScaleChanged }); + + // Initialize the terminal only once the swapchainpanel is loaded - that + // way, we'll be able to query the real pixel size it got on layout + _layoutUpdatedRevoker = swapChainPanel.LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) { + // This event fires every time the layout changes, but it is always the last one to fire + // in any layout change chain. That gives us great flexibility in finding the right point + // at which to initialize our renderer (and our terminal). + // Any earlier than the last layout update and we may not know the terminal's starting size. + + if (_InitializeTerminal()) + { + // Only let this succeed once. + _layoutUpdatedRevoker.revoke(); + } + }); + + container.Children().Append(swapChainPanel); + container.Children().Append(_scrollBar); + Controls::Grid::SetColumn(swapChainPanel, 0); + Controls::Grid::SetColumn(_scrollBar, 1); + + Controls::Grid root{}; + Controls::Image bgImageLayer{}; + root.Children().Append(bgImageLayer); + root.Children().Append(container); + + _root = root; + _bgImageLayer = bgImageLayer; + + _swapChainPanel = swapChainPanel; + this->Content(_root); + + _ApplyUISettings(); + + // These are important: + // 1. When we get tapped, focus us + _tappedRevoker = this->Tapped(winrt::auto_revoke, [this](auto&, auto& e) { + Focus(FocusState::Pointer); + e.Handled(true); + }); + // 2. Make sure we can be focused (why this isn't `Focusable` I'll never know) + this->IsTabStop(true); + // 3. Actually not sure about this one. Maybe it isn't necessary either. + this->AllowFocusOnInteraction(true); + + // DON'T CALL _InitializeTerminal here - wait until the swap chain is loaded to do that. + + // Subscribe to the connection's disconnected event and call our connection closed handlers. + _connectionStateChangedRevoker = _connection.StateChanged(winrt::auto_revoke, [this](auto&& /*s*/, auto&& /*v*/) { + _ConnectionStateChangedHandlers(*this, nullptr); + }); + + _root.AllowDrop(true); + _root.Drop({ get_weak(), &TermControl::_DragDropHandler }); + _root.DragOver({ get_weak(), &TermControl::_DragOverHandler }); + } + + // Method Description: + // - Create the SearchBoxControl object, and attach it + // to the Terminal Control root + // Arguments: + // - + // Return Value: + // - + void TermControl::CreateSearchBoxControl() + { + if (!_searchBox) + { + _searchBox = winrt::make_self(); + _searchBox->HorizontalAlignment(HorizontalAlignment::Right); + _searchBox->VerticalAlignment(VerticalAlignment::Top); + // We need to make sure the searchbox does not overlap + // with the scroll bar + Thickness searchBoxPadding = { 0, 0, _scrollBar.ActualWidth(), 0 }; + _searchBox->Margin(searchBoxPadding); + + _root.Children().Append(*_searchBox); + + // Event handlers + _searchBox->Search({ get_weak(), &TermControl::_Search }); + _searchBox->Closed({ get_weak(), &TermControl::_CloseSearchBoxControl }); + } + + _searchBox->SetFocusOnTextbox(); + } + + // Method Description: + // - Search text in text buffer. This is triggered if the user click + // search button or press enter. + // Arguments: + // - text: the text to search + // - goForward: boolean that represents if the current search direction is forward + // - caseSensitive: boolean that represents if the current search is case sensitive + // Return Value: + // - + void TermControl::_Search(const winrt::hstring& text, const bool goForward, const bool caseSensitive) + { + if (text.size() == 0) + { + return; + } + + const Search::Direction direction = goForward ? + Search::Direction::Forward : + Search::Direction::Backward; + + const Search::Sensitivity sensitivity = caseSensitive ? + Search::Sensitivity::CaseSensitive : + Search::Sensitivity::CaseInsensitive; + + Search search(*GetUiaData(), text.c_str(), direction, sensitivity); + auto lock = _terminal->LockForWriting(); + if (search.FindNext()) + { + _terminal->SetBoxSelection(false); + search.Select(); + _renderer->TriggerSelection(); + } + } + + // Method Description: + // - The handler for the close button or pressing "Esc" when focusing on the + // search dialog. + // This removes the SearchBoxControl object from the XAML tree, + // reset smart pointer and set focus back to Terminal + // Arguments: + // - IInspectable: not used + // - RoutedEventArgs: not used + // Return Value: + // - + void TermControl::_CloseSearchBoxControl(const winrt::Windows::Foundation::IInspectable& /*sender*/, RoutedEventArgs const& /*args*/) + { + unsigned int idx; + _root.Children().IndexOf(*_searchBox, idx); + _root.Children().RemoveAt(idx); + + _searchBox = nullptr; + + // Set focus back to terminal control + this->Focus(FocusState::Programmatic); + } + + // Method Description: + // - Given new settings for this profile, applies the settings to the current terminal. + // Arguments: + // - newSettings: New settings values for the profile in this terminal. + // Return Value: + // - + winrt::fire_and_forget TermControl::UpdateSettings(Settings::IControlSettings newSettings) + { + _settings = newSettings; + auto weakThis{ get_weak() }; + + // Dispatch a call to the UI thread to apply the new settings to the + // terminal. + co_await winrt::resume_foreground(_root.Dispatcher()); + + // If 'weakThis' is locked, then we can safely work with 'this' + if (auto control{ weakThis.get() }) + { + // Update our control settings + _ApplyUISettings(); + + // Update DxEngine's SelectionBackground + _renderEngine->SetSelectionBackground(_settings.SelectionBackground()); + + // Update the terminal core with its new Core settings + _terminal->UpdateSettings(_settings); + + // Refresh our font with the renderer + _UpdateFont(); + + const auto width = _swapChainPanel.ActualWidth(); + const auto height = _swapChainPanel.ActualHeight(); + if (width != 0 && height != 0) + { + // If the font size changed, or the _swapchainPanel's size changed + // for any reason, we'll need to make sure to also resize the + // buffer. _DoResize will invalidate everything for us. + auto lock = _terminal->LockForWriting(); + _DoResize(width, height); + } + + // set TSF Foreground + Media::SolidColorBrush foregroundBrush{}; + foregroundBrush.Color(ColorRefToColor(_settings.DefaultForeground())); + _tsfInputControl.Foreground(foregroundBrush); + } + } + + // Method Description: + // - Style our UI elements based on the values in our _settings, and set up + // other control-specific settings. This method will be called whenever + // the settings are reloaded. + // * Calls _InitializeBackgroundBrush to set up the Xaml brush responsible + // for the control's background + // * Calls _BackgroundColorChanged to style the background of the control + // - Core settings will be passed to the terminal in _InitializeTerminal + // Arguments: + // - + // Return Value: + // - + void TermControl::_ApplyUISettings() + { + _InitializeBackgroundBrush(); + + uint32_t bg = _settings.DefaultBackground(); + _BackgroundColorChanged(bg); + + // Apply padding as swapChainPanel's margin + auto newMargin = _ParseThicknessFromPadding(_settings.Padding()); + auto existingMargin = _swapChainPanel.Margin(); + _swapChainPanel.Margin(newMargin); + + // Initialize our font information. + const auto fontFace = _settings.FontFace(); + const short fontHeight = gsl::narrow(_settings.FontSize()); + // The font width doesn't terribly matter, we'll only be using the + // height to look it up + // The other params here also largely don't matter. + // The family is only used to determine if the font is truetype or + // not, but DX doesn't use that info at all. + // The Codepage is additionally not actually used by the DX engine at all. + _actualFont = { fontFace, 0, 10, { 0, fontHeight }, CP_UTF8, false }; + _desiredFont = { _actualFont }; + + // set TSF Foreground + Media::SolidColorBrush foregroundBrush{}; + foregroundBrush.Color(ColorRefToColor(_settings.DefaultForeground())); + _tsfInputControl.Foreground(foregroundBrush); + _tsfInputControl.Margin(newMargin); + + // set number of rows to scroll at a time + _rowsToScroll = _settings.RowsToScroll(); + } + + // Method Description: + // - Set up each layer's brush used to display the control's background. + // - Respects the settings for acrylic, background image and opacity from + // _settings. + // * If acrylic is not enabled, setup a solid color background, otherwise + // use bgcolor as acrylic's tint + // - Avoids image flickering and acrylic brush redraw if settings are changed + // but the appropriate brush is still in place. + // - Does not apply background color outside of acrylic mode; + // _BackgroundColorChanged must be called to do so. + // Arguments: + // - + // Return Value: + // - + void TermControl::_InitializeBackgroundBrush() + { + if (_settings.UseAcrylic()) + { + // See if we've already got an acrylic background brush + // to avoid the flicker when setting up a new one + auto acrylic = _root.Background().try_as(); + + // Instantiate a brush if there's not already one there + if (acrylic == nullptr) + { + acrylic = Media::AcrylicBrush{}; + acrylic.BackgroundSource(Media::AcrylicBackgroundSource::HostBackdrop); + } + + // see GH#1082: Initialize background color so we don't get a + // fade/flash when _BackgroundColorChanged is called + uint32_t color = _settings.DefaultBackground(); + winrt::Windows::UI::Color bgColor{}; + bgColor.R = GetRValue(color); + bgColor.G = GetGValue(color); + bgColor.B = GetBValue(color); + bgColor.A = 255; + + acrylic.FallbackColor(bgColor); + acrylic.TintColor(bgColor); + + // Apply brush settings + acrylic.TintOpacity(_settings.TintOpacity()); + + // Apply brush to control if it's not already there + if (_root.Background() != acrylic) + { + _root.Background(acrylic); + } + } + else + { + Media::SolidColorBrush solidColor{}; + _root.Background(solidColor); + } + + if (!_settings.BackgroundImage().empty()) + { + Windows::Foundation::Uri imageUri{ _settings.BackgroundImage() }; + + // Check if the image brush is already pointing to the image + // in the modified settings; if it isn't (or isn't there), + // set a new image source for the brush + auto imageSource = _bgImageLayer.Source().try_as(); + + if (imageSource == nullptr || + imageSource.UriSource() == nullptr || + imageSource.UriSource().RawUri() != imageUri.RawUri()) + { + // Note that BitmapImage handles the image load asynchronously, + // which is especially important since the image + // may well be both large and somewhere out on the + // internet. + Media::Imaging::BitmapImage image(imageUri); + _bgImageLayer.Source(image); + } + + // Apply stretch, opacity and alignment settings + _bgImageLayer.Stretch(_settings.BackgroundImageStretchMode()); + _bgImageLayer.Opacity(_settings.BackgroundImageOpacity()); + _bgImageLayer.HorizontalAlignment(_settings.BackgroundImageHorizontalAlignment()); + _bgImageLayer.VerticalAlignment(_settings.BackgroundImageVerticalAlignment()); + } + else + { + _bgImageLayer.Source(nullptr); + } + } + + // Method Description: + // - Style the background of the control with the provided background color + // Arguments: + // - color: The background color to use as a uint32 (aka DWORD COLORREF) + // Return Value: + // - + winrt::fire_and_forget TermControl::_BackgroundColorChanged(const uint32_t color) + { + auto weakThis{ get_weak() }; + + co_await winrt::resume_foreground(_root.Dispatcher()); + + if (auto control{ weakThis.get() }) + { + const auto R = GetRValue(color); + const auto G = GetGValue(color); + const auto B = GetBValue(color); + + winrt::Windows::UI::Color bgColor{}; + bgColor.R = R; + bgColor.G = G; + bgColor.B = B; + bgColor.A = 255; + + if (auto acrylic = _root.Background().try_as()) + { + acrylic.FallbackColor(bgColor); + acrylic.TintColor(bgColor); + } + else if (auto solidColor = _root.Background().try_as()) + { + solidColor.Color(bgColor); + } + + // Set the default background as transparent to prevent the + // DX layer from overwriting the background image or acrylic effect + _settings.DefaultBackground(ARGB(0, R, G, B)); + } + } + + TermControl::~TermControl() + { + Close(); + } + + // Method Description: + // - Creates an automation peer for the Terminal Control, enabling accessibility on our control. + // Arguments: + // - None + // Return Value: + // - The automation peer for our control + Windows::UI::Xaml::Automation::Peers::AutomationPeer TermControl::OnCreateAutomationPeer() + try + { + if (GetUiaData()) + { + // create a custom automation peer with this code pattern: + // (https://docs.microsoft.com/en-us/windows/uwp/design/accessibility/custom-automation-peers) + auto autoPeer = winrt::make_self(this); + + _uiaEngine = std::make_unique<::Microsoft::Console::Render::UiaEngine>(autoPeer.get()); + _renderer->AddRenderEngine(_uiaEngine.get()); + return *autoPeer; + } + return nullptr; + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + return nullptr; + } + + ::Microsoft::Console::Types::IUiaData* TermControl::GetUiaData() const + { + return _terminal.get(); + } + + const FontInfo TermControl::GetActualFont() const + { + return _actualFont; + } + + const Windows::UI::Xaml::Thickness TermControl::GetPadding() const + { + return _swapChainPanel.Margin(); + } + + TerminalConnection::ConnectionState TermControl::ConnectionState() const + { + return _connection.State(); + } + + winrt::fire_and_forget TermControl::SwapChainChanged() + { + if (!_initializedTerminal) + { + return; + } + + auto chain = _renderEngine->GetSwapChain(); + auto weakThis{ get_weak() }; + + co_await winrt::resume_foreground(_swapChainPanel.Dispatcher()); + + // If 'weakThis' is locked, then we can safely work with 'this' + if (auto control{ weakThis.get() }) + { + auto lock = _terminal->LockForWriting(); + auto nativePanel = _swapChainPanel.as(); + nativePanel->SetSwapChain(chain.Get()); + } + } + + winrt::fire_and_forget TermControl::_SwapChainRoutine() + { + auto chain = _renderEngine->GetSwapChain(); + auto weakThis{ get_weak() }; + + co_await winrt::resume_foreground(_swapChainPanel.Dispatcher()); + + if (auto control{ weakThis.get() }) + { + _terminal->LockConsole(); + auto nativePanel = _swapChainPanel.as(); + nativePanel->SetSwapChain(chain.Get()); + _terminal->UnlockConsole(); + } + } + + bool TermControl::_InitializeTerminal() + { + if (_initializedTerminal) + { + return false; + } + + const auto windowWidth = _swapChainPanel.ActualWidth(); // Width() and Height() are NaN? + const auto windowHeight = _swapChainPanel.ActualHeight(); + + if (windowWidth == 0 || windowHeight == 0) + { + return false; + } + + _terminal = std::make_unique<::Microsoft::Terminal::Core::Terminal>(); + + // First create the render thread. + // Then stash a local pointer to the render thread so we can initialize it and enable it + // to paint itself *after* we hand off its ownership to the renderer. + // We split up construction and initialization of the render thread object this way + // because the renderer and render thread have circular references to each other. + auto renderThread = std::make_unique<::Microsoft::Console::Render::RenderThread>(); + auto* const localPointerToThread = renderThread.get(); + + // Now create the renderer and initialize the render thread. + _renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(_terminal.get(), nullptr, 0, std::move(renderThread)); + ::Microsoft::Console::Render::IRenderTarget& renderTarget = *_renderer; + + THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get())); + + // Set up the DX Engine + auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>(); + _renderer->AddRenderEngine(dxEngine.get()); + + // Initialize our font with the renderer + // We don't have to care about DPI. We'll get a change message immediately if it's not 96 + // and react accordingly. + _UpdateFont(true); + + const COORD windowSize{ static_cast(windowWidth), static_cast(windowHeight) }; + + // Fist set up the dx engine with the window size in pixels. + // Then, using the font, get the number of characters that can fit. + // Resize our terminal connection to match that size, and initialize the terminal with that size. + const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, windowSize); + THROW_IF_FAILED(dxEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() })); + + // Update DxEngine's SelectionBackground + dxEngine->SetSelectionBackground(_settings.SelectionBackground()); + + const auto vp = dxEngine->GetViewportInCharacters(viewInPixels); + const auto width = vp.Width(); + const auto height = vp.Height(); + _connection.Resize(height, width); + + // Override the default width and height to match the size of the swapChainPanel + _settings.InitialCols(width); + _settings.InitialRows(height); + + _terminal->CreateFromSettings(_settings, renderTarget); + + // Tell the DX Engine to notify us when the swap chain changes. + dxEngine->SetCallback(std::bind(&TermControl::SwapChainChanged, this)); + + // TODO:GH#3927 - Make it possible to hot-reload this setting. Right + // here, the setting will only be used when the Terminal is initialized. + dxEngine->SetRetroTerminalEffects(_settings.RetroTerminalEffect()); + + THROW_IF_FAILED(dxEngine->Enable()); + _renderEngine = std::move(dxEngine); + + // This event is explicitly revoked in the destructor: does not need weak_ref + auto onReceiveOutputFn = [this](const hstring str) { + _terminal->Write(str); + }; + _connectionOutputEventToken = _connection.TerminalOutput(onReceiveOutputFn); + + auto inputFn = std::bind(&TermControl::_SendInputToConnection, this, std::placeholders::_1); + _terminal->SetWriteInputCallback(inputFn); + + _SwapChainRoutine(); + + // Set up the height of the ScrollViewer and the grid we're using to fake our scrolling height + auto bottom = _terminal->GetViewport().BottomExclusive(); + auto bufferHeight = bottom; + + const auto originalMaximum = _scrollBar.Maximum(); + const auto originalMinimum = _scrollBar.Minimum(); + const auto originalValue = _scrollBar.Value(); + const auto originalViewportSize = _scrollBar.ViewportSize(); + + _scrollBar.Maximum(bufferHeight - bufferHeight); + _scrollBar.Minimum(0); + _scrollBar.Value(0); + _scrollBar.ViewportSize(bufferHeight); + _scrollBar.ValueChanged({ this, &TermControl::_ScrollbarChangeHandler }); + _scrollBar.PointerPressed({ this, &TermControl::_CapturePointer }); + _scrollBar.PointerReleased({ this, &TermControl::_ReleasePointerCapture }); + + // Apply settings for scrollbar + if (_settings.ScrollState() == ScrollbarState::Visible) + { + _scrollBar.IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::MouseIndicator); + } + else if (_settings.ScrollState() == ScrollbarState::Hidden) + { + _scrollBar.IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::None); + + // In the scenario where the user has turned off the OS setting to automatically hide scollbars, the + // Terminal scrollbar would still be visible; so, we need to set the control's visibility accordingly to + // achieve the intended effect. + _scrollBar.Visibility(Visibility::Collapsed); + } + else + { + // Default behavior + _scrollBar.IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::MouseIndicator); + } + + _root.PointerWheelChanged({ this, &TermControl::_MouseWheelHandler }); + + // These need to be hooked up to the SwapChainPanel because we don't want the scrollbar to respond to pointer events (GitHub #950) + _swapChainPanel.PointerPressed({ this, &TermControl::_PointerPressedHandler }); + _swapChainPanel.PointerMoved({ this, &TermControl::_PointerMovedHandler }); + _swapChainPanel.PointerReleased({ this, &TermControl::_PointerReleasedHandler }); + + localPointerToThread->EnablePainting(); + + // No matter what order these guys are in, The KeyDown's will fire + // before the CharacterReceived, so we can't easily get characters + // first, then fallback to getting keys from vkeys. + // TODO: This apparently handles keys and characters correctly, though + // I'd keep an eye on it, and test more. + // I presume that the characters that aren't translated by terminalInput + // just end up getting ignored, and the rest of the input comes + // through CharacterReceived. + // I don't believe there's a difference between KeyDown and + // PreviewKeyDown for our purposes + // These two handlers _must_ be on this, not _root. + this->PreviewKeyDown({ this, &TermControl::_KeyDownHandler }); + this->CharacterReceived({ this, &TermControl::_CharacterHandler }); + + auto pfnTitleChanged = std::bind(&TermControl::_TerminalTitleChanged, this, std::placeholders::_1); + _terminal->SetTitleChangedCallback(pfnTitleChanged); + + auto pfnBackgroundColorChanged = std::bind(&TermControl::_BackgroundColorChanged, this, std::placeholders::_1); + _terminal->SetBackgroundCallback(pfnBackgroundColorChanged); + + auto pfnScrollPositionChanged = std::bind(&TermControl::_TerminalScrollPositionChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); + _terminal->SetScrollPositionChangedCallback(pfnScrollPositionChanged); + + static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast(1.0 / 30.0 * 1000000)); + _autoScrollTimer.Interval(AutoScrollUpdateInterval); + _autoScrollTimer.Tick({ get_weak(), &TermControl::_UpdateAutoScroll }); + + // Set up blinking cursor + int blinkTime = GetCaretBlinkTime(); + if (blinkTime != INFINITE) + { + // Create a timer + _cursorTimer = std::make_optional(DispatcherTimer()); + _cursorTimer.value().Interval(std::chrono::milliseconds(blinkTime)); + _cursorTimer.value().Tick({ get_weak(), &TermControl::_BlinkCursor }); + _cursorTimer.value().Start(); + } + else + { + // The user has disabled cursor blinking + _cursorTimer = std::nullopt; + } + + // import value from WinUser (convert from milli-seconds to micro-seconds) + _multiClickTimer = GetDoubleClickTime() * 1000; + + _gotFocusRevoker = this->GotFocus(winrt::auto_revoke, { this, &TermControl::_GotFocusHandler }); + _lostFocusRevoker = this->LostFocus(winrt::auto_revoke, { this, &TermControl::_LostFocusHandler }); + + // Focus the control here. If we do it up above (in _Create_), then the + // focus won't actually get passed to us. I believe this is because + // we're not technically a part of the UI tree yet, so focusing us + // becomes a no-op. + this->Focus(FocusState::Programmatic); + + _connection.Start(); + _initializedTerminal = true; + _InitializedHandlers(*this, nullptr); + return true; + } + + void TermControl::_CharacterHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/, + Input::CharacterReceivedRoutedEventArgs const& e) + { + if (_closing) + { + return; + } + + const auto ch = e.Character(); + + const bool handled = _terminal->SendCharEvent(ch); + e.Handled(handled); + } + + void TermControl::_KeyDownHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/, + Input::KeyRoutedEventArgs const& e) + { + // If the current focused element is a child element of searchbox, + // we do not send this event up to terminal + if (_searchBox && _searchBox->ContainsFocus()) + { + return; + } + + // mark event as handled and do nothing if... + // - closing + // - key modifier is pressed + // NOTE: for key combos like CTRL + C, two events are fired (one for CTRL, one for 'C'). We care about the 'C' event and then check for key modifiers below. + if (_closing || + e.OriginalKey() == VirtualKey::Control || + e.OriginalKey() == VirtualKey::Shift || + e.OriginalKey() == VirtualKey::Menu || + e.OriginalKey() == VirtualKey::LeftWindows || + e.OriginalKey() == VirtualKey::RightWindows) + + { + e.Handled(true); + return; + } + + const auto modifiers = _GetPressedModifierKeys(); + const auto vkey = gsl::narrow_cast(e.OriginalKey()); + const auto scanCode = gsl::narrow_cast(e.KeyStatus().ScanCode); + bool handled = false; + + // GH#2235: Terminal::Settings hasn't been modified to differentiate between AltGr and Ctrl+Alt yet. + // -> Don't check for key bindings if this is an AltGr key combination. + if (!modifiers.IsAltGrPressed()) + { + auto bindings = _settings.KeyBindings(); + if (bindings) + { + handled = bindings.TryKeyChord({ + modifiers.IsCtrlPressed(), + modifiers.IsAltPressed(), + modifiers.IsShiftPressed(), + vkey, + }); + } + } + + if (!handled) + { + handled = _TrySendKeyEvent(vkey, scanCode, modifiers); + } + + // Manually prevent keyboard navigation with tab. We want to send tab to + // the terminal, and we don't want to be able to escape focus of the + // control with tab. + if (e.OriginalKey() == VirtualKey::Tab) + { + handled = true; + } + + e.Handled(handled); + } + + // Method Description: + // - Send this particular key event to the terminal. + // See Terminal::SendKeyEvent for more information. + // - Clears the current selection. + // - Makes the cursor briefly visible during typing. + // Arguments: + // - vkey: The vkey of the key pressed. + // - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states. + bool TermControl::_TrySendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates modifiers) + { + // 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 (_terminal->IsSelectionActive()) + { + _terminal->ClearSelection(); + + 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. + const auto handled = vkey ? _terminal->SendKeyEvent(vkey, scanCode, modifiers) : true; + + if (_cursorTimer.has_value()) + { + // Manually show the cursor when a key is pressed. Restarting + // the timer prevents flickering. + _terminal->SetCursorVisible(true); + _cursorTimer.value().Start(); + } + + return handled; + } + + // Method Description: + // - handle a mouse click event. Begin selection process. + // Arguments: + // - sender: the XAML element responding to the pointer input + // - args: event data + void TermControl::_PointerPressedHandler(Windows::Foundation::IInspectable const& sender, + Input::PointerRoutedEventArgs const& args) + { + _CapturePointer(sender, args); + + const auto ptr = args.Pointer(); + const auto point = args.GetCurrentPoint(_root); + + if (!_focused) + { + Focus(FocusState::Pointer); + + // Save the click position here when the terminal does not have focus + // because they might be performing a click-drag selection. Since we + // only want to start the selection when the user moves the pointer with + // the left mouse button held down, the PointerMovedHandler will use + // this saved position to set the SelectionAnchor. + _clickDragStartPos = point.Position(); + } + + if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse || ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Pen) + { + const auto modifiers = static_cast(args.KeyModifiers()); + // static_cast to a uint32_t because we can't use the WI_IsFlagSet + // macro directly with a VirtualKeyModifiers + const auto altEnabled = WI_IsFlagSet(modifiers, static_cast(VirtualKeyModifiers::Menu)); + const auto shiftEnabled = WI_IsFlagSet(modifiers, static_cast(VirtualKeyModifiers::Shift)); + + if (point.Properties().IsLeftButtonPressed()) + { + // _clickDragStartPos having a value signifies to us that + // the user clicked on an unfocused terminal. We don't want + // a single left click from out of focus to start a selection, + // so we return fast here. + if (_clickDragStartPos) + { + args.Handled(true); + return; + } + + const auto cursorPosition = point.Position(); + const auto terminalPosition = _GetTerminalPosition(cursorPosition); + + // handle ALT key + _terminal->SetBoxSelection(altEnabled); + + auto clickCount = _NumberOfClicks(cursorPosition, point.Timestamp()); + + // This formula enables the number of clicks to cycle properly between single-, double-, and triple-click. + // To increase the number of acceptable click states, simply increment MAX_CLICK_COUNT and add another if-statement + const unsigned int MAX_CLICK_COUNT = 3; + const auto multiClickMapper = clickCount > MAX_CLICK_COUNT ? ((clickCount + MAX_CLICK_COUNT - 1) % MAX_CLICK_COUNT) + 1 : clickCount; + + if (multiClickMapper == 3) + { + _terminal->TripleClickSelection(terminalPosition); + } + else if (multiClickMapper == 2) + { + _terminal->DoubleClickSelection(terminalPosition); + } + else + { + if (shiftEnabled && _terminal->IsSelectionActive()) + { + _terminal->SetEndSelectionPosition(terminalPosition); + } + else + { + // save location before rendering + _terminal->SetSelectionAnchor(terminalPosition); + } + + _lastMouseClick = point.Timestamp(); + _lastMouseClickPos = cursorPosition; + } + _renderer->TriggerSelection(); + } + else if (point.Properties().IsRightButtonPressed()) + { + // copyOnSelect causes right-click to always paste + if (_terminal->IsCopyOnSelectActive() || !_terminal->IsSelectionActive()) + { + PasteTextFromClipboard(); + } + else + { + CopySelectionToClipboard(!shiftEnabled); + } + } + } + else if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Touch) + { + const auto contactRect = point.Properties().ContactRect(); + // Set our touch rect, to start a pan. + _touchAnchor = winrt::Windows::Foundation::Point{ contactRect.X, contactRect.Y }; + } + + args.Handled(true); + } + + // Method Description: + // - handle a mouse moved event. Specifically handling mouse drag to update selection process. + // Arguments: + // - sender: not used + // - args: event data + void TermControl::_PointerMovedHandler(Windows::Foundation::IInspectable const& /*sender*/, + Input::PointerRoutedEventArgs const& args) + { + const auto ptr = args.Pointer(); + const auto point = args.GetCurrentPoint(_root); + + if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse || ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Pen) + { + if (point.Properties().IsLeftButtonPressed()) + { + // If this does not have a value, it means that PointerPressedHandler already + // set the SelectionAnchor. If it does have a value, that means the user is + // performing a click-drag selection on an unfocused terminal, so + // a SelectionAnchor isn't set yet. We'll have to set it here. + if (_clickDragStartPos) + { + _terminal->SetSelectionAnchor(_GetTerminalPosition(*_clickDragStartPos)); + } + + const auto cursorPosition = point.Position(); + _SetEndSelectionPointAtCursor(cursorPosition); + + const double cursorBelowBottomDist = cursorPosition.Y - _swapChainPanel.Margin().Top - _swapChainPanel.ActualHeight(); + const double cursorAboveTopDist = -1 * cursorPosition.Y + _swapChainPanel.Margin().Top; + + constexpr double MinAutoScrollDist = 2.0; // Arbitrary value + double newAutoScrollVelocity = 0.0; + if (cursorBelowBottomDist > MinAutoScrollDist) + { + newAutoScrollVelocity = _GetAutoScrollSpeed(cursorBelowBottomDist); + } + else if (cursorAboveTopDist > MinAutoScrollDist) + { + newAutoScrollVelocity = -1.0 * _GetAutoScrollSpeed(cursorAboveTopDist); + } + + if (newAutoScrollVelocity != 0) + { + _TryStartAutoScroll(point, newAutoScrollVelocity); + } + else + { + _TryStopAutoScroll(ptr.PointerId()); + } + } + } + else if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Touch && _touchAnchor) + { + const auto contactRect = point.Properties().ContactRect(); + winrt::Windows::Foundation::Point newTouchPoint{ contactRect.X, contactRect.Y }; + const auto anchor = _touchAnchor.value(); + + // Get the difference between the point we've dragged to and the start of the touch. + const float fontHeight = float(_actualFont.GetSize().Y); + + const float dy = newTouchPoint.Y - anchor.Y; + + // If we've moved more than one row of text, we'll want to scroll the viewport + if (std::abs(dy) > fontHeight) + { + // Multiply by -1, because moving the touch point down will + // create a positive delta, but we want the viewport to move up, + // so we'll need a negative scroll amount (and the inverse for + // panning down) + const float numRows = -1.0f * (dy / fontHeight); + + const auto currentOffset = this->GetScrollOffset(); + const double newValue = (numRows) + (currentOffset); + + _scrollBar.Value(static_cast(newValue)); + + // Use this point as our new scroll anchor. + _touchAnchor = newTouchPoint; + } + } + args.Handled(true); + } + + // Method Description: + // - Event handler for the PointerReleased event. We use this to de-anchor + // touch events, to stop scrolling via touch. + // Arguments: + // - sender: the XAML element responding to the pointer input + // - args: event data + void TermControl::_PointerReleasedHandler(Windows::Foundation::IInspectable const& sender, + Input::PointerRoutedEventArgs const& args) + { + _ReleasePointerCapture(sender, args); + + const auto ptr = args.Pointer(); + + _clickDragStartPos = std::nullopt; + + if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse || ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Pen) + { + const auto modifiers = static_cast(args.KeyModifiers()); + // static_cast to a uint32_t because we can't use the WI_IsFlagSet + // macro directly with a VirtualKeyModifiers + const auto shiftEnabled = WI_IsFlagSet(modifiers, static_cast(VirtualKeyModifiers::Shift)); + + if (_terminal->IsCopyOnSelectActive()) + { + CopySelectionToClipboard(!shiftEnabled); + } + } + else if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Touch) + { + _touchAnchor = std::nullopt; + } + + _TryStopAutoScroll(ptr.PointerId()); + + args.Handled(true); + } + + // Method Description: + // - Event handler for the PointerWheelChanged event. This is raised in + // response to mouse wheel changes. Depending upon what modifier keys are + // pressed, different actions will take place. + // Arguments: + // - args: the event args containing information about t`he mouse wheel event. + void TermControl::_MouseWheelHandler(Windows::Foundation::IInspectable const& /*sender*/, + Input::PointerRoutedEventArgs const& args) + { + const auto point = args.GetCurrentPoint(_root); + const auto delta = point.Properties().MouseWheelDelta(); + + // Get the state of the Ctrl & Shift keys + // static_cast to a uint32_t because we can't use the WI_IsFlagSet macro + // directly with a VirtualKeyModifiers + const auto modifiers = static_cast(args.KeyModifiers()); + const auto ctrlPressed = WI_IsFlagSet(modifiers, static_cast(VirtualKeyModifiers::Control)); + const auto shiftPressed = WI_IsFlagSet(modifiers, static_cast(VirtualKeyModifiers::Shift)); + + if (ctrlPressed && shiftPressed) + { + _MouseTransparencyHandler(delta); + } + else if (ctrlPressed) + { + _MouseZoomHandler(delta); + } + else + { + _MouseScrollHandler(delta, point); + } + } + + // Method Description: + // - Adjust the opacity of the acrylic background in response to a mouse + // scrolling event. + // Arguments: + // - mouseDelta: the mouse wheel delta that triggered this event. + void TermControl::_MouseTransparencyHandler(const double mouseDelta) + { + // Transparency is on a scale of [0.0,1.0], so only increment by .01. + const auto effectiveDelta = mouseDelta < 0 ? -.01 : .01; + + if (_settings.UseAcrylic()) + { + try + { + auto acrylicBrush = _root.Background().as(); + acrylicBrush.TintOpacity(acrylicBrush.TintOpacity() + effectiveDelta); + } + CATCH_LOG(); + } + } + + // Method Description: + // - Adjust the font size of the terminal in response to a mouse scrolling + // event. + // Arguments: + // - mouseDelta: the mouse wheel delta that triggered this event. + void TermControl::_MouseZoomHandler(const double mouseDelta) + { + const auto fontDelta = mouseDelta < 0 ? -1 : 1; + AdjustFontSize(fontDelta); + } + + // Method Description: + // - Reset the font size of the terminal to its default size. + // Arguments: + // - none + void TermControl::ResetFontSize() + { + _SetFontSize(_settings.FontSize()); + } + + // Method Description: + // - Adjust the font size of the terminal control. + // Arguments: + // - fontSizeDelta: The amount to increase or decrease the font size by. + void TermControl::AdjustFontSize(int fontSizeDelta) + { + const auto newSize = _desiredFont.GetEngineSize().Y + fontSizeDelta; + _SetFontSize(newSize); + } + + // Method Description: + // - Scroll the visible viewport in response to a mouse wheel event. + // Arguments: + // - mouseDelta: the mouse wheel delta that triggered this event. + void TermControl::_MouseScrollHandler(const double mouseDelta, Windows::UI::Input::PointerPoint const& pointerPoint) + { + const auto currentOffset = this->GetScrollOffset(); + + // negative = down, positive = up + // However, for us, the signs are flipped. + const auto rowDelta = mouseDelta < 0 ? 1.0 : -1.0; + + // With one of the precision mouses, one click is always a multiple of 120, + // but the "smooth scrolling" mode results in non-int values + + double newValue = (_rowsToScroll * rowDelta) + (currentOffset); + + // The scroll bar's ValueChanged handler will actually move the viewport + // for us. + _scrollBar.Value(static_cast(newValue)); + + if (_terminal->IsSelectionActive() && pointerPoint.Properties().IsLeftButtonPressed()) + { + // If user is mouse selecting and scrolls, they then point at new character. + // Make sure selection reflects that immediately. + _SetEndSelectionPointAtCursor(pointerPoint.Position()); + } + } + + void TermControl::_ScrollbarChangeHandler(Windows::Foundation::IInspectable const& /*sender*/, + Controls::Primitives::RangeBaseValueChangedEventArgs const& args) + { + if (_isTerminalInitiatedScroll) + { + return; + } + + const auto newValue = static_cast(args.NewValue()); + + // This is a scroll event that wasn't initiated by the terminal + // itself - it was initiated by the mouse wheel, or the scrollbar. + _terminal->UserScrollViewport(newValue); + + // We've just told the terminal to update its viewport to reflect the + // new scroll value so the scroll bar matches the viewport now. + _willUpdateScrollBarToMatchViewport.store(false); + } + + // Method Description: + // - captures the pointer so that none of the other XAML elements respond to pointer events + // Arguments: + // - sender: XAML element that is interacting with pointer + // - args: pointer data (i.e.: mouse, touch) + // Return Value: + // - true if we successfully capture the pointer, false otherwise. + bool TermControl::_CapturePointer(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& args) + { + IUIElement uielem; + if (sender.try_as(uielem)) + { + uielem.CapturePointer(args.Pointer()); + return true; + } + return false; + } + + // Method Description: + // - releases the captured pointer because we're done responding to XAML pointer events + // Arguments: + // - sender: XAML element that is interacting with pointer + // - args: pointer data (i.e.: mouse, touch) + // Return Value: + // - true if we release capture of the pointer, false otherwise. + bool TermControl::_ReleasePointerCapture(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& args) + { + IUIElement uielem; + if (sender.try_as(uielem)) + { + uielem.ReleasePointerCapture(args.Pointer()); + return true; + } + return false; + } + + // Method Description: + // - Starts new pointer related auto scroll behavior, or continues existing one. + // Does nothing when there is already auto scroll associated with another pointer. + // Arguments: + // - pointerPoint: info about pointer that causes auto scroll. Pointer's position + // is later used to update selection. + // - scrollVelocity: target velocity of scrolling in characters / sec + void TermControl::_TryStartAutoScroll(Windows::UI::Input::PointerPoint const& pointerPoint, const double scrollVelocity) + { + // Allow only one pointer at the time + if (!_autoScrollingPointerPoint.has_value() || _autoScrollingPointerPoint.value().PointerId() == pointerPoint.PointerId()) + { + _autoScrollingPointerPoint = pointerPoint; + _autoScrollVelocity = scrollVelocity; + + // If this is first time the auto scroll update is about to be called, + // kick-start it by initializing its time delta as if it started now + if (!_lastAutoScrollUpdateTime.has_value()) + { + _lastAutoScrollUpdateTime = std::chrono::high_resolution_clock::now(); + } + + // Apparently this check is not necessary but greatly improves performance + if (!_autoScrollTimer.IsEnabled()) + { + _autoScrollTimer.Start(); + } + } + } + + // Method Description: + // - Stops auto scroll if it's active and is associated with supplied pointer id. + // Arguments: + // - pointerId: id of pointer for which to stop auto scroll + void TermControl::_TryStopAutoScroll(const uint32_t pointerId) + { + if (_autoScrollingPointerPoint.has_value() && pointerId == _autoScrollingPointerPoint.value().PointerId()) + { + _autoScrollingPointerPoint = std::nullopt; + _autoScrollVelocity = 0; + _lastAutoScrollUpdateTime = std::nullopt; + + // Apparently this check is not necessary but greatly improves performance + if (_autoScrollTimer.IsEnabled()) + { + _autoScrollTimer.Stop(); + } + } + } + + // Method Description: + // - Called continuously to gradually scroll viewport when user is + // mouse selecting outside it (to 'follow' the cursor). + // Arguments: + // - none + void TermControl::_UpdateAutoScroll(Windows::Foundation::IInspectable const& /* sender */, + Windows::Foundation::IInspectable const& /* e */) + { + if (_autoScrollVelocity != 0) + { + const auto timeNow = std::chrono::high_resolution_clock::now(); + + if (_lastAutoScrollUpdateTime.has_value()) + { + static constexpr double microSecPerSec = 1000000.0; + const double deltaTime = std::chrono::duration_cast(timeNow - _lastAutoScrollUpdateTime.value()).count() / microSecPerSec; + _scrollBar.Value(_scrollBar.Value() + _autoScrollVelocity * deltaTime); + + if (_autoScrollingPointerPoint.has_value()) + { + _SetEndSelectionPointAtCursor(_autoScrollingPointerPoint.value().Position()); + } + } + + _lastAutoScrollUpdateTime = timeNow; + } + } + + // Method Description: + // - Event handler for the GotFocus event. This is used to... + // - enable accessibility notifications for this TermControl + // - start blinking the cursor when the window is focused + // - update the number of lines to scroll to the value set in the system + void TermControl::_GotFocusHandler(Windows::Foundation::IInspectable const& /* sender */, + RoutedEventArgs const& /* args */) + { + if (_closing) + { + return; + } + _focused = true; + + if (_uiaEngine.get()) + { + THROW_IF_FAILED(_uiaEngine->Enable()); + } + + if (_tsfInputControl != nullptr) + { + _tsfInputControl.NotifyFocusEnter(); + } + + if (_cursorTimer.has_value()) + { + // When the terminal focuses, show the cursor immediately + _terminal->SetCursorVisible(true); + _cursorTimer.value().Start(); + } + _rowsToScroll = _settings.RowsToScroll(); + } + + // Method Description: + // - Event handler for the LostFocus event. This is used to... + // - disable accessibility notifications for this TermControl + // - hide and stop blinking the cursor when the window loses focus. + void TermControl::_LostFocusHandler(Windows::Foundation::IInspectable const& /* sender */, + RoutedEventArgs const& /* args */) + { + if (_closing) + { + return; + } + _focused = false; + + if (_uiaEngine.get()) + { + THROW_IF_FAILED(_uiaEngine->Disable()); + } + + if (_tsfInputControl != nullptr) + { + _tsfInputControl.NotifyFocusLeave(); + } + + if (_cursorTimer.has_value()) + { + _cursorTimer.value().Stop(); + _terminal->SetCursorVisible(false); + } + } + + // Method Description: + // - Writes the given sequence as input to the active terminal connection, + // Arguments: + // - wstr: the string of characters to write to the terminal connection. + // Return Value: + // - + void TermControl::_SendInputToConnection(const std::wstring& wstr) + { + _connection.WriteInput(wstr); + } + + // Method Description: + // - Pre-process text pasted (presumably from the clipboard) + // before sending it over the terminal's connection, converting + // Windows-space \r\n line-endings to \r line-endings + void TermControl::_SendPastedTextToConnection(const std::wstring& wstr) + { + // Some notes on this implementation: + // + // - std::regex can do this in a single line, but is somewhat + // overkill for a simple search/replace operation (and its + // performance guarantees aren't exactly stellar) + // - The STL doesn't have a simple string search/replace method. + // This fact is lamentable. + // - This line-ending conversion is intentionally fairly + // conservative, to avoid stripping out lone \n characters + // where they could conceivably be intentional. + + std::wstring stripped{ wstr }; + + std::wstring::size_type pos = 0; + + while ((pos = stripped.find(L"\r\n", pos)) != std::wstring::npos) + { + stripped.replace(pos, 2, L"\r"); + } + + _connection.WriteInput(stripped); + _terminal->TrySnapOnInput(); + } + + // Method Description: + // - Update the font with the renderer. This will be called either when the + // font changes or the DPI changes, as DPI changes will necessitate a + // font change. This method will *not* change the buffer/viewport size + // to account for the new glyph dimensions. Callers should make sure to + // appropriately call _DoResize after this method is called. + // Arguments: + // - initialUpdate: whether this font update should be considered as being + // concerned with initialization process. Value forwarded to event handler. + void TermControl::_UpdateFont(const bool initialUpdate) + { + auto lock = _terminal->LockForWriting(); + + const int newDpi = static_cast(static_cast(USER_DEFAULT_SCREEN_DPI) * _swapChainPanel.CompositionScaleX()); + + // TODO: MSFT:20895307 If the font doesn't exist, this doesn't + // actually fail. We need a way to gracefully fallback. + _renderer->TriggerFontChange(newDpi, _desiredFont, _actualFont); + + const auto actualNewSize = _actualFont.GetSize(); + _fontSizeChangedHandlers(actualNewSize.X, actualNewSize.Y, initialUpdate); + } + + // Method Description: + // - Set the font size of the terminal control. + // Arguments: + // - fontSize: The size of the font. + void TermControl::_SetFontSize(int fontSize) + { + try + { + // Make sure we have a non-zero font size + const auto newSize = std::max(gsl::narrow(fontSize), static_cast(1)); + const auto fontFace = _settings.FontFace(); + _actualFont = { fontFace, 0, 10, { 0, newSize }, CP_UTF8, false }; + _desiredFont = { _actualFont }; + + // Refresh our font with the renderer + _UpdateFont(); + // Resize the terminal's BUFFER to match the new font size. This does + // NOT change the size of the window, because that can lead to more + // problems (like what happens when you change the font size while the + // window is maximized?) + auto lock = _terminal->LockForWriting(); + _DoResize(_swapChainPanel.ActualWidth(), _swapChainPanel.ActualHeight()); + } + CATCH_LOG(); + } + + // Method Description: + // - Triggered when the swapchain changes size. We use this to resize the + // terminal buffers to match the new visible size. + // Arguments: + // - e: a SizeChangedEventArgs with the new dimensions of the SwapChainPanel + void TermControl::_SwapChainSizeChanged(winrt::Windows::Foundation::IInspectable const& /*sender*/, + SizeChangedEventArgs const& e) + { + if (!_initializedTerminal) + { + return; + } + + auto lock = _terminal->LockForWriting(); + + const auto foundationSize = e.NewSize(); + + _DoResize(foundationSize.Width, foundationSize.Height); + } + + void TermControl::_SwapChainScaleChanged(Windows::UI::Xaml::Controls::SwapChainPanel const& sender, + Windows::Foundation::IInspectable const& /*args*/) + { + if (_renderEngine) + { + const auto scale = sender.CompositionScaleX(); + const auto dpi = (int)(scale * USER_DEFAULT_SCREEN_DPI); + + // TODO: MSFT: 21169071 - Shouldn't this all happen through _renderer and trigger the invalidate automatically on DPI change? + THROW_IF_FAILED(_renderEngine->UpdateDpi(dpi)); + _renderer->TriggerRedrawAll(); + } + } + + // Method Description: + // - Toggle the cursor on and off when called by the cursor blink timer. + // Arguments: + // - sender: not used + // - e: not used + void TermControl::_BlinkCursor(Windows::Foundation::IInspectable const& /* sender */, + Windows::Foundation::IInspectable const& /* e */) + { + if ((_closing) || (!_terminal->IsCursorBlinkingAllowed() && _terminal->IsCursorVisible())) + { + return; + } + _terminal->SetCursorVisible(!_terminal->IsCursorVisible()); + } + + // Method Description: + // - Sets selection's end position to match supplied cursor position, e.g. while mouse dragging. + // Arguments: + // - cursorPosition: in pixels, relative to the origin of the control + void TermControl::_SetEndSelectionPointAtCursor(Windows::Foundation::Point const& cursorPosition) + { + auto terminalPosition = _GetTerminalPosition(cursorPosition); + + const short lastVisibleRow = std::max(_terminal->GetViewport().Height() - 1, 0); + const short lastVisibleCol = std::max(_terminal->GetViewport().Width() - 1, 0); + + terminalPosition.Y = std::clamp(terminalPosition.Y, 0, lastVisibleRow); + terminalPosition.X = std::clamp(terminalPosition.X, 0, lastVisibleCol); + + // save location (for rendering) + render + _terminal->SetEndSelectionPosition(terminalPosition); + _renderer->TriggerSelection(); + } + + // Method Description: + // - Process a resize event that was initiated by the user. This can either + // be due to the user resizing the window (causing the swapchain to + // resize) or due to the DPI changing (causing us to need to resize the + // buffer to match) + // Arguments: + // - newWidth: the new width of the swapchain, in pixels. + // - newHeight: the new height of the swapchain, in pixels. + void TermControl::_DoResize(const double newWidth, const double newHeight) + { + SIZE size; + size.cx = static_cast(newWidth); + size.cy = static_cast(newHeight); + + // Don't actually resize so small that a single character wouldn't fit + // in either dimension. The buffer really doesn't like being size 0. + if (size.cx < _actualFont.GetSize().X || size.cy < _actualFont.GetSize().Y) + { + return; + } + + // Tell the dx engine that our window is now the new size. + THROW_IF_FAILED(_renderEngine->SetWindowSize(size)); + + // Invalidate everything + _renderer->TriggerRedrawAll(); + + // Convert our new dimensions to characters + const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, + { static_cast(size.cx), static_cast(size.cy) }); + const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels); + + // If this function succeeds with S_FALSE, then the terminal didn't + // actually change size. No need to notify the connection of this + // no-op. + // TODO: MSFT:20642295 Resizing the buffer will corrupt it + // I believe we'll need support for CSI 2J, and additionally I think + // we're resetting the viewport to the top + const HRESULT hr = _terminal->UserResize({ vp.Width(), vp.Height() }); + if (SUCCEEDED(hr) && hr != S_FALSE) + { + _connection.Resize(vp.Height(), vp.Width()); + } + } + + void TermControl::_TerminalTitleChanged(const std::wstring_view& wstr) + { + _titleChangedHandlers(winrt::hstring{ wstr }); + } + + // Method Description: + // - Update the position and size of the scrollbar to match the given + // viewport top, viewport height, and buffer size. + // The change will be actually handled in _ScrollbarChangeHandler. + // This should be done on the UI thread. Make sure the caller is calling + // us in a RunAsync block. + // Arguments: + // - viewTop: the top of the visible viewport, in rows. 0 indicates the top + // of the buffer. + // - viewHeight: the height of the viewport in rows. + // - bufferSize: the length of the buffer, in rows + void TermControl::_ScrollbarUpdater(Controls::Primitives::ScrollBar scrollBar, + const int viewTop, + const int viewHeight, + const int bufferSize) + { + // The terminal is already in the scroll position it wants, so no need + // to tell it to scroll. + _isTerminalInitiatedScroll = true; + + const auto hiddenContent = bufferSize - viewHeight; + scrollBar.Maximum(hiddenContent); + scrollBar.Minimum(0); + scrollBar.ViewportSize(viewHeight); + scrollBar.Value(viewTop); + + _isTerminalInitiatedScroll = false; + } + + // Method Description: + // - Update the position and size of the scrollbar to match the given + // viewport top, viewport height, and buffer size. + // Additionally fires a ScrollPositionChanged event for anyone who's + // registered an event handler for us. + // Arguments: + // - viewTop: the top of the visible viewport, in rows. 0 indicates the top + // of the buffer. + // - viewHeight: the height of the viewport in rows. + // - bufferSize: the length of the buffer, in rows + winrt::fire_and_forget TermControl::_TerminalScrollPositionChanged(const int viewTop, + const int viewHeight, + const int bufferSize) + { + // Since this callback fires from non-UI thread, we might be already + // closed/closing. + if (_closing.load()) + { + return; + } + + _scrollPositionChangedHandlers(viewTop, viewHeight, bufferSize); + + auto weakThis{ get_weak() }; + + co_await winrt::resume_foreground(_scrollBar.Dispatcher()); + + // Even if we weren't closed/closing few lines above, we might be + // while waiting for this block of code to be dispatched. + // If 'weakThis' is locked, then we can safely work with 'this' + if (auto control{ weakThis.get() }) + { + if (!_closing.load()) + { + // Update our scrollbar + _ScrollbarUpdater(_scrollBar, viewTop, viewHeight, bufferSize); + } + } + } + + hstring TermControl::Title() + { + if (!_initializedTerminal) + return L""; + + hstring hstr(_terminal->GetConsoleTitle()); + return hstr; + } + + // Method Description: + // - Given a copy-able selection, get the selected text from the buffer and send it to the + // Windows Clipboard (CascadiaWin32:main.cpp). + // - CopyOnSelect does NOT clear the selection + // Arguments: + // - trimTrailingWhitespace: enable removing any whitespace from copied selection + // and get text to appear on separate lines. + bool TermControl::CopySelectionToClipboard(bool trimTrailingWhitespace) + { + // no selection --> nothing to copy + if (_terminal == nullptr || !_terminal->IsSelectionActive()) + { + return false; + } + // extract text from buffer + const auto bufferData = _terminal->RetrieveSelectedTextFromBuffer(trimTrailingWhitespace); + + // convert text: vector --> string + std::wstring textData; + for (const auto& text : bufferData.text) + { + textData += text; + } + + // convert text to HTML format + const auto htmlData = TextBuffer::GenHTML(bufferData, + _actualFont.GetUnscaledSize().Y, + _actualFont.GetFaceName(), + _settings.DefaultBackground(), + "Windows Terminal"); + + // convert to RTF format + const auto rtfData = TextBuffer::GenRTF(bufferData, + _actualFont.GetUnscaledSize().Y, + _actualFont.GetFaceName(), + _settings.DefaultBackground()); + + if (!_terminal->IsCopyOnSelectActive()) + { + _terminal->ClearSelection(); + } + + // send data up for clipboard + auto copyArgs = winrt::make_self(winrt::hstring(textData), + winrt::to_hstring(htmlData), + winrt::to_hstring(rtfData)); + _clipboardCopyHandlers(*this, *copyArgs); + return true; + } + + // Method Description: + // - Initiate a paste operation. + void TermControl::PasteTextFromClipboard() + { + // attach TermControl::_SendInputToConnection() as the clipboardDataHandler. + // This is called when the clipboard data is loaded. + auto clipboardDataHandler = std::bind(&TermControl::_SendPastedTextToConnection, this, std::placeholders::_1); + auto pasteArgs = winrt::make_self(clipboardDataHandler); + + // send paste event up to TermApp + _clipboardPasteHandlers(*this, *pasteArgs); + } + + void TermControl::Close() + { + if (!_closing.exchange(true)) + { + // Stop accepting new output and state changes before we disconnect everything. + _connection.TerminalOutput(_connectionOutputEventToken); + _connectionStateChangedRevoker.revoke(); + + _tsfInputControl.Close(); // Disconnect the TSF input control so it doesn't receive EditContext events. + + if (auto localConnection{ std::exchange(_connection, nullptr) }) + { + localConnection.Close(); + // connection is destroyed. + } + + if (auto localRenderEngine{ std::exchange(_renderEngine, nullptr) }) + { + if (auto localRenderer{ std::exchange(_renderer, nullptr) }) + { + localRenderer->TriggerTeardown(); + // renderer is destroyed + } + // renderEngine is destroyed + } + + if (auto localTerminal{ std::exchange(_terminal, nullptr) }) + { + _initializedTerminal = false; + // terminal is destroyed. + } + } + } + + // Method Description: + // - Scrolls the viewport of the terminal and updates the scroll bar accordingly + // Arguments: + // - viewTop: the viewTop to scroll to + void TermControl::ScrollViewport(int viewTop) + { + _scrollBar.Value(viewTop); + } + + int TermControl::GetScrollOffset() + { + return _terminal->GetScrollOffset(); + } + + // Function Description: + // - Gets the height of the terminal in lines of text + // Return Value: + // - The height of the terminal in lines of text + int TermControl::GetViewHeight() const + { + const auto viewPort = _terminal->GetViewport(); + return viewPort.Height(); + } + + // Function Description: + // - Determines how much space (in pixels) an app would need to reserve to + // create a control with the settings stored in the settings param. This + // accounts for things like the font size and face, the initialRows and + // initialCols, and scrollbar visibility. The returned sized is based upon + // the provided DPI value + // Arguments: + // - settings: A IControlSettings with the settings to get the pixel size of. + // - dpi: The DPI we should create the terminal at. This affects things such + // as font size, scrollbar and other control scaling, etc. Make sure the + // caller knows what monitor the control is about to appear on. + // Return Value: + // - a point containing the requested dimensions in pixels. + winrt::Windows::Foundation::Point TermControl::GetProposedDimensions(IControlSettings const& settings, const uint32_t dpi) + { + // Initialize our font information. + const auto fontFace = settings.FontFace(); + const short fontHeight = gsl::narrow(settings.FontSize()); + // The font width doesn't terribly matter, we'll only be using the + // height to look it up + // The other params here also largely don't matter. + // The family is only used to determine if the font is truetype or + // not, but DX doesn't use that info at all. + // The Codepage is additionally not actually used by the DX engine at all. + FontInfo actualFont = { fontFace, 0, 10, { 0, fontHeight }, CP_UTF8, false }; + FontInfoDesired desiredFont = { actualFont }; + + // If the settings have negative or zero row or column counts, ignore those counts. + // (The lower TerminalCore layer also has upper bounds as well, but at this layer + // we may eventually impose different ones depending on how many pixels we can address.) + const auto cols = std::max(settings.InitialCols(), 1); + const auto rows = std::max(settings.InitialRows(), 1); + + // Create a DX engine and initialize it with our font and DPI. We'll + // then use it to measure how much space the requested rows and columns + // will take up. + // TODO: MSFT:21254947 - use a static function to do this instead of + // instantiating a DxEngine + auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>(); + THROW_IF_FAILED(dxEngine->UpdateDpi(dpi)); + THROW_IF_FAILED(dxEngine->UpdateFont(desiredFont, actualFont)); + + const float scale = dxEngine->GetScaling(); + const auto fontSize = actualFont.GetSize(); + + // Manually multiply by the scaling factor. The DX engine doesn't + // actually store the scaled font size in the fontInfo.GetSize() + // property when the DX engine is in Composition mode (which it is for + // the Terminal). At runtime, this is fine, as we'll transform + // everything by our scaling, so it'll work out. However, right now we + // need to get the exact pixel count. + const float fFontWidth = gsl::narrow(fontSize.X * scale); + const float fFontHeight = gsl::narrow(fontSize.Y * scale); + + // UWP XAML scrollbars aren't guaranteed to be the same size as the + // ComCtl scrollbars, but it's certainly close enough. + const auto scrollbarSize = GetSystemMetricsForDpi(SM_CXVSCROLL, dpi); + + double width = cols * fFontWidth; + + // Reserve additional space if scrollbar is intended to be visible + if (settings.ScrollState() == ScrollbarState::Visible) + { + width += scrollbarSize; + } + + double height = rows * fFontHeight; + auto thickness = _ParseThicknessFromPadding(settings.Padding()); + width += thickness.Left + thickness.Right; + height += thickness.Top + thickness.Bottom; + + return { gsl::narrow_cast(width), gsl::narrow_cast(height) }; + } + + // Method Description: + // - Get the size of a single character of this control. The size is in + // DIPs. If you need it in _pixels_, you'll need to multiply by the + // current display scaling. + // Arguments: + // - + // Return Value: + // - The dimensions of a single character of this control, in DIPs + winrt::Windows::Foundation::Size TermControl::CharacterDimensions() const + { + const auto fontSize = _actualFont.GetSize(); + return { gsl::narrow_cast(fontSize.X), gsl::narrow_cast(fontSize.Y) }; + } + + // Method Description: + // - Get the absolute minimum size that this control can be resized to and + // still have 1x1 character visible. This includes the space needed for + // the scrollbar and the padding. + // Arguments: + // - + // Return Value: + // - The minimum size that this terminal control can be resized to and still + // have a visible character. + winrt::Windows::Foundation::Size TermControl::MinimumSize() const + { + const auto fontSize = _actualFont.GetSize(); + double width = fontSize.X; + double height = fontSize.Y; + // Reserve additional space if scrollbar is intended to be visible + if (_settings.ScrollState() == ScrollbarState::Visible) + { + width += _scrollBar.ActualWidth(); + } + + // Account for the size of any padding + const auto padding = _swapChainPanel.Margin(); + width += padding.Left + padding.Right; + height += padding.Top + padding.Bottom; + + return { gsl::narrow_cast(width), gsl::narrow_cast(height) }; + } + + // Method Description: + // - Adjusts given dimension (width or height) so that it aligns to the character grid. + // The snap is always downward. + // Arguments: + // - widthOrHeight: if true operates on width, otherwise on height + // - dimension: a dimension (width or height) to be snapped + // Return Value: + // - A dimension that would be aligned to the character grid. + float TermControl::SnapDimensionToGrid(const bool widthOrHeight, const float dimension) const + { + const auto fontSize = _actualFont.GetSize(); + const auto fontDimension = widthOrHeight ? fontSize.X : fontSize.Y; + + const auto padding = _swapChainPanel.Margin(); + auto nonTerminalArea = gsl::narrow_cast(widthOrHeight ? + padding.Left + padding.Right : + padding.Top + padding.Bottom); + + if (widthOrHeight && _settings.ScrollState() == ScrollbarState::Visible) + { + nonTerminalArea += gsl::narrow_cast(_scrollBar.ActualWidth()); + } + + const auto gridSize = dimension - nonTerminalArea; + const int cells = static_cast(gridSize / fontDimension); + return cells * fontDimension + nonTerminalArea; + } + + // Method Description: + // - Create XAML Thickness object based on padding props provided. + // Used for controlling the TermControl XAML Grid container's Padding prop. + // Arguments: + // - padding: 2D padding values + // Single Double value provides uniform padding + // Two Double values provide isometric horizontal & vertical padding + // Four Double values provide independent padding for 4 sides of the bounding rectangle + // Return Value: + // - Windows::UI::Xaml::Thickness object + Windows::UI::Xaml::Thickness TermControl::_ParseThicknessFromPadding(const hstring padding) + { + const wchar_t singleCharDelim = L','; + std::wstringstream tokenStream(padding.c_str()); + std::wstring token; + uint8_t paddingPropIndex = 0; + std::array thicknessArr = {}; + size_t* idx = nullptr; + + // Get padding values till we run out of delimiter separated values in the stream + // or we hit max number of allowable values (= 4) for the bounding rectangle + // Non-numeral values detected will default to 0 + // std::getline will not throw exception unless flags are set on the wstringstream + // std::stod will throw invalid_argument exception if the input is an invalid double value + // std::stod will throw out_of_range exception if the input value is more than DBL_MAX + try + { + for (; std::getline(tokenStream, token, singleCharDelim) && (paddingPropIndex < thicknessArr.size()); paddingPropIndex++) + { + // std::stod internally calls wcstod which handles whitespace prefix (which is ignored) + // & stops the scan when first char outside the range of radix is encountered + // We'll be permissive till the extent that stod function allows us to be by default + // Ex. a value like 100.3#535w2 will be read as 100.3, but ;df25 will fail + thicknessArr[paddingPropIndex] = std::stod(token, idx); + } + } + catch (...) + { + // If something goes wrong, even if due to a single bad padding value, we'll reset the index & return default 0 padding + paddingPropIndex = 0; + LOG_CAUGHT_EXCEPTION(); + } + + switch (paddingPropIndex) + { + case 1: + return ThicknessHelper::FromUniformLength(thicknessArr[0]); + case 2: + return ThicknessHelper::FromLengths(thicknessArr[0], thicknessArr[1], thicknessArr[0], thicknessArr[1]); + // No case for paddingPropIndex = 3, since it's not a norm to provide just Left, Top & Right padding values leaving out Bottom + case 4: + return ThicknessHelper::FromLengths(thicknessArr[0], thicknessArr[1], thicknessArr[2], thicknessArr[3]); + default: + return Thickness(); + } + } + + // Method Description: + // - Get the modifier keys that are currently pressed. This can be used to + // find out which modifiers (ctrl, alt, shift) are pressed in events that + // don't necessarily include that state. + // Return Value: + // - The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states. + ControlKeyStates TermControl::_GetPressedModifierKeys() const + { + CoreWindow window = CoreWindow::GetForCurrentThread(); + // DONT USE + // != CoreVirtualKeyStates::None + // OR + // == CoreVirtualKeyStates::Down + // Sometimes with the key down, the state is Down | Locked. + // Sometimes with the key up, the state is Locked. + // IsFlagSet(Down) is the only correct solution. + + struct KeyModifier + { + VirtualKey vkey; + ControlKeyStates flags; + }; + + constexpr std::array modifiers{ { + { VirtualKey::RightMenu, ControlKeyStates::RightAltPressed }, + { VirtualKey::LeftMenu, ControlKeyStates::LeftAltPressed }, + { VirtualKey::RightControl, ControlKeyStates::RightCtrlPressed }, + { VirtualKey::LeftControl, ControlKeyStates::LeftCtrlPressed }, + { VirtualKey::Shift, ControlKeyStates::ShiftPressed }, + } }; + + ControlKeyStates flags; + + for (const auto& mod : modifiers) + { + const auto state = window.GetKeyState(mod.vkey); + const auto isDown = WI_IsFlagSet(state, CoreVirtualKeyStates::Down); + + if (isDown) + { + flags |= mod.flags; + } + } + + return flags; + } + + // Method Description: + // - Gets the corresponding viewport terminal position for the cursor + // by excluding the padding and normalizing with the font size. + // This is used for selection. + // Arguments: + // - cursorPosition: the (x,y) position of a given cursor (i.e.: mouse cursor). + // NOTE: origin (0,0) is top-left. + // Return Value: + // - the corresponding viewport terminal position for the given Point parameter + const COORD TermControl::_GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition) + { + // Exclude padding from cursor position calculation + COORD terminalPosition = { + static_cast(cursorPosition.X - _swapChainPanel.Margin().Left), + static_cast(cursorPosition.Y - _swapChainPanel.Margin().Top) + }; + + const auto fontSize = _actualFont.GetSize(); + FAIL_FAST_IF(fontSize.X == 0); + FAIL_FAST_IF(fontSize.Y == 0); + + // Normalize to terminal coordinates by using font size + terminalPosition.X /= fontSize.X; + terminalPosition.Y /= fontSize.Y; + + return terminalPosition; + } + + // Method Description: + // - Composition Completion handler for the TSFInputControl that + // handles writing text out to TerminalConnection + // Arguments: + // - text: the text to write to TerminalConnection + // Return Value: + // - + void TermControl::_CompositionCompleted(winrt::hstring text) + { + _connection.WriteInput(text); + } + + // Method Description: + // - CurrentCursorPosition handler for the TSFInputControl that + // handles returning current cursor position. + // Arguments: + // - eventArgs: event for storing the current cursor position + // Return Value: + // - + void TermControl::_CurrentCursorPositionHandler(const IInspectable& /*sender*/, const CursorPositionEventArgs& eventArgs) + { + const COORD cursorPos = _terminal->GetCursorPosition(); + Windows::Foundation::Point p = { gsl::narrow(cursorPos.X), gsl::narrow(cursorPos.Y) }; + eventArgs.CurrentPosition(p); + } + + // Method Description: + // - FontInfo handler for the TSFInputControl that + // handles returning current font information + // Arguments: + // - eventArgs: event for storing the current font information + // Return Value: + // - + void TermControl::_FontInfoHandler(const IInspectable& /*sender*/, const FontInfoEventArgs& eventArgs) + { + eventArgs.FontSize(CharacterDimensions()); + eventArgs.FontFace(_actualFont.GetFaceName()); + } + + // Method Description: + // - Returns the number of clicks that occurred (double and triple click support) + // Arguments: + // - clickPos: the (x,y) position of a given cursor (i.e.: mouse cursor). + // NOTE: origin (0,0) is top-left. + // - clickTime: the timestamp that the click occurred + // Return Value: + // - if the click is in the same position as the last click and within the timeout, the number of clicks within that time window + // - otherwise, 1 + const unsigned int TermControl::_NumberOfClicks(winrt::Windows::Foundation::Point clickPos, Timestamp clickTime) + { + // if click occurred at a different location or past the multiClickTimer... + Timestamp delta; + THROW_IF_FAILED(UInt64Sub(clickTime, _lastMouseClick, &delta)); + if (clickPos != _lastMouseClickPos || delta > _multiClickTimer) + { + // exit early. This is a single click. + _multiClickCounter = 1; + } + else + { + _multiClickCounter++; + } + return _multiClickCounter; + } + + // Method Description: + // - Calculates speed of single axis of auto scrolling. It has to allow for both + // fast and precise selection. + // Arguments: + // - cursorDistanceFromBorder: distance from viewport border to cursor, in pixels. Must be non-negative. + // Return Value: + // - positive speed in characters / sec + double TermControl::_GetAutoScrollSpeed(double cursorDistanceFromBorder) const + { + // The numbers below just feel well, feel free to change. + // TODO: Maybe account for space beyond border that user has available + return std::pow(cursorDistanceFromBorder, 2.0) / 25.0 + 2.0; + } + + // Method Description: + // - Async handler for the "Drop" event. If a file was dropped onto our + // root, we'll try to get the path of the file dropped onto us, and write + // the full path of the file to our terminal connection. Like conhost, if + // the path contains a space, we'll wrap the path in quotes. + // - Unlike conhost, if multiple files are dropped onto the terminal, we'll + // write all the paths to the terminal, separated by spaces. + // Arguments: + // - e: The DragEventArgs from the Drop event + // Return Value: + // - + winrt::fire_and_forget TermControl::_DoDragDrop(DragEventArgs const e) + { + if (e.DataView().Contains(StandardDataFormats::StorageItems())) + { + auto items = co_await e.DataView().GetStorageItemsAsync(); + if (items.Size() > 0) + { + std::wstring allPaths; + for (auto item : items) + { + // Join the paths with spaces + if (!allPaths.empty()) + { + allPaths += L" "; + } + + std::wstring fullPath{ item.Path() }; + const auto containsSpaces = std::find(fullPath.begin(), + fullPath.end(), + L' ') != fullPath.end(); + + auto lock = _terminal->LockForWriting(); + + if (containsSpaces) + { + fullPath.insert(0, L"\""); + fullPath += L"\""; + } + + allPaths += fullPath; + } + _SendInputToConnection(allPaths); + } + } + } + + // Method Description: + // - Synchronous handler for the "Drop" event. We'll dispatch the async + // _DoDragDrop method to handle this, because getting information about + // the file that was potentially dropped onto us must be done off the UI + // thread. + // Arguments: + // - e: The DragEventArgs from the Drop event + // Return Value: + // - + void TermControl::_DragDropHandler(Windows::Foundation::IInspectable const& /*sender*/, + DragEventArgs const& e) + { + // Dispatch an async method to handle the drop event. + _DoDragDrop(e); + } + + // Method Description: + // - Handle the DragOver event. We'll signal that the drag operation we + // support is the "copy" operation, and we'll also customize the + // appearance of the drag-drop UI, by removing the preview and setting a + // custom caption. For more information, see + // https://docs.microsoft.com/en-us/windows/uwp/design/input/drag-and-drop#customize-the-ui + // Arguments: + // - e: The DragEventArgs from the DragOver event + // Return Value: + // - + void TermControl::_DragOverHandler(Windows::Foundation::IInspectable const& /*sender*/, + DragEventArgs const& e) + { + if (!e.DataView().Contains(StandardDataFormats::StorageItems())) + { + // We can't do anything for non-storageitems right now. + return; + } + + // Make sure to set the AcceptedOperation, so that we can later receive the path in the Drop event + e.AcceptedOperation(DataPackageOperation::Copy); + + // Sets custom UI text + e.DragUIOverride().Caption(RS_(L"DragFileCaption")); + // Sets if the caption is visible + e.DragUIOverride().IsCaptionVisible(true); + // Sets if the dragged content is visible + e.DragUIOverride().IsContentVisible(false); + // Sets if the glyph is visibile + e.DragUIOverride().IsGlyphVisible(false); + } + + // -------------------------------- WinRT Events --------------------------------- + // Winrt events need a method for adding a callback to the event and removing the callback. + // These macros will define them both for you. + DEFINE_EVENT(TermControl, TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs); + DEFINE_EVENT(TermControl, FontSizeChanged, _fontSizeChangedHandlers, TerminalControl::FontSizeChangedEventArgs); + DEFINE_EVENT(TermControl, ScrollPositionChanged, _scrollPositionChangedHandlers, TerminalControl::ScrollPositionChangedEventArgs); + + DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TermControl, PasteFromClipboard, _clipboardPasteHandlers, TerminalControl::TermControl, TerminalControl::PasteFromClipboardEventArgs); + DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TermControl, CopyToClipboard, _clipboardCopyHandlers, TerminalControl::TermControl, TerminalControl::CopyToClipboardEventArgs); + // clang-format on +} diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index c566c440f35..a0290bccce8 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -162,6 +162,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation Timestamp _lastMouseClick; unsigned int _multiClickCounter; std::optional _lastMouseClickPos; + std::optional _clickDragStartPos{ std::nullopt }; // Event revokers -- we need to deregister ourselves before we die, // lest we get callbacks afterwards. diff --git a/src/cascadia/TerminalControl/TermControlUiaProvider.cpp b/src/cascadia/TerminalControl/TermControlUiaProvider.cpp index 353d30a28bf..b2a30e43511 100644 --- a/src/cascadia/TerminalControl/TermControlUiaProvider.cpp +++ b/src/cascadia/TerminalControl/TermControlUiaProvider.cpp @@ -95,6 +95,11 @@ const winrt::Windows::UI::Xaml::Thickness TermControlUiaProvider::GetPadding() c return _termControl->GetPadding(); } +void TermControlUiaProvider::ChangeViewport(const SMALL_RECT NewWindow) +{ + _termControl->ScrollViewport(NewWindow.Top); +} + HRESULT TermControlUiaProvider::GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _Out_ std::deque>& result) { try diff --git a/src/cascadia/TerminalControl/TermControlUiaProvider.hpp b/src/cascadia/TerminalControl/TermControlUiaProvider.hpp index c90d3bf6ca3..0c41fa53d21 100644 --- a/src/cascadia/TerminalControl/TermControlUiaProvider.hpp +++ b/src/cascadia/TerminalControl/TermControlUiaProvider.hpp @@ -45,6 +45,7 @@ namespace Microsoft::Terminal const COORD GetFontSize() const; const winrt::Windows::UI::Xaml::Thickness GetPadding() const; + void ChangeViewport(const SMALL_RECT NewWindow) override; protected: HRESULT GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _Out_ std::deque>& selectionRanges) override; diff --git a/src/cascadia/TerminalControl/UiaTextRange.cpp b/src/cascadia/TerminalControl/UiaTextRange.cpp index 45e907bd856..35169adcc36 100644 --- a/src/cascadia/TerminalControl/UiaTextRange.cpp +++ b/src/cascadia/TerminalControl/UiaTextRange.cpp @@ -8,6 +8,7 @@ using namespace Microsoft::Terminal; using namespace Microsoft::Console::Types; using namespace Microsoft::WRL; +using namespace winrt::Windows::Graphics::Display; HRESULT UiaTextRange::GetSelectionRanges(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* pProvider, @@ -105,9 +106,10 @@ IFACEMETHODIMP UiaTextRange::Clone(_Outptr_result_maybenull_ ITextRangeProvider* return S_OK; } -void UiaTextRange::_ChangeViewport(const SMALL_RECT /*NewWindow*/) +void UiaTextRange::_ChangeViewport(const SMALL_RECT NewWindow) { - // TODO GitHub #2361: Update viewport when calling UiaTextRangeBase::ScrollIntoView() + auto provider = static_cast(_pProvider); + provider->ChangeViewport(NewWindow); } // Method Description: @@ -121,21 +123,59 @@ void UiaTextRange::_TranslatePointToScreen(LPPOINT clientPoint) const { auto provider = static_cast(_pProvider); + auto includeOffsets = [](long clientPos, double termControlPos, double padding, double scaleFactor) { + auto result = base::ClampedNumeric(clientPos); + result += padding; + result *= scaleFactor; + result += termControlPos; + return result; + }; + // update based on TermControl location (important for Panes) UiaRect boundingRect; THROW_IF_FAILED(provider->get_BoundingRectangle(&boundingRect)); - clientPoint->x += gsl::narrow(boundingRect.left); - clientPoint->y += gsl::narrow(boundingRect.top); // update based on TermControl padding - auto padding = provider->GetPadding(); - clientPoint->x += gsl::narrow(padding.Left); - clientPoint->y += gsl::narrow(padding.Top); + const auto padding = provider->GetPadding(); + + // Get scale factor for display + const auto scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel(); + + clientPoint->x = includeOffsets(clientPoint->x, boundingRect.left, padding.Left, scaleFactor); + clientPoint->y = includeOffsets(clientPoint->y, boundingRect.top, padding.Top, scaleFactor); } -void UiaTextRange::_TranslatePointFromScreen(LPPOINT /*screenPoint*/) const +// Method Description: +// - Transform coordinates relative to the screen to relative to the client +// Arguments: +// - screenPoint: coordinates relative to the screen where +// (0,0) is the top-left of the screen +// Return Value: +// - +void UiaTextRange::_TranslatePointFromScreen(LPPOINT screenPoint) const { - // TODO GitHub #2103: NON-HWND IMPLEMENTATION OF SCREENTOCLIENT() + auto provider = static_cast(_pProvider); + + auto includeOffsets = [](long screenPos, double termControlPos, double padding, double scaleFactor) { + auto result = base::ClampedNumeric(screenPos); + result -= termControlPos; + result /= scaleFactor; + result -= padding; + return result; + }; + + // update based on TermControl location (important for Panes) + UiaRect boundingRect; + THROW_IF_FAILED(provider->get_BoundingRectangle(&boundingRect)); + + // update based on TermControl padding + const auto padding = provider->GetPadding(); + + // Get scale factor for display + const auto scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel(); + + screenPoint->x = includeOffsets(screenPoint->x, boundingRect.left, padding.Left, scaleFactor); + screenPoint->y = includeOffsets(screenPoint->y, boundingRect.top, padding.Top, scaleFactor); } const COORD UiaTextRange::_getScreenFontSize() const diff --git a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp index e6b4be530b7..112cf396432 100644 --- a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp @@ -128,6 +128,8 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final g.EnableConptyModeForTests(); expectedOutput.clear(); + _checkConptyOutput = true; + _logConpty = false; return true; } @@ -152,6 +154,8 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final TEST_METHOD(WriteAFewSimpleLines); TEST_METHOD(PassthroughClearScrollback); + TEST_METHOD(MoveCursorAtEOL); + private: bool _writeCallback(const char* const pch, size_t const cch); void _flushFirstFrame(); @@ -344,6 +348,70 @@ void ConptyRoundtripTests::WriteAFewSimpleLines() verifyData(termTb); } +void ConptyRoundtripTests::MoveCursorAtEOL() +{ + // This is a test for GH#1245 + VERIFY_IS_NOT_NULL(_pVtRenderEngine.get()); + + auto& g = ServiceLocator::LocateGlobals(); + auto& renderer = *g.pRender; + auto& gci = g.getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer(); + auto& hostSm = si.GetStateMachine(); + auto& hostTb = si.GetTextBuffer(); + auto& termTb = *term->_buffer; + + _flushFirstFrame(); + + Log::Comment(NoThrowString().Format( + L"Write exactly a full line of text")); + hostSm.ProcessString(std::wstring(TerminalViewWidth, L'A')); + + auto verifyData0 = [](TextBuffer& tb) { + auto iter = tb.GetCellDataAt({ 0, 0 }); + TestUtils::VerifySpanOfText(L"A", iter, 0, TerminalViewWidth); + TestUtils::VerifySpanOfText(L" ", iter, 0, TerminalViewWidth); + }; + + verifyData0(hostTb); + + // TODO: GH#405/#4415 - Before #405 merges, the VT sequences conpty emits + // might change, but the buffer contents shouldn't. + // If they do change and these tests break, that's to be expected. + expectedOutput.push_back(std::string(TerminalViewWidth, 'A')); + expectedOutput.push_back("\x1b[1;80H"); + + VERIFY_SUCCEEDED(renderer.PaintFrame()); + + verifyData0(termTb); + + Log::Comment(NoThrowString().Format( + L"Emulate backspacing at a bash prompt when the previous line wrapped.\n" + L"We'll move the cursor up to the last char of the prev line, and erase it.")); + hostSm.ProcessString(L"\x1b[1;80H"); + hostSm.ProcessString(L"\x1b[K"); + + auto verifyData1 = [](TextBuffer& tb) { + auto iter = tb.GetCellDataAt({ 0, 0 }); + // There should be 79 'A's, followed by a space, and the following line should be blank. + TestUtils::VerifySpanOfText(L"A", iter, 0, TerminalViewWidth - 1); + TestUtils::VerifySpanOfText(L" ", iter, 0, 1); + TestUtils::VerifySpanOfText(L" ", iter, 0, TerminalViewWidth); + + auto& cursor = tb.GetCursor(); + VERIFY_ARE_EQUAL(TerminalViewWidth - 1, cursor.GetPosition().X); + VERIFY_ARE_EQUAL(0, cursor.GetPosition().Y); + }; + + verifyData1(hostTb); + + expectedOutput.push_back(" "); + expectedOutput.push_back("\x1b[1;80H"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); + + verifyData1(termTb); +} + void ConptyRoundtripTests::PassthroughClearScrollback() { Log::Comment(NoThrowString().Format( diff --git a/src/interactivity/onecore/BgfxEngine.cpp b/src/interactivity/onecore/BgfxEngine.cpp index b73c769c7a5..32d52760bdf 100644 --- a/src/interactivity/onecore/BgfxEngine.cpp +++ b/src/interactivity/onecore/BgfxEngine.cpp @@ -138,7 +138,7 @@ BgfxEngine::BgfxEngine(PVOID SharedViewBase, LONG DisplayHeight, LONG DisplayWid for (SHORT j = 0; j < _displayWidth; j++) { NewRun[j].Character = L' '; - NewRun[j].Atribute = 0; + NewRun[j].Attribute = 0; } } @@ -157,7 +157,7 @@ BgfxEngine::BgfxEngine(PVOID SharedViewBase, LONG DisplayHeight, LONG DisplayWid for (size_t i = 0; i < clusters.size() && i < (size_t)_displayWidth; i++) { NewRun[coord.X + i].Character = clusters.at(i).GetTextAsSingle(); - NewRun[coord.X + i].Atribute = _currentLegacyColorAttribute; + NewRun[coord.X + i].Attribute = _currentLegacyColorAttribute; } return S_OK; diff --git a/src/interactivity/win32/screenInfoUiaProvider.hpp b/src/interactivity/win32/screenInfoUiaProvider.hpp index 3b39a32eec8..7984621835d 100644 --- a/src/interactivity/win32/screenInfoUiaProvider.hpp +++ b/src/interactivity/win32/screenInfoUiaProvider.hpp @@ -41,7 +41,7 @@ namespace Microsoft::Console::Interactivity::Win32 IFACEMETHODIMP get_FragmentRoot(_COM_Outptr_result_maybenull_ IRawElementProviderFragmentRoot** ppProvider) override; HWND GetWindowHandle() const; - void ChangeViewport(const SMALL_RECT NewWindow); + void ChangeViewport(const SMALL_RECT NewWindow) override; protected: HRESULT GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _Out_ std::deque>& selectionRanges) override; diff --git a/src/renderer/base/thread.cpp b/src/renderer/base/thread.cpp index 8dd6784cd9a..99efd4b2e35 100644 --- a/src/renderer/base/thread.cpp +++ b/src/renderer/base/thread.cpp @@ -17,6 +17,8 @@ RenderThread::RenderThread() : _fKeepRunning(true), _hPaintEnabledEvent(nullptr) { + _fNextFrameRequested.clear(); + _fPainting.clear(); } RenderThread::~RenderThread() @@ -158,10 +160,21 @@ DWORD WINAPI RenderThread::_ThreadProc() while (_fKeepRunning) { WaitForSingleObject(_hPaintEnabledEvent, INFINITE); - WaitForSingleObject(_hEvent, INFINITE); + + // Skip waiting if next frame is requested. + if (_fNextFrameRequested.test_and_set(std::memory_order_relaxed)) + { + _fNextFrameRequested.clear(std::memory_order_relaxed); + } + else + { + WaitForSingleObject(_hEvent, INFINITE); + } ResetEvent(_hPaintCompletedEvent); + _fPainting.test_and_set(std::memory_order_acquire); + LOG_IF_FAILED(_pRenderer->PaintFrame()); SetEvent(_hPaintCompletedEvent); @@ -171,6 +184,8 @@ DWORD WINAPI RenderThread::_ThreadProc() { Sleep(s_FrameLimitMilliseconds); } + + _fPainting.clear(std::memory_order_release); } return S_OK; @@ -178,6 +193,14 @@ DWORD WINAPI RenderThread::_ThreadProc() void RenderThread::NotifyPaint() { + // If we are currently painting a frame, set _fNextFrameRequested flag + // to indicate we want to paint next frame immediately. + if (_fPainting.test_and_set(std::memory_order_acquire)) + { + _fNextFrameRequested.test_and_set(std::memory_order_relaxed); + return; + } + SetEvent(_hEvent); } diff --git a/src/renderer/base/thread.hpp b/src/renderer/base/thread.hpp index 7dd20535c0e..3e991f61836 100644 --- a/src/renderer/base/thread.hpp +++ b/src/renderer/base/thread.hpp @@ -47,5 +47,7 @@ namespace Microsoft::Console::Render IRenderer* _pRenderer; // Non-ownership pointer bool _fKeepRunning; + std::atomic_flag _fNextFrameRequested; + std::atomic_flag _fPainting; }; } diff --git a/src/renderer/dx/CustomTextLayout.cpp b/src/renderer/dx/CustomTextLayout.cpp index 9ea3cdf3bf6..d00700d274a 100644 --- a/src/renderer/dx/CustomTextLayout.cpp +++ b/src/renderer/dx/CustomTextLayout.cpp @@ -404,30 +404,8 @@ CustomTextLayout::CustomTextLayout(gsl::not_null const factory // Offsets is how far to move the origin (in pixels) from where it is auto& offset = _glyphOffsets.at(i); - // Get how many columns we expected the glyph to have and multiply into pixels. - UINT16 columns = 0; - { - // Because of typographic features such as ligatures, it is well possible for a glyph to represent - // multiple code points. Previous calls to IDWriteTextAnalyzer::GetGlyphs stores the mapping - // information between code points and glyphs in _glyphClusters. - // To properly allocate the columns for such glyphs, we need to find all characters that this glyph - // is representing and add column counts for all the characters together. - - // Find the range for current glyph run in _glyphClusters. - const auto runStartIterator = _glyphClusters.begin() + run.textStart; - const auto runEndIterator = _glyphClusters.begin() + run.textStart + run.textLength; - - // Find the range of characters that the current glyph is representing. - const auto firstIterator = std::find(runStartIterator, runEndIterator, i - run.glyphStart); - const auto lastIterator = std::find(runStartIterator, runEndIterator, i - run.glyphStart + 1); - - // Add all allocated column counts together. - for (auto j = firstIterator; j < lastIterator; j++) - { - const auto charIndex = std::distance(_glyphClusters.begin(), j); - columns += _textClusterColumns.at(charIndex); - } - } + // Get how many columns we expected the glyph to have and mutiply into pixels. + const auto columns = _textClusterColumns.at(i); const auto advanceExpected = static_cast(columns * _width); // If what we expect is bigger than what we have... pad it out. diff --git a/src/renderer/vt/XtermEngine.cpp b/src/renderer/vt/XtermEngine.cpp index 2c1d8a08ed1..dfbe434f2d2 100644 --- a/src/renderer/vt/XtermEngine.cpp +++ b/src/renderer/vt/XtermEngine.cpp @@ -258,6 +258,20 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe, hr = _Write(seq); } } + else if (_delayedEolWrap) + { + // GH#1245, GH#357 - If we were in the delayed EOL wrap state, make + // sure to _manually_ position the cursor now, with a full CUP + // sequence, don't try and be clever with \b or \r or other control + // sequences. Different terminals (conhost, gnome-terminal, wt) all + // behave differently with how the cursor behaves at an end of line. + // This is the only solution that works in all of them, and also + // works wrapped lines emitted by conpty. + // + // Make sure to do this _after_ the possible \r\n branch above, + // otherwise we might accidentally break wrapped lines (GH#405) + hr = _CursorPosition(coord); + } else if (coord.X == 0 && coord.Y == _lastText.Y) { // Start of this line @@ -298,6 +312,7 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe, _newBottomLine = false; } _deferredCursorPos = INVALID_COORDS; + _delayedEolWrap = false; return hr; } diff --git a/src/renderer/vt/paint.cpp b/src/renderer/vt/paint.cpp index 10642d158cc..1c20a85a1a9 100644 --- a/src/renderer/vt/paint.cpp +++ b/src/renderer/vt/paint.cpp @@ -450,7 +450,7 @@ using namespace Microsoft::Console::Types; RETURN_IF_FAILED(VtEngine::_WriteTerminalUtf8(wstr)); // Update our internal tracker of the cursor's position. - // See MSFT:20266233 + // See MSFT:20266233 (which is also GH#357) // If the cursor is at the rightmost column of the terminal, and we write a // space, the cursor won't actually move to the next cell (which would // be {0, _lastText.Y++}). The cursor will stay visibly in that last @@ -461,10 +461,24 @@ using namespace Microsoft::Console::Types; // we'll determine that we need to emit a \b to put the cursor in the // right position. This is wrong, and will cause us to move the cursor // back one character more than we wanted. - if (_lastText.X < _lastViewport.RightInclusive()) + // + // GH#1245: This needs to be RightExclusive, _not_ inclusive. Otherwise, we + // won't update our internal cursor position tracker correctly at the last + // character of the row. + if (_lastText.X < _lastViewport.RightExclusive()) { _lastText.X += static_cast(columnsActual); } + // GH#1245: If we wrote the exactly last char of the row, then we're in the + // "delayed EOL wrap" state. Different terminals (conhost, gnome-terminal, + // wt) all behave differently with how the cursor behaves at an end of line. + // Mark that we're in the delayed EOL wrap state - we don't want to be + // clever about how we move the cursor in this state, since different + // terminals will handle a backspace differently in this state. + if (_lastText.X >= _lastViewport.RightInclusive()) + { + _delayedEolWrap = true; + } short sNumSpaces; try diff --git a/src/renderer/vt/tracing.cpp b/src/renderer/vt/tracing.cpp index 3c5f4e0aa63..f03918d47d4 100644 --- a/src/renderer/vt/tracing.cpp +++ b/src/renderer/vt/tracing.cpp @@ -67,12 +67,15 @@ std::string toPrintableString(const std::string_view& inString) void RenderTracing::TraceString(const std::string_view& instr) const { #ifndef UNIT_TESTING - const std::string _seq = toPrintableString(instr); - const char* const seq = _seq.c_str(); - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TraceString", - TraceLoggingString(seq), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); + if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0)) + { + const std::string _seq = toPrintableString(instr); + const char* const seq = _seq.c_str(); + TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, + "VtEngine_TraceString", + TraceLoggingString(seq), + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); + } #else UNREFERENCED_PARAMETER(instr); #endif UNIT_TESTING @@ -117,12 +120,15 @@ std::string _CoordToString(const COORD& c) void RenderTracing::TraceInvalidate(const Viewport invalidRect) const { #ifndef UNIT_TESTING - const auto invalidatedStr = _ViewportToString(invalidRect); - const auto invalidated = invalidatedStr.c_str(); - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TraceInvalidate", - TraceLoggingString(invalidated), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); + if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0)) + { + const auto invalidatedStr = _ViewportToString(invalidRect); + const auto invalidated = invalidatedStr.c_str(); + TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, + "VtEngine_TraceInvalidate", + TraceLoggingString(invalidated), + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); + } #else UNREFERENCED_PARAMETER(invalidRect); #endif UNIT_TESTING @@ -131,12 +137,15 @@ void RenderTracing::TraceInvalidate(const Viewport invalidRect) const void RenderTracing::TraceInvalidateAll(const Viewport viewport) const { #ifndef UNIT_TESTING - const auto invalidatedStr = _ViewportToString(viewport); - const auto invalidatedAll = invalidatedStr.c_str(); - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TraceInvalidateAll", - TraceLoggingString(invalidatedAll), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); + if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0)) + { + const auto invalidatedStr = _ViewportToString(viewport); + const auto invalidatedAll = invalidatedStr.c_str(); + TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, + "VtEngine_TraceInvalidateAll", + TraceLoggingString(invalidatedAll), + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); + } #else UNREFERENCED_PARAMETER(viewport); #endif UNIT_TESTING @@ -162,21 +171,24 @@ void RenderTracing::TraceStartPaint(const bool quickReturn, const bool cursorMoved) const { #ifndef UNIT_TESTING - const auto invalidatedStr = _ViewportToString(invalidRect); - const auto invalidated = invalidatedStr.c_str(); - const auto lastViewStr = _ViewportToString(lastViewport); - const auto lastView = lastViewStr.c_str(); - const auto scrollDeltaStr = _CoordToString(scrollDelt); - const auto scrollDelta = scrollDeltaStr.c_str(); - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TraceStartPaint", - TraceLoggingBool(quickReturn), - TraceLoggingBool(invalidRectUsed), - TraceLoggingString(invalidated), - TraceLoggingString(lastView), - TraceLoggingString(scrollDelta), - TraceLoggingBool(cursorMoved), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); + if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0)) + { + const auto invalidatedStr = _ViewportToString(invalidRect); + const auto invalidated = invalidatedStr.c_str(); + const auto lastViewStr = _ViewportToString(lastViewport); + const auto lastView = lastViewStr.c_str(); + const auto scrollDeltaStr = _CoordToString(scrollDelt); + const auto scrollDelta = scrollDeltaStr.c_str(); + TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, + "VtEngine_TraceStartPaint", + TraceLoggingBool(quickReturn), + TraceLoggingBool(invalidRectUsed), + TraceLoggingString(invalidated), + TraceLoggingString(lastView), + TraceLoggingString(scrollDelta), + TraceLoggingBool(cursorMoved), + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); + } #else UNREFERENCED_PARAMETER(quickReturn); UNREFERENCED_PARAMETER(invalidRectUsed); @@ -200,12 +212,15 @@ void RenderTracing::TraceEndPaint() const void RenderTracing::TraceLastText(const COORD lastTextPos) const { #ifndef UNIT_TESTING - const auto lastTextStr = _CoordToString(lastTextPos); - const auto lastText = lastTextStr.c_str(); - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TraceLastText", - TraceLoggingString(lastText), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); + if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0)) + { + const auto lastTextStr = _CoordToString(lastTextPos); + const auto lastText = lastTextStr.c_str(); + TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, + "VtEngine_TraceLastText", + TraceLoggingString(lastText), + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); + } #else UNREFERENCED_PARAMETER(lastTextPos); #endif UNIT_TESTING diff --git a/src/renderer/vt/vtrenderer.hpp b/src/renderer/vt/vtrenderer.hpp index 27658e7f9c8..5e9cdb1e716 100644 --- a/src/renderer/vt/vtrenderer.hpp +++ b/src/renderer/vt/vtrenderer.hpp @@ -144,6 +144,8 @@ namespace Microsoft::Console::Render Microsoft::Console::VirtualTerminal::RenderTracing _trace; bool _inResizeRequest{ false }; + bool _delayedEolWrap{ false }; + [[nodiscard]] HRESULT _Write(std::string_view const str) noexcept; [[nodiscard]] HRESULT _WriteFormattedString(const std::string* const pFormat, ...) noexcept; [[nodiscard]] HRESULT _Flush() noexcept; diff --git a/src/renderer/wddmcon/WddmConRenderer.cpp b/src/renderer/wddmcon/WddmConRenderer.cpp index 23804ec31f2..d9404f9dade 100644 --- a/src/renderer/wddmcon/WddmConRenderer.cpp +++ b/src/renderer/wddmcon/WddmConRenderer.cpp @@ -248,10 +248,10 @@ bool WddmConEngine::IsInitialized() NewChar = &_displayState[rowIndex]->New[colIndex]; OldChar->Character = NewChar->Character; - OldChar->Atribute = NewChar->Atribute; + OldChar->Attribute = NewChar->Attribute; NewChar->Character = L' '; - NewChar->Atribute = 0x0; + NewChar->Attribute = 0x0; } } @@ -275,10 +275,10 @@ bool WddmConEngine::IsInitialized() NewChar = &_displayState[coord.Y]->New[coord.X + i]; OldChar->Character = NewChar->Character; - OldChar->Atribute = NewChar->Atribute; + OldChar->Attribute = NewChar->Attribute; NewChar->Character = clusters.at(i).GetTextAsSingle(); - NewChar->Atribute = _currentLegacyColorAttribute; + NewChar->Attribute = _currentLegacyColorAttribute; } return WDDMConUpdateDisplay(_hWddmConCtx, _displayState[coord.Y], FALSE); diff --git a/src/renderer/wddmcon/main.cxx b/src/renderer/wddmcon/main.cxx index 11ca30730fe..b4d585603ef 100644 --- a/src/renderer/wddmcon/main.cxx +++ b/src/renderer/wddmcon/main.cxx @@ -617,7 +617,7 @@ WDDMConUpdateDisplay( pCtx->pwszGlyphRunAccel[ColumnIndex] = pRowInformation->New[ColumnIndex].Character; if (ColumnIndexReadAhead != pCtx->DisplaySize.Width) { - while (pRowInformation->New[ColumnIndex].Atribute == pRowInformation->New[ColumnIndexReadAhead].Atribute) { + while (pRowInformation->New[ColumnIndex].Attribute == pRowInformation->New[ColumnIndexReadAhead].Attribute) { if (memcmp(&pRowInformation->New[ColumnIndexReadAhead], &pRowInformation->Old[ColumnIndexReadAhead], sizeof(CD_IO_CHARACTER)) == 0) { @@ -667,11 +667,11 @@ WDDMConUpdateDisplay( } pCtx->pD2DColorBrush->SetColor( - ConsoleColors[(pCharacter->Atribute >> 4) & 0xF]); + ConsoleColors[(pCharacter->Attribute >> 4) & 0xF]); pCtx->pD2DSwapChainRT->FillRectangle(&GlyphRectangle, pCtx->pD2DColorBrush); - pCtx->pD2DColorBrush->SetColor(ConsoleColors[pCharacter->Atribute & 0xF]); + pCtx->pD2DColorBrush->SetColor(ConsoleColors[pCharacter->Attribute & 0xF]); pCtx->pD2DSwapChainRT->DrawTextLayout(Origin, pTextLayout, pCtx->pD2DColorBrush, diff --git a/src/types/ScreenInfoUiaProviderBase.h b/src/types/ScreenInfoUiaProviderBase.h index 82bdd31fcb0..97c7a8f698c 100644 --- a/src/types/ScreenInfoUiaProviderBase.h +++ b/src/types/ScreenInfoUiaProviderBase.h @@ -47,6 +47,7 @@ namespace Microsoft::Console::Types ~ScreenInfoUiaProviderBase() = default; [[nodiscard]] HRESULT Signal(_In_ EVENTID id); + virtual void ChangeViewport(const SMALL_RECT NewWindow) = 0; // IRawElementProviderSimple methods IFACEMETHODIMP get_ProviderOptions(_Out_ ProviderOptions* pOptions) noexcept override;