diff --git a/.github/ISSUE_TEMPLATE/Bug_Report.md b/.github/ISSUE_TEMPLATE/Bug_Report.md index d11fa6de851..580ba8fe308 100644 --- a/.github/ISSUE_TEMPLATE/Bug_Report.md +++ b/.github/ISSUE_TEMPLATE/Bug_Report.md @@ -26,6 +26,8 @@ This bug tracker is monitored by Windows Terminal development team and other tec **Important: When reporting BSODs or security issues, DO NOT attach memory dumps, logs, or traces to Github issues**. Instead, send dumps/traces to secure@microsoft.com, referencing this GitHub issue. +If this is an application crash, please also provide a Feedback Hub submission link so we can find your diagnostic data on the backend. Use the category "Apps > Windows Terminal (Preview)" and choose "Share My Feedback" after submission to get the link. + Please use this form and describe your issue, concisely but precisely, with as much detail as possible. --> @@ -33,7 +35,7 @@ Please use this form and describe your issue, concisely but precisely, with as m # Environment ```none -Windows build number: [run "ver" at a command prompt] +Windows build number: [run `[Environment]::OSVersion` for powershell, or `ver` for cmd] Windows Terminal version (if applicable): Any other software? diff --git a/OpenConsole.sln b/OpenConsole.sln index 788474d06c8..2824b840799 100644 --- a/OpenConsole.sln +++ b/OpenConsole.sln @@ -243,6 +243,13 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalAppLib", "src\casca {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LocalTests_TerminalApp", "src\cascadia\LocalTests_TerminalApp\TerminalApp.LocalTests.vcxproj", "{CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}" + ProjectSection(ProjectDependencies) = postProject + {CA5CAD1A-9A12-429C-B551-8562EC954746} = {CA5CAD1A-9A12-429C-B551-8562EC954746} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winconpty", "src\winconpty\winconpty.vcxproj", "{58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution AuditMode|ARM64 = AuditMode|ARM64 @@ -257,14 +264,8 @@ Global EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.AuditMode|ARM64.Build.0 = Release|ARM64 - {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.AuditMode|ARM64.Deploy.0 = Release|ARM64 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.AuditMode|x64.ActiveCfg = Release|x64 - {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.AuditMode|x64.Build.0 = Release|x64 - {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.AuditMode|x64.Deploy.0 = Release|x64 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.AuditMode|x86.ActiveCfg = Release|x86 - {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.AuditMode|x86.Build.0 = Release|x86 - {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.AuditMode|x86.Deploy.0 = Release|x86 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|ARM64.Deploy.0 = Debug|ARM64 @@ -284,11 +285,8 @@ Global {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|x86.Build.0 = Release|x86 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|x86.Deploy.0 = Release|x86 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|ARM64.Build.0 = Release|ARM64 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|x64.ActiveCfg = Release|x64 - {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|x64.Build.0 = Release|x64 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|x86.ActiveCfg = Release|Win32 - {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|x86.Build.0 = Release|Win32 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Debug|ARM64.ActiveCfg = Debug|ARM64 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Debug|ARM64.Build.0 = Debug|ARM64 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Debug|x64.ActiveCfg = Debug|x64 @@ -302,11 +300,8 @@ Global {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Release|x86.ActiveCfg = Release|Win32 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Release|x86.Build.0 = Release|Win32 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.AuditMode|ARM64.Build.0 = Release|ARM64 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.AuditMode|x64.ActiveCfg = Release|x64 - {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.AuditMode|x64.Build.0 = Release|x64 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.AuditMode|x86.ActiveCfg = Release|Win32 - {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.AuditMode|x86.Build.0 = Release|Win32 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Debug|ARM64.ActiveCfg = Debug|ARM64 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Debug|ARM64.Build.0 = Debug|ARM64 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Debug|x64.ActiveCfg = Debug|x64 @@ -320,11 +315,8 @@ Global {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Release|x86.ActiveCfg = Release|Win32 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Release|x86.Build.0 = Release|Win32 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.AuditMode|ARM64.Build.0 = Release|ARM64 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.AuditMode|x64.ActiveCfg = Release|x64 - {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.AuditMode|x64.Build.0 = Release|x64 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.AuditMode|x86.ActiveCfg = Release|Win32 - {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.AuditMode|x86.Build.0 = Release|Win32 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Debug|ARM64.ActiveCfg = Debug|ARM64 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Debug|ARM64.Build.0 = Debug|ARM64 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Debug|x64.ActiveCfg = Debug|x64 @@ -338,11 +330,8 @@ Global {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Release|x86.ActiveCfg = Release|Win32 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Release|x86.Build.0 = Release|Win32 {3AE13314-1939-4DFA-9C14-38CA0834050C}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {3AE13314-1939-4DFA-9C14-38CA0834050C}.AuditMode|ARM64.Build.0 = Release|ARM64 {3AE13314-1939-4DFA-9C14-38CA0834050C}.AuditMode|x64.ActiveCfg = Release|x64 - {3AE13314-1939-4DFA-9C14-38CA0834050C}.AuditMode|x64.Build.0 = Release|x64 {3AE13314-1939-4DFA-9C14-38CA0834050C}.AuditMode|x86.ActiveCfg = Release|Win32 - {3AE13314-1939-4DFA-9C14-38CA0834050C}.AuditMode|x86.Build.0 = Release|Win32 {3AE13314-1939-4DFA-9C14-38CA0834050C}.Debug|ARM64.ActiveCfg = Debug|ARM64 {3AE13314-1939-4DFA-9C14-38CA0834050C}.Debug|ARM64.Build.0 = Debug|ARM64 {3AE13314-1939-4DFA-9C14-38CA0834050C}.Debug|x64.ActiveCfg = Debug|x64 @@ -356,11 +345,8 @@ Global {3AE13314-1939-4DFA-9C14-38CA0834050C}.Release|x86.ActiveCfg = Release|Win32 {3AE13314-1939-4DFA-9C14-38CA0834050C}.Release|x86.Build.0 = Release|Win32 {DCF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {DCF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|ARM64.Build.0 = Release|ARM64 {DCF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|x64.ActiveCfg = Release|x64 - {DCF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|x64.Build.0 = Release|x64 {DCF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|x86.ActiveCfg = Release|Win32 - {DCF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|x86.Build.0 = Release|Win32 {DCF55140-EF6A-4736-A403-957E4F7430BB}.Debug|ARM64.ActiveCfg = Debug|ARM64 {DCF55140-EF6A-4736-A403-957E4F7430BB}.Debug|ARM64.Build.0 = Debug|ARM64 {DCF55140-EF6A-4736-A403-957E4F7430BB}.Debug|x64.ActiveCfg = Debug|x64 @@ -374,11 +360,8 @@ Global {DCF55140-EF6A-4736-A403-957E4F7430BB}.Release|x86.ActiveCfg = Release|Win32 {DCF55140-EF6A-4736-A403-957E4F7430BB}.Release|x86.Build.0 = Release|Win32 {1CF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {1CF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|ARM64.Build.0 = Release|ARM64 {1CF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|x64.ActiveCfg = Release|x64 - {1CF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|x64.Build.0 = Release|x64 {1CF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|x86.ActiveCfg = Release|Win32 - {1CF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|x86.Build.0 = Release|Win32 {1CF55140-EF6A-4736-A403-957E4F7430BB}.Debug|ARM64.ActiveCfg = Debug|ARM64 {1CF55140-EF6A-4736-A403-957E4F7430BB}.Debug|ARM64.Build.0 = Debug|ARM64 {1CF55140-EF6A-4736-A403-957E4F7430BB}.Debug|x64.ActiveCfg = Debug|x64 @@ -392,11 +375,8 @@ Global {1CF55140-EF6A-4736-A403-957E4F7430BB}.Release|x86.ActiveCfg = Release|Win32 {1CF55140-EF6A-4736-A403-957E4F7430BB}.Release|x86.Build.0 = Release|Win32 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.AuditMode|ARM64.Build.0 = Release|ARM64 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.AuditMode|x64.ActiveCfg = Release|x64 - {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.AuditMode|x64.Build.0 = Release|x64 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.AuditMode|x86.ActiveCfg = Release|Win32 - {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.AuditMode|x86.Build.0 = Release|Win32 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Debug|ARM64.ActiveCfg = Debug|ARM64 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Debug|ARM64.Build.0 = Debug|ARM64 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Debug|x64.ActiveCfg = Debug|x64 @@ -410,11 +390,8 @@ Global {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Release|x86.ActiveCfg = Release|Win32 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Release|x86.Build.0 = Release|Win32 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {1C959542-BAC2-4E55-9A6D-13251914CBB9}.AuditMode|ARM64.Build.0 = Release|ARM64 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.AuditMode|x64.ActiveCfg = Release|x64 - {1C959542-BAC2-4E55-9A6D-13251914CBB9}.AuditMode|x64.Build.0 = Release|x64 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.AuditMode|x86.ActiveCfg = Release|Win32 - {1C959542-BAC2-4E55-9A6D-13251914CBB9}.AuditMode|x86.Build.0 = Release|Win32 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Debug|ARM64.ActiveCfg = Debug|ARM64 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Debug|ARM64.Build.0 = Debug|ARM64 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Debug|x64.ActiveCfg = Debug|x64 @@ -428,11 +405,8 @@ Global {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Release|x86.ActiveCfg = Release|Win32 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Release|x86.Build.0 = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC954746}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {06EC74CB-9A12-429C-B551-8562EC954746}.AuditMode|ARM64.Build.0 = Release|ARM64 {06EC74CB-9A12-429C-B551-8562EC954746}.AuditMode|x64.ActiveCfg = Release|x64 - {06EC74CB-9A12-429C-B551-8562EC954746}.AuditMode|x64.Build.0 = Release|x64 {06EC74CB-9A12-429C-B551-8562EC954746}.AuditMode|x86.ActiveCfg = Release|Win32 - {06EC74CB-9A12-429C-B551-8562EC954746}.AuditMode|x86.Build.0 = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC954746}.Debug|ARM64.ActiveCfg = Debug|ARM64 {06EC74CB-9A12-429C-B551-8562EC954746}.Debug|ARM64.Build.0 = Debug|ARM64 {06EC74CB-9A12-429C-B551-8562EC954746}.Debug|x64.ActiveCfg = Debug|x64 @@ -446,11 +420,8 @@ Global {06EC74CB-9A12-429C-B551-8562EC954746}.Release|x86.ActiveCfg = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC954746}.Release|x86.Build.0 = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC954747}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {06EC74CB-9A12-429C-B551-8562EC954747}.AuditMode|ARM64.Build.0 = Release|ARM64 {06EC74CB-9A12-429C-B551-8562EC954747}.AuditMode|x64.ActiveCfg = Release|x64 - {06EC74CB-9A12-429C-B551-8562EC954747}.AuditMode|x64.Build.0 = Release|x64 {06EC74CB-9A12-429C-B551-8562EC954747}.AuditMode|x86.ActiveCfg = Release|Win32 - {06EC74CB-9A12-429C-B551-8562EC954747}.AuditMode|x86.Build.0 = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC954747}.Debug|ARM64.ActiveCfg = Debug|ARM64 {06EC74CB-9A12-429C-B551-8562EC954747}.Debug|ARM64.Build.0 = Debug|ARM64 {06EC74CB-9A12-429C-B551-8562EC954747}.Debug|x64.ActiveCfg = Debug|x64 @@ -464,11 +435,8 @@ Global {06EC74CB-9A12-429C-B551-8562EC954747}.Release|x86.ActiveCfg = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC954747}.Release|x86.Build.0 = Release|Win32 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {531C23E7-4B76-4C08-8AAD-04164CB628C9}.AuditMode|ARM64.Build.0 = Release|ARM64 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.AuditMode|x64.ActiveCfg = Release|x64 - {531C23E7-4B76-4C08-8AAD-04164CB628C9}.AuditMode|x64.Build.0 = Release|x64 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.AuditMode|x86.ActiveCfg = Release|Win32 - {531C23E7-4B76-4C08-8AAD-04164CB628C9}.AuditMode|x86.Build.0 = Release|Win32 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Debug|ARM64.ActiveCfg = Debug|ARM64 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Debug|ARM64.Build.0 = Debug|ARM64 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Debug|x64.ActiveCfg = Debug|x64 @@ -482,11 +450,8 @@ Global {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Release|x86.ActiveCfg = Release|Win32 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Release|x86.Build.0 = Release|Win32 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {531C23E7-4B76-4C08-8BBD-04164CB628C9}.AuditMode|ARM64.Build.0 = Release|ARM64 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.AuditMode|x64.ActiveCfg = Release|x64 - {531C23E7-4B76-4C08-8BBD-04164CB628C9}.AuditMode|x64.Build.0 = Release|x64 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.AuditMode|x86.ActiveCfg = Release|Win32 - {531C23E7-4B76-4C08-8BBD-04164CB628C9}.AuditMode|x86.Build.0 = Release|Win32 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Debug|ARM64.ActiveCfg = Debug|ARM64 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Debug|ARM64.Build.0 = Debug|ARM64 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Debug|x64.ActiveCfg = Debug|x64 @@ -500,11 +465,8 @@ Global {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Release|x86.ActiveCfg = Release|Win32 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Release|x86.Build.0 = Release|Win32 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.AuditMode|ARM64.Build.0 = Release|ARM64 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.AuditMode|x64.ActiveCfg = Release|x64 - {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.AuditMode|x64.Build.0 = Release|x64 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.AuditMode|x86.ActiveCfg = Release|Win32 - {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.AuditMode|x86.Build.0 = Release|Win32 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Debug|ARM64.ActiveCfg = Debug|ARM64 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Debug|ARM64.Build.0 = Debug|ARM64 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Debug|x64.ActiveCfg = Debug|x64 @@ -516,11 +478,8 @@ Global {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Release|x64.Build.0 = Release|x64 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Release|x86.ActiveCfg = Release|Win32 {12144E07-FE63-4D33-9231-748B8D8C3792}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {12144E07-FE63-4D33-9231-748B8D8C3792}.AuditMode|ARM64.Build.0 = Release|ARM64 {12144E07-FE63-4D33-9231-748B8D8C3792}.AuditMode|x64.ActiveCfg = Release|x64 - {12144E07-FE63-4D33-9231-748B8D8C3792}.AuditMode|x64.Build.0 = Release|x64 {12144E07-FE63-4D33-9231-748B8D8C3792}.AuditMode|x86.ActiveCfg = Release|Win32 - {12144E07-FE63-4D33-9231-748B8D8C3792}.AuditMode|x86.Build.0 = Release|Win32 {12144E07-FE63-4D33-9231-748B8D8C3792}.Debug|ARM64.ActiveCfg = Debug|ARM64 {12144E07-FE63-4D33-9231-748B8D8C3792}.Debug|ARM64.Build.0 = Debug|ARM64 {12144E07-FE63-4D33-9231-748B8D8C3792}.Debug|x64.ActiveCfg = Debug|x64 @@ -534,11 +493,8 @@ Global {12144E07-FE63-4D33-9231-748B8D8C3792}.Release|x86.ActiveCfg = Release|Win32 {12144E07-FE63-4D33-9231-748B8D8C3792}.Release|x86.Build.0 = Release|Win32 {6AF01638-84CF-4B65-9870-484DFFCAC772}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {6AF01638-84CF-4B65-9870-484DFFCAC772}.AuditMode|ARM64.Build.0 = Release|ARM64 {6AF01638-84CF-4B65-9870-484DFFCAC772}.AuditMode|x64.ActiveCfg = Release|x64 - {6AF01638-84CF-4B65-9870-484DFFCAC772}.AuditMode|x64.Build.0 = Release|x64 {6AF01638-84CF-4B65-9870-484DFFCAC772}.AuditMode|x86.ActiveCfg = Release|Win32 - {6AF01638-84CF-4B65-9870-484DFFCAC772}.AuditMode|x86.Build.0 = Release|Win32 {6AF01638-84CF-4B65-9870-484DFFCAC772}.Debug|ARM64.ActiveCfg = Debug|ARM64 {6AF01638-84CF-4B65-9870-484DFFCAC772}.Debug|ARM64.Build.0 = Debug|ARM64 {6AF01638-84CF-4B65-9870-484DFFCAC772}.Debug|x64.ActiveCfg = Debug|x64 @@ -552,11 +508,8 @@ Global {6AF01638-84CF-4B65-9870-484DFFCAC772}.Release|x86.ActiveCfg = Release|Win32 {6AF01638-84CF-4B65-9870-484DFFCAC772}.Release|x86.Build.0 = Release|Win32 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.AuditMode|ARM64.Build.0 = Release|ARM64 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.AuditMode|x64.ActiveCfg = Release|x64 - {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.AuditMode|x64.Build.0 = Release|x64 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.AuditMode|x86.ActiveCfg = Release|Win32 - {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.AuditMode|x86.Build.0 = Release|Win32 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Debug|ARM64.ActiveCfg = Debug|ARM64 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Debug|ARM64.Build.0 = Debug|ARM64 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Debug|x64.ActiveCfg = Debug|x64 @@ -570,11 +523,8 @@ Global {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Release|x86.ActiveCfg = Release|Win32 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Release|x86.Build.0 = Release|Win32 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.AuditMode|ARM64.Build.0 = Release|ARM64 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.AuditMode|x64.ActiveCfg = Release|x64 - {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.AuditMode|x64.Build.0 = Release|x64 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.AuditMode|x86.ActiveCfg = Release|Win32 - {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.AuditMode|x86.Build.0 = Release|Win32 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Debug|ARM64.ActiveCfg = Debug|ARM64 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Debug|ARM64.Build.0 = Debug|ARM64 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Debug|x64.ActiveCfg = Debug|x64 @@ -588,11 +538,8 @@ Global {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Release|x86.ActiveCfg = Release|Win32 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Release|x86.Build.0 = Release|Win32 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.AuditMode|ARM64.Build.0 = Release|ARM64 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.AuditMode|x64.ActiveCfg = Release|x64 - {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.AuditMode|x64.Build.0 = Release|x64 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.AuditMode|x86.ActiveCfg = Release|Win32 - {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.AuditMode|x86.Build.0 = Release|Win32 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Debug|ARM64.ActiveCfg = Debug|ARM64 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Debug|ARM64.Build.0 = Debug|ARM64 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Debug|x64.ActiveCfg = Debug|x64 @@ -606,11 +553,8 @@ Global {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Release|x86.ActiveCfg = Release|Win32 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Release|x86.Build.0 = Release|Win32 {18D09A24-8240-42D6-8CB6-236EEE820262}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {18D09A24-8240-42D6-8CB6-236EEE820262}.AuditMode|ARM64.Build.0 = Release|ARM64 {18D09A24-8240-42D6-8CB6-236EEE820262}.AuditMode|x64.ActiveCfg = Release|x64 - {18D09A24-8240-42D6-8CB6-236EEE820262}.AuditMode|x64.Build.0 = Release|x64 {18D09A24-8240-42D6-8CB6-236EEE820262}.AuditMode|x86.ActiveCfg = Release|Win32 - {18D09A24-8240-42D6-8CB6-236EEE820262}.AuditMode|x86.Build.0 = Release|Win32 {18D09A24-8240-42D6-8CB6-236EEE820262}.Debug|ARM64.ActiveCfg = Debug|ARM64 {18D09A24-8240-42D6-8CB6-236EEE820262}.Debug|ARM64.Build.0 = Debug|ARM64 {18D09A24-8240-42D6-8CB6-236EEE820262}.Debug|x64.ActiveCfg = Debug|x64 @@ -624,11 +568,8 @@ Global {18D09A24-8240-42D6-8CB6-236EEE820262}.Release|x86.ActiveCfg = Release|Win32 {18D09A24-8240-42D6-8CB6-236EEE820262}.Release|x86.Build.0 = Release|Win32 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.AuditMode|ARM64.ActiveCfg = Debug|Win32 - {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.AuditMode|ARM64.Build.0 = Debug|Win32 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.AuditMode|x64.ActiveCfg = Release|x64 - {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.AuditMode|x64.Build.0 = Release|x64 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.AuditMode|x86.ActiveCfg = Release|Win32 - {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.AuditMode|x86.Build.0 = Release|Win32 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Debug|ARM64.ActiveCfg = Debug|Win32 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Debug|x64.ActiveCfg = Debug|x64 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Debug|x64.Build.0 = Debug|x64 @@ -640,11 +581,8 @@ Global {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Release|x86.ActiveCfg = Release|Win32 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Release|x86.Build.0 = Release|Win32 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.AuditMode|ARM64.ActiveCfg = Debug|Win32 - {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.AuditMode|ARM64.Build.0 = Debug|Win32 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.AuditMode|x64.ActiveCfg = Release|x64 - {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.AuditMode|x64.Build.0 = Release|x64 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.AuditMode|x86.ActiveCfg = Release|Win32 - {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.AuditMode|x86.Build.0 = Release|Win32 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Debug|ARM64.ActiveCfg = Debug|Win32 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Debug|x64.ActiveCfg = Debug|x64 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Debug|x64.Build.0 = Debug|x64 @@ -656,11 +594,8 @@ Global {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Release|x86.ActiveCfg = Release|Win32 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Release|x86.Build.0 = Release|Win32 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {FC802440-AD6A-4919-8F2C-7701F2B38D79}.AuditMode|ARM64.Build.0 = Release|ARM64 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.AuditMode|x64.ActiveCfg = Release|x64 - {FC802440-AD6A-4919-8F2C-7701F2B38D79}.AuditMode|x64.Build.0 = Release|x64 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.AuditMode|x86.ActiveCfg = Release|Win32 - {FC802440-AD6A-4919-8F2C-7701F2B38D79}.AuditMode|x86.Build.0 = Release|Win32 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Debug|ARM64.ActiveCfg = Debug|ARM64 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Debug|ARM64.Build.0 = Debug|ARM64 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Debug|x64.ActiveCfg = Debug|x64 @@ -674,11 +609,8 @@ Global {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Release|x86.ActiveCfg = Release|Win32 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Release|x86.Build.0 = Release|Win32 {919544AC-D39B-463F-8414-3C3C67CF727C}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {919544AC-D39B-463F-8414-3C3C67CF727C}.AuditMode|ARM64.Build.0 = Release|ARM64 {919544AC-D39B-463F-8414-3C3C67CF727C}.AuditMode|x64.ActiveCfg = Release|x64 - {919544AC-D39B-463F-8414-3C3C67CF727C}.AuditMode|x64.Build.0 = Release|x64 {919544AC-D39B-463F-8414-3C3C67CF727C}.AuditMode|x86.ActiveCfg = Release|Win32 - {919544AC-D39B-463F-8414-3C3C67CF727C}.AuditMode|x86.Build.0 = Release|Win32 {919544AC-D39B-463F-8414-3C3C67CF727C}.Debug|ARM64.ActiveCfg = Debug|ARM64 {919544AC-D39B-463F-8414-3C3C67CF727C}.Debug|ARM64.Build.0 = Debug|ARM64 {919544AC-D39B-463F-8414-3C3C67CF727C}.Debug|x64.ActiveCfg = Debug|x64 @@ -692,11 +624,8 @@ Global {919544AC-D39B-463F-8414-3C3C67CF727C}.Release|x86.ActiveCfg = Release|Win32 {919544AC-D39B-463F-8414-3C3C67CF727C}.Release|x86.Build.0 = Release|Win32 {ED82003F-FC5D-4E94-8B36-F480018ED064}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {ED82003F-FC5D-4E94-8B36-F480018ED064}.AuditMode|ARM64.Build.0 = Release|ARM64 {ED82003F-FC5D-4E94-8B36-F480018ED064}.AuditMode|x64.ActiveCfg = Release|x64 - {ED82003F-FC5D-4E94-8B36-F480018ED064}.AuditMode|x64.Build.0 = Release|x64 {ED82003F-FC5D-4E94-8B36-F480018ED064}.AuditMode|x86.ActiveCfg = Release|Win32 - {ED82003F-FC5D-4E94-8B36-F480018ED064}.AuditMode|x86.Build.0 = Release|Win32 {ED82003F-FC5D-4E94-8B36-F480018ED064}.Debug|ARM64.ActiveCfg = Debug|ARM64 {ED82003F-FC5D-4E94-8B36-F480018ED064}.Debug|ARM64.Build.0 = Debug|ARM64 {ED82003F-FC5D-4E94-8B36-F480018ED064}.Debug|x64.ActiveCfg = Debug|x64 @@ -710,11 +639,8 @@ Global {ED82003F-FC5D-4E94-8B36-F480018ED064}.Release|x86.ActiveCfg = Release|Win32 {ED82003F-FC5D-4E94-8B36-F480018ED064}.Release|x86.Build.0 = Release|Win32 {06EC74CB-9A12-429C-B551-8532EC964726}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {06EC74CB-9A12-429C-B551-8532EC964726}.AuditMode|ARM64.Build.0 = Release|ARM64 {06EC74CB-9A12-429C-B551-8532EC964726}.AuditMode|x64.ActiveCfg = Release|x64 - {06EC74CB-9A12-429C-B551-8532EC964726}.AuditMode|x64.Build.0 = Release|x64 {06EC74CB-9A12-429C-B551-8532EC964726}.AuditMode|x86.ActiveCfg = Release|Win32 - {06EC74CB-9A12-429C-B551-8532EC964726}.AuditMode|x86.Build.0 = Release|Win32 {06EC74CB-9A12-429C-B551-8532EC964726}.Debug|ARM64.ActiveCfg = Debug|ARM64 {06EC74CB-9A12-429C-B551-8532EC964726}.Debug|ARM64.Build.0 = Debug|ARM64 {06EC74CB-9A12-429C-B551-8532EC964726}.Debug|x64.ActiveCfg = Debug|x64 @@ -728,11 +654,8 @@ Global {06EC74CB-9A12-429C-B551-8532EC964726}.Release|x86.ActiveCfg = Release|Win32 {06EC74CB-9A12-429C-B551-8532EC964726}.Release|x86.Build.0 = Release|Win32 {ED82003F-FC5D-4E94-8B47-F480018ED064}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {ED82003F-FC5D-4E94-8B47-F480018ED064}.AuditMode|ARM64.Build.0 = Release|ARM64 {ED82003F-FC5D-4E94-8B47-F480018ED064}.AuditMode|x64.ActiveCfg = Release|x64 - {ED82003F-FC5D-4E94-8B47-F480018ED064}.AuditMode|x64.Build.0 = Release|x64 {ED82003F-FC5D-4E94-8B47-F480018ED064}.AuditMode|x86.ActiveCfg = Release|Win32 - {ED82003F-FC5D-4E94-8B47-F480018ED064}.AuditMode|x86.Build.0 = Release|Win32 {ED82003F-FC5D-4E94-8B47-F480018ED064}.Debug|ARM64.ActiveCfg = Debug|ARM64 {ED82003F-FC5D-4E94-8B47-F480018ED064}.Debug|ARM64.Build.0 = Debug|ARM64 {ED82003F-FC5D-4E94-8B47-F480018ED064}.Debug|x64.ActiveCfg = Debug|x64 @@ -746,11 +669,8 @@ Global {ED82003F-FC5D-4E94-8B47-F480018ED064}.Release|x86.ActiveCfg = Release|Win32 {ED82003F-FC5D-4E94-8B47-F480018ED064}.Release|x86.Build.0 = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC964846}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {06EC74CB-9A12-429C-B551-8562EC964846}.AuditMode|ARM64.Build.0 = Release|ARM64 {06EC74CB-9A12-429C-B551-8562EC964846}.AuditMode|x64.ActiveCfg = Release|x64 - {06EC74CB-9A12-429C-B551-8562EC964846}.AuditMode|x64.Build.0 = Release|x64 {06EC74CB-9A12-429C-B551-8562EC964846}.AuditMode|x86.ActiveCfg = Release|Win32 - {06EC74CB-9A12-429C-B551-8562EC964846}.AuditMode|x86.Build.0 = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC964846}.Debug|ARM64.ActiveCfg = Debug|ARM64 {06EC74CB-9A12-429C-B551-8562EC964846}.Debug|ARM64.Build.0 = Debug|ARM64 {06EC74CB-9A12-429C-B551-8562EC964846}.Debug|x64.ActiveCfg = Debug|x64 @@ -764,11 +684,8 @@ Global {06EC74CB-9A12-429C-B551-8562EC964846}.Release|x86.ActiveCfg = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC964846}.Release|x86.Build.0 = Release|Win32 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.AuditMode|ARM64.Build.0 = Release|ARM64 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.AuditMode|x64.ActiveCfg = Release|x64 - {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.AuditMode|x64.Build.0 = Release|x64 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.AuditMode|x86.ActiveCfg = Release|Win32 - {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.AuditMode|x86.Build.0 = Release|Win32 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Debug|ARM64.ActiveCfg = Debug|ARM64 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Debug|ARM64.Build.0 = Debug|ARM64 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Debug|x64.ActiveCfg = Debug|x64 @@ -782,11 +699,8 @@ Global {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Release|x86.ActiveCfg = Release|Win32 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Release|x86.Build.0 = Release|Win32 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.AuditMode|ARM64.Build.0 = Release|ARM64 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.AuditMode|x64.ActiveCfg = Release|x64 - {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.AuditMode|x64.Build.0 = Release|x64 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.AuditMode|x86.ActiveCfg = Release|Win32 - {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.AuditMode|x86.Build.0 = Release|Win32 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Debug|ARM64.ActiveCfg = Debug|ARM64 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Debug|ARM64.Build.0 = Debug|ARM64 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Debug|x64.ActiveCfg = Debug|x64 @@ -800,11 +714,8 @@ Global {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Release|x86.ActiveCfg = Release|Win32 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Release|x86.Build.0 = Release|Win32 {990F2657-8580-4828-943F-5DD657D11842}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {990F2657-8580-4828-943F-5DD657D11842}.AuditMode|ARM64.Build.0 = Release|ARM64 {990F2657-8580-4828-943F-5DD657D11842}.AuditMode|x64.ActiveCfg = Release|x64 - {990F2657-8580-4828-943F-5DD657D11842}.AuditMode|x64.Build.0 = Release|x64 {990F2657-8580-4828-943F-5DD657D11842}.AuditMode|x86.ActiveCfg = Release|Win32 - {990F2657-8580-4828-943F-5DD657D11842}.AuditMode|x86.Build.0 = Release|Win32 {990F2657-8580-4828-943F-5DD657D11842}.Debug|ARM64.ActiveCfg = Debug|ARM64 {990F2657-8580-4828-943F-5DD657D11842}.Debug|ARM64.Build.0 = Debug|ARM64 {990F2657-8580-4828-943F-5DD657D11842}.Debug|x64.ActiveCfg = Debug|x64 @@ -818,11 +729,8 @@ Global {990F2657-8580-4828-943F-5DD657D11842}.Release|x86.ActiveCfg = Release|Win32 {990F2657-8580-4828-943F-5DD657D11842}.Release|x86.Build.0 = Release|Win32 {814DBDDE-894E-4327-A6E1-740504850098}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {814DBDDE-894E-4327-A6E1-740504850098}.AuditMode|ARM64.Build.0 = Release|ARM64 {814DBDDE-894E-4327-A6E1-740504850098}.AuditMode|x64.ActiveCfg = Release|x64 - {814DBDDE-894E-4327-A6E1-740504850098}.AuditMode|x64.Build.0 = Release|x64 {814DBDDE-894E-4327-A6E1-740504850098}.AuditMode|x86.ActiveCfg = Release|Win32 - {814DBDDE-894E-4327-A6E1-740504850098}.AuditMode|x86.Build.0 = Release|Win32 {814DBDDE-894E-4327-A6E1-740504850098}.Debug|ARM64.ActiveCfg = Debug|ARM64 {814DBDDE-894E-4327-A6E1-740504850098}.Debug|ARM64.Build.0 = Debug|ARM64 {814DBDDE-894E-4327-A6E1-740504850098}.Debug|x64.ActiveCfg = Debug|x64 @@ -836,11 +744,8 @@ Global {814DBDDE-894E-4327-A6E1-740504850098}.Release|x86.ActiveCfg = Release|Win32 {814DBDDE-894E-4327-A6E1-740504850098}.Release|x86.Build.0 = Release|Win32 {814CBEEE-894E-4327-A6E1-740504850098}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {814CBEEE-894E-4327-A6E1-740504850098}.AuditMode|ARM64.Build.0 = Release|ARM64 {814CBEEE-894E-4327-A6E1-740504850098}.AuditMode|x64.ActiveCfg = Release|x64 - {814CBEEE-894E-4327-A6E1-740504850098}.AuditMode|x64.Build.0 = Release|x64 {814CBEEE-894E-4327-A6E1-740504850098}.AuditMode|x86.ActiveCfg = Release|Win32 - {814CBEEE-894E-4327-A6E1-740504850098}.AuditMode|x86.Build.0 = Release|Win32 {814CBEEE-894E-4327-A6E1-740504850098}.Debug|ARM64.ActiveCfg = Debug|ARM64 {814CBEEE-894E-4327-A6E1-740504850098}.Debug|ARM64.Build.0 = Debug|ARM64 {814CBEEE-894E-4327-A6E1-740504850098}.Debug|x64.ActiveCfg = Debug|x64 @@ -872,11 +777,8 @@ Global {18D09A24-8240-42D6-8CB6-236EEE820263}.Release|x86.ActiveCfg = Release|Win32 {18D09A24-8240-42D6-8CB6-236EEE820263}.Release|x86.Build.0 = Release|Win32 {990F2657-8580-4828-943F-5DD657D11843}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {990F2657-8580-4828-943F-5DD657D11843}.AuditMode|ARM64.Build.0 = Release|ARM64 {990F2657-8580-4828-943F-5DD657D11843}.AuditMode|x64.ActiveCfg = Release|x64 - {990F2657-8580-4828-943F-5DD657D11843}.AuditMode|x64.Build.0 = Release|x64 {990F2657-8580-4828-943F-5DD657D11843}.AuditMode|x86.ActiveCfg = Release|Win32 - {990F2657-8580-4828-943F-5DD657D11843}.AuditMode|x86.Build.0 = Release|Win32 {990F2657-8580-4828-943F-5DD657D11843}.Debug|ARM64.ActiveCfg = Debug|ARM64 {990F2657-8580-4828-943F-5DD657D11843}.Debug|ARM64.Build.0 = Debug|ARM64 {990F2657-8580-4828-943F-5DD657D11843}.Debug|x64.ActiveCfg = Debug|x64 @@ -926,11 +828,8 @@ Global {48D21369-3D7B-4431-9967-24E81292CF62}.Release|x86.ActiveCfg = Release|Win32 {48D21369-3D7B-4431-9967-24E81292CF62}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.AuditMode|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.AuditMode|x64.ActiveCfg = Release|x64 - {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.AuditMode|x64.Build.0 = Release|x64 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.AuditMode|x86.ActiveCfg = Release|Win32 - {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.AuditMode|x86.Build.0 = Release|Win32 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Debug|x64.ActiveCfg = Debug|x64 @@ -944,11 +843,8 @@ Global {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {CA5CAD1A-ABCD-429C-B551-8562EC954746}.AuditMode|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.AuditMode|x64.ActiveCfg = Release|x64 - {CA5CAD1A-ABCD-429C-B551-8562EC954746}.AuditMode|x64.Build.0 = Release|x64 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.AuditMode|x86.ActiveCfg = Release|Win32 - {CA5CAD1A-ABCD-429C-B551-8562EC954746}.AuditMode|x86.Build.0 = Release|Win32 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Debug|x64.ActiveCfg = Debug|x64 @@ -962,11 +858,8 @@ Global {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.AuditMode|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.AuditMode|x64.ActiveCfg = Release|x64 - {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.AuditMode|x64.Build.0 = Release|x64 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.AuditMode|x86.ActiveCfg = Release|Win32 - {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.AuditMode|x86.Build.0 = Release|Win32 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Debug|x64.ActiveCfg = Debug|x64 @@ -980,11 +873,8 @@ Global {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|x64.ActiveCfg = Release|x64 - {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|x64.Build.0 = Release|x64 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|x86.ActiveCfg = Release|Win32 - {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|x86.Build.0 = Release|Win32 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Debug|x64.ActiveCfg = Debug|x64 @@ -998,11 +888,8 @@ Global {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.AuditMode|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.AuditMode|x64.ActiveCfg = Release|x64 - {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.AuditMode|x64.Build.0 = Release|x64 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.AuditMode|x86.ActiveCfg = Release|Win32 - {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.AuditMode|x86.Build.0 = Release|Win32 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Debug|x64.ActiveCfg = Debug|x64 @@ -1016,11 +903,8 @@ Global {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|x64.ActiveCfg = Release|x64 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|x64.Build.0 = Release|x64 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|x86.ActiveCfg = Release|Win32 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|x86.Build.0 = Release|Win32 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|x64.ActiveCfg = Debug|x64 @@ -1034,14 +918,8 @@ Global {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|x86.Build.0 = Release|Win32 {2D310963-F3E0-4EE5-8AC6-FBC94DCC3310}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {2D310963-F3E0-4EE5-8AC6-FBC94DCC3310}.AuditMode|ARM64.Build.0 = Release|ARM64 - {2D310963-F3E0-4EE5-8AC6-FBC94DCC3310}.AuditMode|ARM64.Deploy.0 = Release|ARM64 {2D310963-F3E0-4EE5-8AC6-FBC94DCC3310}.AuditMode|x64.ActiveCfg = Release|x64 - {2D310963-F3E0-4EE5-8AC6-FBC94DCC3310}.AuditMode|x64.Build.0 = Release|x64 - {2D310963-F3E0-4EE5-8AC6-FBC94DCC3310}.AuditMode|x64.Deploy.0 = Release|x64 {2D310963-F3E0-4EE5-8AC6-FBC94DCC3310}.AuditMode|x86.ActiveCfg = Release|x86 - {2D310963-F3E0-4EE5-8AC6-FBC94DCC3310}.AuditMode|x86.Build.0 = Release|x86 - {2D310963-F3E0-4EE5-8AC6-FBC94DCC3310}.AuditMode|x86.Deploy.0 = Release|x86 {2D310963-F3E0-4EE5-8AC6-FBC94DCC3310}.Debug|ARM64.ActiveCfg = Debug|ARM64 {2D310963-F3E0-4EE5-8AC6-FBC94DCC3310}.Debug|ARM64.Build.0 = Debug|ARM64 {2D310963-F3E0-4EE5-8AC6-FBC94DCC3310}.Debug|ARM64.Deploy.0 = Debug|ARM64 @@ -1061,11 +939,8 @@ Global {2D310963-F3E0-4EE5-8AC6-FBC94DCC3310}.Release|x86.Build.0 = Release|x86 {2D310963-F3E0-4EE5-8AC6-FBC94DCC3310}.Release|x86.Deploy.0 = Release|x86 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 - {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.AuditMode|x64.ActiveCfg = AuditMode|x64 - {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.AuditMode|x64.Build.0 = AuditMode|x64 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.AuditMode|x86.ActiveCfg = AuditMode|Win32 - {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.AuditMode|x86.Build.0 = AuditMode|Win32 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Debug|ARM64.ActiveCfg = Debug|ARM64 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Debug|ARM64.Build.0 = Debug|ARM64 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Debug|x64.ActiveCfg = Debug|x64 @@ -1097,11 +972,8 @@ Global {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Release|x86.ActiveCfg = Release|Win32 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Release|x86.Build.0 = Release|Win32 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 - {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|x64.ActiveCfg = AuditMode|x64 - {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|x64.Build.0 = AuditMode|x64 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|x86.ActiveCfg = AuditMode|Win32 - {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|x86.Build.0 = AuditMode|Win32 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|ARM64.ActiveCfg = Debug|ARM64 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|ARM64.Build.0 = Debug|ARM64 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|x64.ActiveCfg = Debug|x64 @@ -1115,11 +987,8 @@ Global {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|x86.ActiveCfg = Release|Win32 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 - {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.AuditMode|x64.ActiveCfg = AuditMode|x64 - {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.AuditMode|x64.Build.0 = AuditMode|x64 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.AuditMode|x86.ActiveCfg = AuditMode|Win32 - {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.AuditMode|x86.Build.0 = AuditMode|Win32 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Debug|x64.ActiveCfg = Debug|x64 @@ -1133,11 +1002,8 @@ Global {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-9A12-429C-B551-8562EC954746}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {CA5CAD1A-9A12-429C-B551-8562EC954746}.AuditMode|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-9A12-429C-B551-8562EC954746}.AuditMode|x64.ActiveCfg = Release|x64 - {CA5CAD1A-9A12-429C-B551-8562EC954746}.AuditMode|x64.Build.0 = Release|x64 {CA5CAD1A-9A12-429C-B551-8562EC954746}.AuditMode|x86.ActiveCfg = Release|Win32 - {CA5CAD1A-9A12-429C-B551-8562EC954746}.AuditMode|x86.Build.0 = Release|Win32 {CA5CAD1A-9A12-429C-B551-8562EC954746}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-9A12-429C-B551-8562EC954746}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-9A12-429C-B551-8562EC954746}.Debug|x64.ActiveCfg = Debug|x64 @@ -1150,6 +1016,36 @@ Global {CA5CAD1A-9A12-429C-B551-8562EC954746}.Release|x64.Build.0 = Release|x64 {CA5CAD1A-9A12-429C-B551-8562EC954746}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-9A12-429C-B551-8562EC954746}.Release|x86.Build.0 = Release|Win32 + {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 + {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.AuditMode|x64.ActiveCfg = AuditMode|x64 + {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.AuditMode|x86.ActiveCfg = AuditMode|Win32 + {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Debug|ARM64.Build.0 = Debug|ARM64 + {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Debug|x64.ActiveCfg = Debug|x64 + {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Debug|x64.Build.0 = Debug|x64 + {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Debug|x86.ActiveCfg = Debug|Win32 + {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Debug|x86.Build.0 = Debug|Win32 + {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Release|ARM64.ActiveCfg = Release|ARM64 + {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Release|ARM64.Build.0 = Release|ARM64 + {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Release|x64.ActiveCfg = Release|x64 + {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Release|x64.Build.0 = Release|x64 + {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Release|x86.ActiveCfg = Release|Win32 + {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Release|x86.Build.0 = Release|Win32 + {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.AuditMode|ARM64.ActiveCfg = Release|ARM64 + {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.AuditMode|x64.ActiveCfg = Release|x64 + {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.AuditMode|x86.ActiveCfg = Release|Win32 + {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Debug|ARM64.Build.0 = Debug|ARM64 + {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Debug|x64.ActiveCfg = Debug|x64 + {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Debug|x64.Build.0 = Debug|x64 + {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Debug|x86.ActiveCfg = Debug|Win32 + {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Debug|x86.Build.0 = Debug|Win32 + {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Release|ARM64.ActiveCfg = Release|ARM64 + {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Release|ARM64.Build.0 = Release|ARM64 + {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Release|x64.ActiveCfg = Release|x64 + {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Release|x64.Build.0 = Release|x64 + {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Release|x86.ActiveCfg = Release|Win32 + {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1210,6 +1106,8 @@ Global {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73} = {89CDCC5C-9F53-4054-97A4-639D99F169CD} {CA5CAD1A-9333-4D05-B12A-1905CBF112F9} = {59840756-302F-44DF-AA47-441A9D673202} {CA5CAD1A-9A12-429C-B551-8562EC954746} = {59840756-302F-44DF-AA47-441A9D673202} + {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506} = {59840756-302F-44DF-AA47-441A9D673202} + {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271} diff --git a/README.md b/README.md index 686f151538a..fb3cc008b15 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ ## Installation +_(Note: in order to run the Windows Terminal, you'll need to be running at least Windows build 18362 or higher.)_ + ### Microsoft Store Download the Microsoft Terminal free from the Microsoft Store and it'll be continuously updated. Or, feel free to side-load [releases](https://github.com/microsoft/terminal/releases) from GitHub, but note they won't auto-update. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000000..6cda10b000d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [many more](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [definition](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). + + diff --git a/build/pipelines/ci.yml b/build/pipelines/ci.yml index 237656e5a03..e623f3ab965 100644 --- a/build/pipelines/ci.yml +++ b/build/pipelines/ci.yml @@ -13,17 +13,20 @@ pr: branches: include: - master + paths: + exclude: + - doc/* + - samples/* + - tools/* # 0.0.yyMM.dd## # 0.0.1904.0900 name: 0.0.$(Date:yyMM).$(Date:dd)$(Rev:rr) jobs: -# This is disabled because the build agents were running out of disk space. -# We're pursuing that in the background, but the spice must flow in the meantime. -# - template: ./templates/build-console-audit-job.yml -# parameters: -# platform: x64 + - template: ./templates/build-console-audit-job.yml + parameters: + platform: x64 - template: ./templates/build-console-ci.yml parameters: diff --git a/build/scripts/Test-WindowsTerminalPackage.ps1 b/build/scripts/Test-WindowsTerminalPackage.ps1 index ca4d3df3d51..67b21958ff4 100644 --- a/build/scripts/Test-WindowsTerminalPackage.ps1 +++ b/build/scripts/Test-WindowsTerminalPackage.ps1 @@ -74,6 +74,14 @@ Try { If ($null -eq $AppXbf) { Throw "Failed to find App.xbf (TerminalApp project) in resources.pri" } + + If ($Manifest.Package.Identity.ProcessorArchitecture -Ne "arm64") { + ### ARM64 doesn't package cpprest_2_10. + If (($null -eq (Get-Item "$AppxPackageRootPath\cpprest_2_10.dll" -EA:Ignore)) -And + ($null -eq (Get-Item "$AppxPackageRootPath\cpprest_2_10d.dll" -EA:Ignore))) { + Throw "Failed to find cpprest_2_10.dll -- check the WAP packaging project" + } + } } Finally { Remove-Item -Recurse -Force $AppxPackageRootPath } diff --git a/consolegit2gitfilters.json b/consolegit2gitfilters.json index 6466ad537b8..d779dec7ca3 100644 --- a/consolegit2gitfilters.json +++ b/consolegit2gitfilters.json @@ -14,6 +14,7 @@ "/.vs/", "/build/", "/src/cascadia/", + "/src/winconpty/", "/.nuget/", "/.github/", "/samples/" diff --git a/dep/gsl b/dep/gsl index b74b286d5e3..1212beae777 160000 --- a/dep/gsl +++ b/dep/gsl @@ -1 +1 @@ -Subproject commit b74b286d5e333561b0f1ef1abd18de2606624455 +Subproject commit 1212beae777dba02c230ece8c0c0ec12790047ea diff --git a/dep/wil b/dep/wil index fbcd1d2abb5..e8c599bca6c 160000 --- a/dep/wil +++ b/dep/wil @@ -1 +1 @@ -Subproject commit fbcd1d2abb558da4564ce343b688f7a658f51318 +Subproject commit e8c599bca6c56c44b6730ad93f6abbc9ecd60fc1 diff --git a/doc/ConsoleHostSettings.md b/doc/ConsoleHostSettings.md index 62a6a3dd27f..0a4d84847b7 100644 --- a/doc/ConsoleHostSettings.md +++ b/doc/ConsoleHostSettings.md @@ -8,9 +8,9 @@ Settings in the Windows Console Host can be a bit tricky to understand. This is |---------------------------|-----------------------|--------------------------------------| |`FontSize` |Coordinate (REG_DWORD) |Size of font in pixels | |`FontFamily` |REG_DWORD |GDI Font family | -|`ScreenBufferSize` |Coordinate (REG_DWORD) |Size of the screen buffer in WxH characters | +|`ScreenBufferSize` |Coordinate (REG_DWORD) |Size of the screen buffer in WxH characters\*\* | |`CursorSize` |REG_DWORD |Cursor height as percentage of a single character | -|`WindowSize` |Coordinate (REG_DWORD) |Initial size of the window in WxH characters | +|`WindowSize` |Coordinate (REG_DWORD) |Initial size of the window in WxH characters\*\* | |`WindowPosition` |Coordinate (REG_DWORD) |Initial position of the window in WxH pixels (if not set, use auto-positioning) | |`WindowAlpha` |REG_DWORD |Opacity of the window (valid range: 0x4D-0xFF) | |`ScreenColors` |REG_DWORD |Default foreground and background colors | @@ -39,6 +39,10 @@ Settings in the Windows Console Host can be a bit tricky to understand. This is *: Only applies to the improved version of the Windows Console Host +**: WxH stands for Width by Height, it's the fact that things like a Window size +store the Width and Height values in the high and low word in the registry's +double word values. + ## The Settings Hierarchy Settings are persisted to a variety of locations depending on how they are modified and how the Windows Console Host was invoked: diff --git a/doc/Niksa.md b/doc/Niksa.md index ad26b6657f2..bd8fe0e2898 100644 --- a/doc/Niksa.md +++ b/doc/Niksa.md @@ -8,6 +8,7 @@ This document serves as a storage point for those posts. - [How are the Windows graphics/messaging stack assembled?](#gfxMsgStack) - [Output Processing between "Far East" and "Western"](#fesb) - [Why do we not backport things?](#backport) +- [Why can't we have mixed elevated and non-elevated tabs in the Terminal?](#elevation) ## Why do we avoid changing CMD.exe? `setlocal` doesn't behave the same way as an environment variable. It's a thing that would have to be put in at the top of the batch script that is `somefile.cmd` as one of its first commands to adjust the way that one specific batch file is processed by the `cmd.exe` engine. That's probably not suitable for your needs, but that's the way we have to go. @@ -145,3 +146,36 @@ It's also costly in terms of time, effort, and testing for us to validate a modi So from our little team working hard to make developers happy, we virtually never make the cut for servicing. We're sorry, but we hope you can understand. It's just the reality of the situation to say "nope" when people ask for a backport. In our team's ideal world, you would all be running the latest console bits everywhere everytime we make a change. But that's just not how it is today. Original Source: https://github.com/microsoft/terminal/issues/279#issuecomment-439179675 + +## Why can't we have mixed elevated and non-elevated tabs in the Terminal? + +_guest speaker @DHowett-MSFT_ + +[1] It is trivial when you are _hosting traditional windows_ with traditional window handles. That works very well in the conemu case, or in the tabbed shell case, where you can take over a window in an elevated session and re-parent it under a window in a non-elevated session. + +When you do that, there's a few security features that I'll touch on in [2]. Because of those, you can parent it but you can't really force it to do anything. + +There's a problem, though. The Terminal isn't architected as a collection of re-parentable windows. For example, it's not running a console host and moving its window into a tab. It was designed to support a "connection" -- something that can read and write text. It's a lower-level primitive than a window. We realized the error of our ways and decided that the UNIX model was right the entire time, and pipes and text and streams are _where it's at._ + +Given that we're using Xaml islands to host a modern UI and stitching a DirectX surface into it, we're far beyond the world of standard window handles anyway. Xaml islands are fully composed into a single HWND, much like Chrome and Firefox and the gamut of DirectX/OpenGL/SDL games. We don't **have** components that can be run in one process (elevated) and hosted in another (non-elevated) that aren't the aforementioned "connections". + +Now, the obvious followup question is _"why can't you have one elevated connection in a tab next to a non-elevated connection?"_ This is where @sba923 should pick up reading (:smile:). I'm probably going to cover some things that you (@robomac) know already. + +[2] When you have two windows on the same desktop in the same window station, they can communicate with eachother. I can use `SendKeys` easily through `WScript.Shell` to send keyboard input to any window that the shell can see. + +Running a process elevated _severs_ that connection. The shell can't see the elevated window. No other program at the same integrity level as the shell can see the elevated window. Even if it has its window handle, it can't really interact with it. This is also why you can't drag/drop from explorer into notepad if notepad is running elevated. Only another elevated process can interact with another elevated window. + +That "security" feature (call it what you like, it was probably intended to be a security feature at one point) only exists for a few session-global object types. Windows are one of them. Pipes aren't really one of them. + +Because of that, it's trivial to break that security. Take the terminal as an example of that. If we start an elevated connection and host it in a _non-elevated_ window, we've suddenly created a conduit through that security boundary. The elevated thing on the other end isn't a window, it's just a text-mode application. It immediately does the bidding of the non-elevated host. + +Anybody that can _control_ the non-elevated host (like `WScript.Shell::SendKeys`) _also_ gets an instant conduit through the elevation boundary. Suddenly, any medium integrity application on your system can control a high-integrity process. This could be your browser, or the bitcoin miner that got installed with the `left-pad` package from NPM, or really any number of things. + +It's a small risk, but it _is_ a risk. + +--- + +Other platforms have accepted that risk in preference for user convenience. They aren't wrong to do so, but I think Microsoft gets less of a "pass" on things like "accepting risk for user convenience". Windows 9x was an unmitigated security disaster, and limited user accounts and elevation prompts and kernel-level security for window management were the answer to those things. They're not locks to be loosened lightly. + +Original Source: https://github.com/microsoft/terminal/issues/632#issuecomment-519375707 + diff --git a/doc/bot.md b/doc/bot.md index 1a215df7e36..73ba489c0fe 100644 --- a/doc/bot.md +++ b/doc/bot.md @@ -8,7 +8,7 @@ We'll be using tags, primarily, to help us understand what needs attention, what ### Quick-Guidance to Core Contributors 1. Look at `Needs-Attention` as top priority 1. Look at `Needs-Triage` during triage meetings to get a handle on what's new and sort it out -1. Look at `Needs-Tag-Fix` when you have a few minutes to fix up things tagged impoperly +1. Look at `Needs-Tag-Fix` when you have a few minutes to fix up things tagged improperly 1. Manually add `Needs-Author-Feedback` when there's something we need the author to follow up on and want attention if they return it or an auto-close for inactivity if it goes stale. ### Tagging/Process Details @@ -33,6 +33,17 @@ We'll be using tags, primarily, to help us understand what needs attention, what ## Rules +### Triage Shorthand +- All rules in this category apply to triaging issues. They're shorthand comments that the triage team can use in order to complete the triage process faster. +- Only individuals with `Write` or `Admin` privileges on the repository can use these responses. + +#### Duplicate Issues +- When a comment on the thread says `/dup #`... +1. Reply with a comment explaining that the issue is a duplicate and recommend that the opener and interested parties follow the issue on the listed ID number. +1. Close the issue +1. Remove all `Needs-*` tags +1. Add `Resolution-Duplicate` + ### Issue Management #### Mark as Triage Needed @@ -64,6 +75,14 @@ We'll be using tags, primarily, to help us understand what needs attention, what - If an issue is filed matching a pattern that happens all the time (common duplicate phrase, obvious multiple-issues-in-one pattern)... - Then close the issue automatically informing the opener that they can resolve the problem and reopen the issue. (See Bug/Feature templates for example situations.) +#### Help ask for Feedback Hub +- When a comment on the thread says `/feedback`... +1. Then reply to the issue with a bit of text on asking the author to send us data with Feedback Hub and give us the link. +1. And add the `Needs-Author-Feedback` tag + +#### Remove Help Wanted from In PR issues +- If an issue gets the `In-PR` tag when a new PR is created, we will remove the `Help-Wanted` tag to avoid someone trying to work on an issue where another person has already submitted a proposed fix. + ### PR Management #### Codeflow Link *(Disabled)* @@ -98,5 +117,14 @@ We'll be using tags, primarily, to help us understand what needs attention, what #### Add committed fix tag for completed PRs - When a PR is finished and there's no outstanding work left on a linked issue, add the `Resolution-Fix-Committed` label +#### Remove Needs-Second from completed PRs +- If a PR is closed and it has the `Needs-Second` tag, the bot will remove the tag. + +### Release Management + +When a release is created, if the PR ID number is linked inside the release description, the bot will walk through the related PR and all of its related issues and leave a message. +- PR message: "πŸŽ‰{release name} {release version} has been released which incorporates this pull request.πŸŽ‰ +- Issue message: πŸŽ‰This issue was addressed in #{pull request ID}, which has now been successfully released as {release name} {release version}.πŸŽ‰" + ## Admin Panel [Here](https://fabric-cp.azurewebsites.net/bot/) diff --git a/doc/building.md b/doc/building.md index efeb878dba5..b7cebc72eca 100644 --- a/doc/building.md +++ b/doc/building.md @@ -1,7 +1,7 @@ # How to build Openconsole -Openconsole can be built with Visual Studio or from the command line. There are build scripts for both cmd and powershell in /tools. +Openconsole can be built with Visual Studio or from the command line. There are build scripts for both cmd and PowerShell in /tools. When using Visual Studio, be sure to set up the path for code formatting. This can be done in Visual Studio by going to Tools > Options > Text Editor > C++ > Formatting and checking "Use custom clang-format.exe file" and choosing the clang-format.exe in the repository at /dep/llvm/clang-format.exe by clicking "browse" right under the check box. @@ -33,4 +33,4 @@ Openconsole has three configuration types: - Release - AuditMode -AuditMode is an experimental mode that enables some additional static analyis from CppCoreCheck. +AuditMode is an experimental mode that enables some additional static analysis from CppCoreCheck. diff --git a/doc/cascadia/SettingsSchema.md b/doc/cascadia/SettingsSchema.md index 4ee2aa9cd15..116be007d60 100644 --- a/doc/cascadia/SettingsSchema.md +++ b/doc/cascadia/SettingsSchema.md @@ -6,6 +6,7 @@ Properties listed below affect the entire window, regardless of the profile sett | Property | Necessity | Type | Default | Description | | -------- | --------- | ---- | ------- | ----------- | | `alwaysShowTabs` | _Required_ | Boolean | `true` | When set to `true`, tabs are always displayed. When set to `false` and `showTabsInTitlebar` is set to `false`, tabs only appear after typing Ctrl + T. | +| `copyOnSelect` | Optional | Boolean | `false` | When set to `true`, a selection is immediately copied to your clipboard upon creation. When set to `false`, the selection persists and awaits further action. | | `defaultProfile` | _Required_ | String | PowerShell guid | Sets the default profile. Opens by typing Ctrl + T or by clicking the '+' icon. The guid of the desired default profile is used as the value. | | `initialCols` | _Required_ | Integer | `120` | The number of columns displayed in the window upon first load. | | `initialRows` | _Required_ | Integer | `30` | The number of rows displayed in the window upon first load. | @@ -25,12 +26,12 @@ Properties listed below are specific to each unique profile. | `commandline` | _Required_ | String | `powershell.exe` | Executable used in the profile. | | `cursorColor` | _Required_ | String | `#FFFFFF` | Sets the cursor color for the profile. Uses hex color format: `"#rrggbb"`. | | `cursorShape` | _Required_ | String | `bar` | Sets the cursor shape for the profile. Possible values: `"vintage"` ( ▃ ), `"bar"` ( ┃ ), `"underscore"` ( ▁ ), `"filledBox"` ( █ ), `"emptyBox"` ( ▯ ) | -| `fontFace` | _Required_ | String | `Consolas` | Name of the font face used in the profile. | -| `fontSize` | _Required_ | Integer | `10` | Sets the font size. | +| `fontFace` | _Required_ | String | `Consolas` | Name of the font face used in the profile. We will try to fallback to Consolas if this can't be found or is invalid. | +| `fontSize` | _Required_ | Integer | `12` | Sets the font size. | | `guid` | _Required_ | String | | Unique identifier of the profile. Written in registry format: `"{00000000-0000-0000-0000-000000000000}"`. | | `historySize` | _Required_ | Integer | `9001` | The number of lines above the ones displayed in the window you can scroll back to. | -| `name` | _Required_ | String | `PowerShell Core` | Name of the profile. Displays in the dropdown menu. | -| `padding` | _Required_ | String | `0, 0, 0, 0` | Sets the padding around the text within the window. Can have three different formats: `"#"` sets the same padding for all sides, `"#, #"` sets the same padding for left-right and top-bottom, and `"#, #, #, #"` sets the padding individually for left, top, right, and bottom. | +| `name` | _Required_ | String | `PowerShell Core` | Name of the profile. Displays in the dropdown menu.
Additionally, this value will be used as the "title" to pass to the shell on startup. Some shells (like `bash`) may choose to ignore this initial value, while others (`cmd`, `powershell`) may use this value over the lifetime of the application. This "title" behavior can be overriden by using `tabTitle`. | +| `padding` | _Required_ | String | `8, 8, 8, 8` | Sets the padding around the text within the window. Can have three different formats: `"#"` sets the same padding for all sides, `"#, #"` sets the same padding for left-right and top-bottom, and `"#, #, #, #"` sets the padding individually for left, top, right, and bottom. | | `snapOnInput` | _Required_ | Boolean | `true` | When set to `true`, the window will scroll to the command input line when typing. When set to `false`, the window will not scroll when you start typing. | | `startingDirectory` | _Required_ | String | `%USERPROFILE%` | The directory the shell starts in when it is loaded. | | `useAcrylic` | _Required_ | Boolean | `false` | When set to `true`, the window will have an acrylic background. When set to `false`, the window will have a plain, untextured background. | @@ -42,9 +43,9 @@ Properties listed below are specific to each unique profile. | `colorTable` | Optional | Array[String] | | Array of colors used in the profile if `colorscheme` is not set. Colors use hex color format: `"#rrggbb"`. Ordering is as follows: `[black, red, green, yellow, blue, magenta, cyan, white, bright black, bright red, bright green, bright yellow, bright blue, bright magenta, bright cyan, bright white]` | | `cursorHeight` | Optional | Integer | | Sets the percentage height of the cursor starting from the bottom. Only works when `cursorShape` is set to `"vintage"`. Accepts values from 25-100. | | `foreground` | Optional | String | | Sets the foreground color of the profile. Overrides `foreground` set in color scheme if `colorscheme` is set. Uses hex color format: `"#rrggbb"`. | -| `icon` | Optional | String | | Image file location of the icon used in the profile. Displays within the tab and the dropdown menu. | +| `icon` | Optional | String | | Image file location of the icon used in the profile. Displays within the tab and the dropdown menu. See [Background Images and Icons](./SettingsSchema.md#background-images-and-icons) below for help on specifying your own icons | | `scrollbarState` | Optional | String | | Defines the visibility of the scrollbar. Possible values: `"visible"`, `"hidden"` | -| `tabTitle` | Optional | String | | Overrides default title of the tab. | +| `tabTitle` | Optional | String | | If set, will replace the `name` as the title to pass to the shell on startup. Some shells (like `bash`) may choose to ignore this initial value, while others (`cmd`, `powershell`) may use this value over the lifetime of the application. | ## Schemes Properties listed below are specific to each color scheme. [ColorTool](https://github.com/microsoft/terminal/tree/master/src/tools/ColorTool) is a great tool you can use to create and explore new color schemes. All colors use hex color format. @@ -78,3 +79,102 @@ Properties listed below are specific to each custom key binding. | -------- | ---- | ----------- | ----------- | | `command` | _Required_ | String | The command executed when the associated key bindings are pressed. | | `keys` | _Required_ | Array[String] | Defines the key combinations used to call the command. | + +### Implemented Keybindings + +Bindings listed below are per the implementation in `src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp` + +- copy +- copyTextWithoutNewlines +- paste +- newTab +- openNewTabDropdown +- duplicateTab +- newTabProfile0 +- newTabProfile1 +- newTabProfile2 +- newTabProfile3 +- newTabProfile4 +- newTabProfile5 +- newTabProfile6 +- newTabProfile7 +- newTabProfile8 +- newWindow +- closeWindow +- closeTab +- closePane +- switchToTab +- nextTab +- prevTab +- increaseFontSize +- decreaseFontSize +- scrollUp +- scrollDown +- scrollUpPage +- scrollDownPage +- switchToTab0 +- switchToTab1 +- switchToTab2 +- switchToTab3 +- switchToTab4 +- switchToTab5 +- switchToTab6 +- switchToTab7 +- switchToTab8 +- openSettings +- splitHorizontal +- splitVertical +- resizePaneLeft +- resizePaneRight +- resizePaneUp +- resizePaneDown +- moveFocusLeft +- moveFocusRight +- moveFocusUp +- moveFocusDown + +## Background Images and Icons +Some Terminal settings allow you to specify custom background images and icons. It is recommended that custom images and icons are stored in system-provided folders and are referred to using the correct [URI Schemes](https://docs.microsoft.com/en-us/windows/uwp/app-resources/uri-schemes). URI Schemes provide a way to reference files independent of their physical paths (which may change in the future). + +The most useful URI schemes to remember when customizing background images and icons are: + +| URI Scheme | Corresponding Physical Path | Use / description | +| --- | --- | ---| +| `ms-appdata:///Local/` | `%localappdata%\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\` | Per-machine files | +| `ms-appdata:///Roaming/` | `%localappdata%\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\RoamingState\` | Common files | + +> ⚠ Note: Do not rely on file references using the `ms-appx` URI Scheme (i.e. icons). These files are considered an internal implementation detail and may change name/location or may be omitted in the future. + +### Icons +Terminal displays icons for each of your profiles which Terminal generates for any built-in shells - PowerShell Core, PowerShell, and any installed Linux/WSL distros. Each profile refers to a stock icon via the `ms-appx` URI Scheme. + +> ⚠ Note: Do not rely on the files referenced by the `ms-appx` URI Scheme - they are considered an internal implementation detail and may change name/location or may be omitted in the future. + +You can refer to you own icons if you wish, e.g.: + +```json + "icon" : "C:\\Users\\richturn\\OneDrive\\WindowsTerminal\\icon-ubuntu-32.png", +``` + +> πŸ‘‰ Tip: Icons should be sized to 32x32px in an appropriate raster image format (e.g. .PNG, .GIF, or .ICO) to avoid having to scale your icons during runtime (causing a noticeable delay and loss of quality.) + +### Custom Background Images +You can apply a background image to each of your profiles, allowing you to configure/brand/style each of your profiles independently from one another if you wish. + +To do so, specify your preferred `backgroundImage`, position it using `backgroundImageAlignment`, set its opacity with `backgroundImageOpacity`, and/or specify how your image fill the available space using `backgroundImageStretchMode`. + +For example: +```json + "backgroundImage": "C:\\Users\\richturn\\OneDrive\\WindowsTerminal\\bg-ubuntu-256.png", + "backgroundImageAlignment": "bottomRight", + "backgroundImageOpacity": 0.1, + "backgroundImageStretchMode": "none" +``` + +> πŸ‘‰ Tip: You can easily roam your collection of images and icons across all your machines by storing your icons and images in OneDrive (as shown above). + +With these settings, your Terminal's Ubuntu profile would look similar to this: + +![Custom icon and background image](../images/custom-icon-and-background-image.jpg) + + diff --git a/doc/images/custom-icon-and-background-image.jpg b/doc/images/custom-icon-and-background-image.jpg new file mode 100644 index 00000000000..540a8c7c7e2 Binary files /dev/null and b/doc/images/custom-icon-and-background-image.jpg differ diff --git a/doc/specs/#1142 - Keybinding Arguments.md b/doc/specs/#1142 - Keybinding Arguments.md new file mode 100644 index 00000000000..1d187caef9a --- /dev/null +++ b/doc/specs/#1142 - Keybinding Arguments.md @@ -0,0 +1,362 @@ +--- +author: Mike Griese @zadjii-msft +created on: 2019-06-19 +last updated: 2019-07-14 +issue id: 1142 +--- + +# Arbitrary Keybindings Arguments + +## Abstract + +The goal of this change is to both simplify the keybindings, and also enable far +more flexibility when editing a user's keybindings. + +Currently, we have many actions that are very similar in implementation - for +example, `newTabProfile0`, `newTabProfile1`, `newTabProfile2`, etc. All these +actions are _fundamentally_ the same function. However, we've needed to define 9 +different actions to enable the user to provide different values to the `newTab` +function. + +With this change, we'll be able to remove these _essentially_ duplicated events, +and allow the user to specify arbitrary arguments to these functions. + +## Inspiration + +Largely inspired by the keybindings in VsCode and Sublime Text. Additionally, +much of the content regarding keybinding events being "handled" was designed as +a solution for [#2285]. + +## Solution Design + +We'll need to introduce args to some actions that we already have defined. These +are the actions I'm thinking about when writing this spec: + +```csharp + // These events already exist like this: + delegate void NewTabWithProfileEventArgs(Int32 profileIndex); + delegate void SwitchToTabEventArgs(Int32 profileIndex); + delegate void ResizePaneEventArgs(Direction direction); + delegate void MoveFocusEventArgs(Direction direction); + + // These events either exist in another form or don't exist. + delegate void CopyTextEventArgs(Boolean copyWhitespace); + delegate void ScrollEventArgs(Int32 numLines); + delegate void SplitProfileEventArgs(Orientation splitOrientation, Int32 profileIndex); +``` + +Ideally, after this change, the bindings for these actions would look something +like the following: + +```js +{ "keys": ["ctrl+shift+1"], "command": "newTabProfile", "args": { "profileIndex":0 } }, +{ "keys": ["ctrl+shift+2"], "command": "newTabProfile", "args": { "profileIndex":1 } }, +// etc... + +{ "keys": ["alt+1"], "command": "switchToTab", "args": { "index":0 } }, +{ "keys": ["alt+2"], "command": "switchToTab", "args": { "index":1 } }, +// etc... + +{ "keys": ["alt+shift+down"], "command": "resizePane", "args": { "direction":"down" } }, +{ "keys": ["alt+shift+up"], "command": "resizePane", "args": { "direction":"up" } }, +// etc... + +{ "keys": ["alt+down"], "command": "moveFocus", "args": { "direction":"down" } }, +{ "keys": ["alt+up"], "command": "moveFocus", "args": { "direction":"up" } }, +// etc... + +{ "keys": ["ctrl+c"], "command": "copy", "args": { "copyWhitespace":true } }, +{ "keys": ["ctrl+shift+c"], "command": "copy", "args": { "copyWhitespace":false } }, + +{ "keys": ["ctrl+shift+down"], "command": "scroll", "args": { "numLines":1 } }, +{ "keys": ["ctrl+shift+up"], "command": "scroll", "args": { "numLines":-1 } }, + +{ "keys": ["ctrl+alt+1"], "command": "splitProfile", "args": { "orientation":"vertical", "profileIndex": 0 } }, +{ "keys": ["ctrl+alt+shift+1"], "command": "splitProfile", "args": { "orientation":"horizontal", "profileIndex": 0 } }, +{ "keys": ["ctrl+alt+2"], "command": "splitProfile", "args": { "orientation":"vertical", "profileIndex": 1 } }, +{ "keys": ["ctrl+alt+shift+2"], "command": "splitProfile", "args": { "orientation":"horizontal", "profileIndex": 1 } }, +// etc... +``` + +Note that instead of having 9 different `newTabProfile` actions, we have a +singular `newTabProfile` action, and that action requires a `profileIndex` in +the `args` object. + +Also, pay attention to the last set of keybindings, the `splitProfile` ones. +This is a function that requires two arguments, both an `orientation` and a +`profileIndex`. Before this change we would have needed to create 20 separate +actions (10 profile indices * 2 directions) to handle these cases. Now it can +be done with a single action that can be much more flexible in its +implementation. + +### Parsing KeyBinding Arguments + +We'll add two new interfaces: `IActionArgs` and `IActionEventArgs`. Classes that +implement `IActionArgs` will contain all the per-action args, like +`CopyWhitespace` or `ProfileIndex`. `IActionArgs` by itself will be an empty +interface, but all other arguments will derive from it. `IActionEventArgs` will +have a single property `Handled`, which will be used for indicating if a +particular event was processed or not. When parsing args, we'll build +`IActionArgs` to contain all the parameters. When dispatching events, we'll +build `IActionEventArgs` using the `IActionArgs` to set all the parameter values. + +All current keybinding events will be changed from their current types to +`TypedEventHandler`s. These `TypedEventHandler`s second param will always be an +instance of `IActionEventArgs`. So, for example: + +```csharp + +delegate void CopyTextEventArgs(); +delegate void NewTabEventArgs(); +delegate void NewTabWithProfileEventArgs(Int32 profileIndex); +// ... + +[default_interface] +runtimeclass AppKeyBindings : Microsoft.Terminal.Settings.IKeyBindings +{ + event CopyTextEventArgs CopyText; + event NewTabEventArgs NewTab; + event NewTabWithProfileEventArgs NewTabWithProfile; +``` + +Becomes: + +```csharp +interface IActionArgs { /* Empty */ } + +runtimeclass ActionEventArgs +{ + Boolean Handled; + ActionArgs Args; +} + +runtimeclass CopyTextArgs : IActionArgs +{ + Boolean CopyWhitespace; +} + +runtimeclass NewTabWithProfileArgs : IActionArgs +{ + Int32 ProfileIndex; +} +runtimeclass NewTabWithProfileEventArgs : NewTabWithProfileArgs, IActionArgs { } + +[default_interface] +runtimeclass AppKeyBindings : Microsoft.Terminal.Settings.IKeyBindings +{ + event Windows.Foundation.TypedEventHandler CopyText; + event Windows.Foundation.TypedEventHandler NewTab; + event Windows.Foundation.TypedEventHandler NewTabWithProfile; +``` + +In this above example, the `CopyTextArgs` class actually contains all the +potential arguments to the Copy action. `ActionEventArgs` is the class that +holds any `ActionArgs`. When we parse the arguments, we'll build a +`CopyTextArgs`, and when we're dispatching the event, we'll build a +`ActionEventArgs` that holds a `CopyTextArgs` as its `Args` value, and dispatch +the `ActionEventArgs` object. + + +We'll also change our existing map in the `AppKeyBindings` implementation. +Currently, it's a `std::unordered_map`, which +uses the `KeyChord` to lookup the `ShortcutAction`. We'll need to introduce a +new type `ActionAndArgs`: + +```csharp +runtimeclass ActionAndArgs +{ + ShortcutAction Action; + IActionArgs Args; +} +``` + +and we'll change the map in `AppKeyBindings` to a `std::unordered_map`. + +When we're parsing keybindings, we'll need to construct args for each of the +events to go with each binding. When we find some key chord bound to a given +Action, we'll construct the `IActionArgs` for that action. For many actions, +these args will be an empty class. However, when we do find an action that needs +additional parsing, `AppKeyBindingsSerialization` will do the extra work to +parse the args for that action. + +We'll keep a collection of functions that can be used for quickly determining +how to parse the args for an action if necessary. This map will be a +`std::unordered_map>`. For +most actions which don't require args, the function in this map will be set to +nullptr, and we'll know that the action doesn't need to parse any more args. +However, for actions that _do_ require args, we'll set up a global function that +can be used to parse a json blob into an `IActionArgs`. + +Once the `IActionArgs` is built for the keybinding, we'll set it in +`AppKeyBindings` with a updated `AppKeyBindings::SetKeyBinding` call. +`SetKeyBinding`'s signature will be updated to take a `ActionAndArgs` instead. +Should an action not need arguments, the `Args` member can be left `null` in the +`ActionAndArgs`. + +### Executing KeyBinding Actions with Arguments + +When we're handling a keybinding in `AppKeyBindings::_DoAction`, we'll trigger +the event handlers with the `IActionArgs` we've stored in the map with the +`ShortcutAction`. + +Then, in `App`, we'll handle each of these events. We set up lambdas as event +handlers for each event in `App::_HookupKeyBindings`. In each of those +functions, we'll inspect the `IActionArgs` parameter, and use args from its +implementation to call callbacks in the `App` class. We will update `App` to +have methods defined with the actual keybinding function signatures. + +Instead of: + +```c++ + void App::_HookupKeyBindings(TerminalApp::AppKeyBindings bindings) noexcept + { + // ... + bindings.NewTabWithProfile([this](const auto index) { _OpenNewTab({ index }); }); + } +``` + +The code will look like: + +```c++ + void App::_HookupKeyBindings(TerminalApp::AppKeyBindings bindings) noexcept + { + // ... + bindings.NewTabWithProfile({ this, &App::_OpenNewTab }); + } + // ... + void App::_OpenNewTab(const TerminalApp::AppKeyBindings& sender, const NewTabEventArgs& args) + { + auto profileIndex = args.ProfileIndex(); + args.Handled(true); + // ... + } +``` + +### Handling Keybinding Events + +Common to all implementations of `IActionArgs` is the `Handled` property. This +will let the app indicate if it was able to actually process a keybinding event +or not. While in the large majority of cases, the events will all be marked +handled, there are some scenarios where the Terminal will need to know if the +event could not be performed. For example, in the case of the `copy` event, the +Terminal is only capable of copying text if there's actually a selection active. +If there isn't a selection active, the `App` should make sure to not mark the +event as not handled (it will leave `args.Handled(false)`). The App should only +mark an event handled if it has actually dispatched the event. + +When an event is handled, we'll make sure to return `true` from +`AppKeyBindings::TryKeyChord`, so that the terminal does not actually process +that keypress. For events that were not handled by the application, the terminal +will get another chance to dispatch the keypress. + +### Serializing KeyBinding Arguments + +Similar to how we parse arguments from the json, we'll need to update the +`AppKeyBindingsSerialization` code to be able to serialize the arguments from a +particular `IActionArgs`. + +## UI/UX Design + +### Keybindings in the New Tab Dropdown + +Small modifications will need to be made to the code responsible for the new tab +dropdown. The new tab dropdown currently also displays the keybindings for each +profile in the new tab dropdown. It does this by querying for the keybinding +associated with each action. As we'll be removing the old `ShortcutAction`s that +this dropdown uses, we'll need a new way to find which key chord corresponds to +opening a given profile. + +We'll need to be able to not only lookup a keybinding by `ShortcutAction`, but +also by a `ShortcutAction` and `IActionArgs`. We'll need to update the +`AppKeyBindings::GetKeyBinding` method to also accept a `IActionArgs`. We'll +also probably want each `IActionArgs` implementation to define an +`Equals(IActionArgs)` method, so that we can easily check if two different +`IActionArgs` are the same in this method. + +## Capabilities +### Accessibility + +N/A + +### Security + +This should not introduce any _new_ security concerns. We're relying on the +security of jsoncpp for parsing json. Adding new keys to the settings file +will rely on jsoncpp's ability to securely parse those json values. + +### Reliability + +We'll need to make sure that invalid keybindings are ignored. Currently, we +already gracefully ignore keybindings that have invalid `keys` or invalid +`commands`. We'll need to add additional validation on invalid sets of `args`. +When we're parsing the args from a Json blob, we'll make sure to only ever look +for keys we're expecting and ignore everything else. + +If a keybinding requires certain args, but those args are not provided, we'll +need to make sure those args each have reasonable default values to use. If for +any reason a reasonable default can't be used for a keybinding argument, then +we'll need to make sure to display an error dialog to the user for that +scenario. + +When we're re-serializing settings, we'll only know about the keybinding arg +keys that were successfully parsed. Other keys will be lost on re-serialization. + +### Compatibility + +This change will need to carefully be crafted to enable upgrading the legacy +keybindings seamlessly. For most actions, the upgrade should be seamless. Since +they already don't have args, their serializations will remain exactly the same. + +However, for the following actions that we'll be removing in favor of actions +with arguments, we'll need to leave legacy deserialization in place to be able +to find these old actions, and automatically build the correct `IActionArgs` +for them: + +* `newTabProfile` + - We'll need to make sure to build args with the right `profileIndex` + corresponding to the old action. +* `switchToTab` + - We'll need to make sure to build args with the right `index` corresponding + to the old action. +* `resizePane` and `moveFocus` + - We'll need to make sure to build args with the right `direction` + corresponding to the old action. +* `scroll` + - We'll need to make sure to build args with the right `amount` value + corresponding to the old action. `Up` will be -1, and `Down` will be 1. + +### Performance, Power, and Efficiency + +N/A + +## Potential Issues + +N/A + +## Future considerations + +* Should we support some sort of conversion from num keys to an automatic arg? + For example, by default, Alt+<N> to focuses the + Nth tab. Currently, those are 8 separate entries in the keybindings. Should we + enable some way for them be combined into a single binding entry, where the + binding automatically recieves the number pressed as an arg? I couldn't find + any prior art of this, so it doesn't seem worth it to try and invent + currently. This might be something that we want to loop back on, but for the + time being, it remains out of scope of this PR. +* When we inevitable support extensions, we'll need to allow extensions to also + be able to support their own custom keybindings and args. We'll probably want + to pass the settings to the extension to have the extension parse its own + settings. We'll want to be able to ask the extension for its own set of + `ActionAndArgs`[1] that it builds from the `keybindings`. Once we + have that set of actions, we'll be able to store them locally, and dispatch + them quickly. + - [1] We probably won't be able to use the `ActionAndArgs` class directly, + since that class is specific to the actions we define. We'll need another + way for extensions to be able to uniquely identify their own actions. + +## Resources + +N/A + +[#2285]: https://github.com/microsoft/terminal/issues/2285 diff --git a/doc/cascadia/Panes.md b/doc/specs/#532 - Panes and Split Windows.md similarity index 97% rename from doc/cascadia/Panes.md rename to doc/specs/#532 - Panes and Split Windows.md index e165c208ab4..afbd31e24fa 100644 --- a/doc/cascadia/Panes.md +++ b/doc/specs/#532 - Panes and Split Windows.md @@ -1,6 +1,8 @@ --- author: "Mike Griese @zadjii-msft" -created on: 2019-May-16 +created on: 2019-05-16 +last updated: 2019-07-07 +issue id: 523 --- # Panes in the Windows Terminal diff --git a/doc/specs/#754 - Cascading Default Settings.md b/doc/specs/#754 - Cascading Default Settings.md new file mode 100644 index 00000000000..7816468454e --- /dev/null +++ b/doc/specs/#754 - Cascading Default Settings.md @@ -0,0 +1,717 @@ +--- +author: Mike Griese @zadjii-msft +created on: 2019-05-31 +last updated: 2019-07-31 +issue id: 754 +--- + +# Cascading Default + User Settings + +## Abstract + +This spec outlines adding support for a cascading settings model. In this model, +there are two settings files, instead of one. + +1. The default settings file +2. The user's settings file + +The default settings file would be a static, read-only file shipped with the +terminal. The user settings file would then contain all the user's chosen +customizations to the settings. These two files would then be composed together +when the app is launched, so that the runtime settings are the union of both the +defaults and whatever modifications the user has chosen. This will enable the +app to always use a default schema that it knows will be valid, and minimize the +settings that the user needs to customize. + +Should the settings schema ever change, the defaults file will change, without +needing to re-write the user's settings file. + +It also outlines a mechanism by which profiles could be dynamically added or +hidden from the profiles list, based on some external source. + +## Inspiration + +Largely inspired by the settings model that both VS Code (and Sublime Text) use. + +### Goal: Minimize Re-Serializing `profiles.json` + +We want to re-serialize the user settings file, `profiles.json`, as little as +possible. Each time we serialize the file, there's the possiblity that we've +re-ordered the keys, as `jsoncpp` provides no ordering guarantee of the keys. +This isn't great, as each write of the file will randomly re-order the file. + +One of our overarching goals with this change should be to re-serialize the user +settings file as little as possible. + +### Goal: Minimize Content in `profiles.json` + +We want the user to only have to make the minimal number of changes possible to +the user settings file. Additionally, the user should only have to have the +settings that they've changed in that file. If the user wants to change only the +`cursorColor` of a profile, they should only need to set that property in the +user settings file, and not need an entire copy of the `Profile` object in their +user settings file. That would create additional noise that's not relevant to +the user. + +### Goal: Remove the Need to Reset Settings Entirely to get New Settings +One problem with the current settings design is that we only generate "default" +settings for the user when there's no settings file present at all. So, when we +want to do things like update the default profiles to have an icon, or add +support for generating WSL profiles, it will only apply to users for fresh +installs. Otherwise, a user needs to completely delete the settings file to have +the terminal re-generate the default settings. + +This is fairly annoying to the end-user, so ideally we'll find a way to be able +to prevent this scenario. + +### Goal: Prevent Roaming Settings from Failing +Another problem currently is that when settings roam to another machine, it's +possible that the second machine doesn't have the same applications installed as +the first, and some profiles might be totally invalid on the second machine. +Take for example, profiles for WSL distros. If you have and Ubuntu profile on +your first machine, and roam that profile to a second machine without Ubuntu +installed, then the Ubuntu profile would be totally broken on the second +machine. + +While we won't be able to non-destructively prevent all failures of this case, +we should be able to catch it in certain scenarios. + +## Solution Design + +The settings are now composed from two files: a "Default" settings file, and a +"User" settings file. + +When we load the settings, we'll perform the following steps, each mentioned in +greater detail below: +1. Load from disk the `defaults.json` (the default settings) -> DefaultsJson +1. Load from disk the `profiles.json` (the user settings) -> UserJson +1. Parse DefaultsJson to create all the default profiles, schemes, keybindings. +1. [Not covered in this spec] Check the UserJson to find the list of dynamic + profile sources that should run. +1. Run all the _enabled_ dynamic profile generators. Those profiles will be + added to the set of profiles. + - During this step, check if any of the profiles added here don't exist in + UserJson. If they _don't_, the generator created a profile that didn't + exist before. Return a value indicating the user settings should be + re-saved (with the new profiles added). +1. [Not covered in this spec] Layer the UserJson.globals.defaults settings to + every profile in the set, both the defaults, and generated profiles. +1. Apply the user settings from UserJson. Layer the profiles on top of the + existing profiles if possible (if both `guid` and `source` match). If a + profile from the user settings does not already exist, make sure to apply the + UserJson.globals.defaults settings first. Also layer Color schemes and + keybindings. + - If a profile has a `source` key, but there is not an existing profile with + a matching `guid` and `source`, don't create a new Profile object for it. + Either that generator didn't run, or the generator wanted to delete that + profile, so we'll effectively hide the profile. +1. Re-order the list of profiles, to match the ordering in the UserJson. If a + profile doesn't exist in UserJson, it should follow all the profiles in the + UserJson. If a profile listed in UserJson doesn't exist, we can skip it + safely in this step (the profile will be a dynamic profile that didn't get + populated.) +1. Validate the settings. +1. If requested in step 5, write the modified settings back to `profiles.json`. + +### Default Settings + +We'll have a static version of the "Default" file **hardcoded within the +application package**. This `defaults.json` file will live within the +application's package, which will prevent users from being able to edit it. + +```json +// This is an auto-generated file. Place any modifications to your settings in "profiles.json" +``` + +This disclaimer will help identify that the file shouldn't be modified. The file +won't actually be generated, but because it's shipped with our app, it'll be +overridden each time the app is updated. "Auto-generated" should be good enough +to indicate to users that it should not be modified. + +Because the `defaults.json` file is hardcoded within our application, we can use +its text directly, without loading the file from disk. This should help save +some startup time, as we'll only need to load the user settings from disk. + +When we make changes to the default settings, or we make changes to the settings +schema, we should make sure that we update the hardcoded `defaults.json` with +the new values. That way, the `defaults.json` file will always have the complete +set of settings in it. + +### Layering settings + +When we load the settings, we'll do it in three stages. First, we'll deserialize +the default settings that we've hardcoded. We'll then generate any profiles that +might come from dynamic profile sources. Then, we'll intelligently layer the +user's setting upon those we've already loaded. If a user wants to make changes +to some objects, like the default profiles, we'll need to make sure to load from +the user settings into the existing objects we created from the default +settings. + +* We'll need to make sure that any profile in the user settings that has a GUID + matching a default profile loads the user settings into the object created + from the defaults. +* We'll need to make sure that there's only one action bound to each key chord + for a keybinding. If there are any key chords in the user settings that match + a default key chord, we should bind them to the action from the user settings + instead. +* For any color schemes whose name matches the name of a default color scheme, + we'll need to apply the user settings to the existing color scheme. For + example, a user could override the `red` entry of the "Campbell" scheme to be + `#ff9900` if they want. This would then apply to all profiles using the + "Campbell" scheme. +* For profiles that were created from a dynamic profile source, they'll have + both a `guid` and `source` guid that must _both_ match. If a user profile with + a `source` set does not find a matching profile at load time, the profile will + be ignored. See more details in the [Dynamic Profiles](#dynamic-profiles) + section. + +### Hiding Default Profiles + +What if a user doesn't want to see one of the profiles that we've included in +the default profiles? + +We will add a `hidden` key to each profile, which defaults to false. When we +want to mark a profile as hidden, we'd just set that value to `true`, instead of +trying to look up the profile's guid. + +So, if someone wanted to hide the default cmd.exe profile, all they'd have to do +is add `"hidden": true` to the cmd.exe entry in their user settings, like so: + +```js +{ + "profiles": [ + { + // Make changes here to the cmd.exe profile + "guid": "{6239a42c-1de4-49a3-80bd-e8fdd045185c}", + "hidden": true + } + ], +``` + +#### Hidden Profiles and the Open New Tab shortcuts + +Currently, there are keyboard shortcuts for "Open New Tab With Profile +<N>". These shortcuts will open up the Nth profile in the new tab +dropdown. Considering we're adding the ability to remove profiles from that +list, but keep them in the overall list of profiles, we'll need to make sure +that the handler for that event still opens the Nth _visible_ profile. + +### Serializing User Settings + +How can we tell that a setting should be written back to the user settings file? + +If the value of the setting isn't the same as the defaults, then it could easily +be added to the user's `profiles.json`. We'll have to do a smart serialization +of the various settings models. We'll pass in the default version **of that +model** during the serialization. If that object finds that a particular setting +is the same as a default setting, then we'll skip serializing it. + +What happens if a user has chosen to set the value to _coincidentally_ the same +value as the default value? We should keep that key in the user's settings file, +even though it is the same. + +In order to facilitate this, we'll need to keep the originally parsed user +settings around in memory. When we go to serialize the settings, we'll check if +either the setting exists already in the user settings file, or the setting has +changed. If either is true, then we'll make sure to write that setting back out. + +For serializing settings for the default profiles, we'll check if the setting is +in the user settings file, or if the value of the setting is different from the +version of that `Profile` from the default settings. For user-created profiles, +we'll compare the value of the setting with the value of the _default +constructed_ `Profile` object. This will help ensure that each profile in the +user's settings file maintains the minimal amount of info necessary. + +When we're adding profiles due to their generation in a dynamic profile +generator, we'll need to serialize them, then insert them back into the +originally parsed json object to be serialized. We don't want the automatic +creation of a new profile to automatically trigger re-writing the entire user +settings file, but we do want newly created dynamic profiles to have an entry +the user can easily edit. + +### Dynamic Profiles + +Sometimes, we may want to auto-generate a profile on the user's behalf. Consider +the case of WSL distros on their machine, or VMs running in Azure they may want +to auto-connect to. These _dynamic_ profiles have a source that might be added +or removed after the app is installed, and they will be different from user to +user. + +Currently, these profiles are only generated when a user first launches the +Terminal. If they already have a `profiles.json` file, then we won't run the +auto-generation behavior. This is obviously not great - if any new types of +dynamic profiles are added, then users that already have the Terminal installed +won't get any of these dynamic profiles. Furthemore, if any of the sources of +these dynamic profiles are removed, then the app won't auto-remove the +associated profile. + +In the new model, with a combined defaults & user settings, how should these +dynamic profiles work? + +I propose we add functionality to automatically search for these profile sources +and add/remove them on _every_ Terminal launch. To make this functionality work +appropriately, we'll need to introduce a constraint on dynamic profiles. + +**For any dynamic profiles, they must be able to be generated using a stable +GUID**. For example, any time we try adding the "Ubuntu" profile, we must be +able to generate the same GUID every time. This way, when a dynamic profile +generator runs, it can check if that profile source already has a profile +associated with it, and do nothing (as to not create many duplicate "Ubuntu" +profiles, for example). + +Additionally, each dynamic profile generator **must have a unique source guid** +to associate with the profile. When a dynamic profile is generated, the source's +guid will be added to the profile, to make sure the profile is correlated with +the source it came from. + +We'll generate these dynamic profiles immediately after parsing the default +profiles and settings. When a generator runs, it'll be able to create unique +profile GUIDs for each source it wants to generate a profile for. It'll hand +back a list of Profile objects, with settings set up how the generator likes, +with GUIDs set. + +After a dynamic profile generator runs, we will determine what new profiles need +to be added to the user settings, so we can append those to the list of +profiles. The deserializer will look at the list of generated profiles and check +if each and every one already has a entry in the user settings. The generator +will just blind hand back a list of profiles, and the deserializer will figure +out if any of them need to be added to the user settings. We'll store some sort +of result indicating that we want a save operation to occur. After the rest of +the deserializing is done, the app will then save the `profiles.json` file, +including these new profiles. + +When we're serializing the settings, instead of comparing a dynamic profile to +the default-constructed `Profile`, we'll compare it to the state of the +`Profile` after the dynamic profile generator created it. It'd then only +serialize settings that are different from the auto-generated version. It will +also always make sure that the `guid` of the dynamic profile is included in the +user settings file, as a point for the user to add customizations to the dynamic +profile to. Additionally, we'll also make sure the `source` is always serialized +as well, to keep the profile correlated with the generator that created it. + +We'll need to keep the state of these dynamically generated profiles around in +memory during runtime to be able to ensure the only state we're serializing is +that which is different from the initially generated dynamic profile. + +When the generator is run, and determines that a new profile has been added, +we'll need to make sure to add the profile to the user's settings file. This +will create an easy point for users to customize the dynamic profiles. When +added to the user settings, all that will be added is the `name`, `guid`, and +`source`. + +Additionally, a user might not want a dynamic profile generator to always run. +They might want to keep their Azure connections visible in the list of profiles, +even if its no longer a valid target. Or they might want to not automatically +connect to Azure to find new instances every time they launch the terminal. To +enable scenarios like this, we'll add an additional setting, +`disabledProfileSources`. This is an array of guids. If any guids are in that +list, then those dynamic profile generators _won't_ be run, suppressing those +profiles from appearing in the profiles list. + +If a dynamic profile generator needs to "delete" a profile, this will also work +naturally with the above rules. Lets examine the case where the user has +uninstalled the Ubuntu distro. When the WSL generator runs, it won't create the +Ubuntu profile. When we get to the Ubuntu profile in the user's settings, it'll +have a `source`, but we won't already have a profile with that `guid` and +`source`. So we'll just ignore it, because whatever source for that profile +doesn't want it anymore. Effectively, this will act like it was "deleted", +though the artifacts still remain untouched in the user's json. + +#### What if a dynamic profile is removed, but it's the default? + +I'll direct our attention to [#1348] - Display a specific error for not finding +the default profile. When we're done loading, and we determine that the default +profile doesn't exist in the finalized list of profiles, we'll display a dialog +to the user. This includes both hidden profiles and dynamic profiles that have +been "deleted". We'll temporarily use the _first_ profile instead. + +#### Dynamic profile GUID generation + +In order to help facilitate the generation of stable, unique GUIDs for +dynamically generated profiles, we'll enforce a few methods on each generator. +The Generator should implement a method that returns its _unique_ namespace for +profiles it generates: + +```c++ +class IDynamicProfileGenerator +{ + ... + virtual std::wstring GetNamespace() = 0; + ... +} +``` + +For example, the WSL generator would return `Microsoft.Terminal.WSL`. The +Powershell Core generator would return `Microsoft.Terminal.PowershellCore`. +We'll use these names to be able to generate uuidv5 GUIDs that will be unique +(so long as the names are unique). + +The generator should also be able to ask the app for two other pieces of +functionality: +* The generator should be able to ask the app for the generator's own namespace + GUID +* The generator should be able to ask the app for a uuidv5 in the generator's + namespace, given a specific name key. + +These two functions will be exposed to the generator like so: + +```c++ +GUID GetNamespaceGuid(IDynamicProfileGenerator& generator); +GUID GetGuidForName(IDynamicProfileGenerator& generator, std::wstring& name); +``` + +The generator does not _need_ to use `GetGuidForName` to generate guids for it's +profiles. If the generator can determine another way to generate stable GUIDs +for its profiles, it's free to use whatever method it wants. `GetGuidForName` is +provided as a convenience. + +It's not the responsibility of the dynamic profile generator to fill in the +`source` of the profiles it generates. The deserializer will make sure to go +through and fill in the guid for the generated profiles given the generator's +namespace GUID. + +### Powershell Core & the Defaults + +How do we handle the potential existence of Powershell Core in this model? +Powershell core is unique as far as the default profiles goes - it may or may +not exist on the user's system. Not only that, but depending on the user's +install of Powershell Core, it might have a path in either `Program Files` or +`Program Files(x86)`. + +Additionally, if it _is_ installed, we set it as the default profile instead of +Windows Powershell. + +Powershell core acts much like a dynamic profile. It has an installation source +that may or not be there. So we'll add a dynamic profile generator for +Powershell Core. This will automatically create a profile for Powershell Core if +necessary. + +Unlike the other dynamic profiles, if Powershell Core is present on +_first_ launch of the terminal, we set that as the default profile. This can +still be done - we'll need to do some special-case work when we're loading the +user settings and we _don't_ find any existing settings. When that happens, +we'll generate all the default user settings. Before we commit them, we'll check +if the Powershell Core profile exists, and if it does, we'll set that as the +default profile before writing the settings to disk. + +### Unbinding a Keybinding + +How can a user unbind a key that's part of the default keybindings? What if a +user really wants ctrl+t to fall through to the +commandline application attached to the shell, instead of opening a new tab? + +We'll need to introduce a new keybinding command that should indicate that the +key is unbound. We'll load the user keybindings and layer them on the defaults +as described above. If during the deserializing we find an entry that's bound to +the command `"unbound"` or any other string that we don't understand, instead of +trying to _set_ the keybinding, we'll _clear_ the keybinding with a new method +`AppKeyBindings::ClearKeyBinding(chord)`. + +### Removing the Globals Object + +As a part of #[1005](https://github.com/microsoft/terminal/pull/1005), all the +global settings were moved to their own object within the serialized settings. +This was to try and make the file easier to parse as a user, considering global +settings would be intermingled with profiles, keybindings, color schemes, etc. +Since this change will make the user settings dramatically easier to navigate, +we should probably remove the `globals` object, and have globals at the root +level again. + +### Default `profiles.json` + +Below is an example of what the default user settings file might look like when +it's first generated, taking all the above points into consideration. + +```js +// To view the default settings, open \defaults.json +{ + "defaultProfile" : "{574e775e-4f2a-5b96-ac1e-a2962a402336}", + "profiles": [ + { + // Make changes here to the cmd.exe profile + "guid": "{6239a42c-1de4-49a3-80bd-e8fdd045185c}" + }, + { + // Make changes here to the Windows Powershell profile + "guid": "{086a83cd-e4ef-418b-89b1-3f6523ff9195}", + }, + { + "guid": "{574e775e-4f2a-5b96-ac1e-a2962a402336}", + "name" : "Powershell Core", + "source": "{2bde4a90-d05f-401c-9492-e40884ead1d8}", + } + ], + + // Add custom color schemes to this array + "schemes": [], + + // Add any keybinding overrides to this array. + // To unbind a default keybinding, set the command to "unbound" + "keybindings": [] +} + +``` + +Note the following: +* cmd.exe and powershell.exe are both in the file, as to give users an easy + point to extend the settings for those default profiles. +* Powershell Core is included in the file, and the default profile has been set + to its GUID. The `source` has been set, indicating that it came from a dynamic profile source. +* There are a few helpful comments scattered throughout the file to help point + the user in the right direction. + +### Re-ordering profiles + +Since there are shortcuts to open the Nth profile in the list of profiles, we +need to expose a way for the user to change the order of the profiles. This was +not a problem when there was only a single list of profiles, but if the defaults +are applied _first_ to the list of profiles, then the user wouldn't be able to +change the order of the default profiles. Additionally, any profiles they add +would _always_ show up after the defaults. + +To remedy this, we could scan the user profiles in the user settings first, and +create `Profile` objects for each of those profiles first. These `Profile`s +would only be initialized with their GUID temporarily, but they'd be placed into +the list of profiles in the order they appear in the user's settings. Then, we'd +load all the default settings, overlaying any default profiles on the `Profile` +objects that might already exist in the list of profiles. If there are any +default profiles that don't appear in the user's settings, they'll appear +_after_ any profiles in the user's settings. Then, we'll overlay the full user +settings on top of the defaults. + +## UI/UX Design + +### Opening `defaults.json` +How do we open both these files to show to the user (for the interim period +before a proper Settings UI is created)? Currently, the "Settings" button only +opens a single json file, `profiles.json`. We could keep that button doing the +same thing, though we want the user to be able to also view the default settings +file, to be able to inspect what settings they wish to change. + +We could have the "Settings" button open _both_ files at the same +time. I'm not sure that `ShellExecute` (which is used to open these files) +provides any ordering guarantees, so it's possible that the `defaults.json` +would open in the foreground of the default json editor, while making in unclear +that there's another file they should be opening instead. Additionally, if +there's _no_ `.json` editor for the user, I believe the shell will attempt +_twice_ to ask the user to select a program to open the file with, and it might +not be clear that they need to select a program in both dialogs. + +Alternatively, we could make the defaults file totally inaccessible from the +Terminal UI, and instead leave a comment in the auto-generated `profiles.json` +like so: + +```json +// To view the default settings, open the defaults.json file in this directory +``` + +The "Settings" button would then only open the file the user needs to edit, and +provide them instructions on how to open the defaults file. + +There could alternatively be a hidden option for the "Open Settings" button, +where holding Alt while clicking on the button would open the +`defaults.json` instead. + +We could additionally add a `ShortcutAction` (to be bound to a keybinding) that +would `openDefaultSettings`, and we could bind that to +ctrl+alt+\`, similar to `openSettings` on +ctrl+\`. + +### How does this work with the settings UI? + +If we only have one version of the settings models (Globals, Profiles, +ColorShemes, Keybindings) at runtime, and the user changes one of the settings +with the settings UI, how can we tell that settings changed? + +Fortunately, this should be handled cleanly by the algorithm proposed above, in +the "Serializing User Settings" section. We'll only be serializing settings that +have changed from the defaults, so only the actual changes they've made will be +persisted back to the user settings file. + +## Capabilities +### Security + +I don't think this will introduce any new security issues that weren't already +present + +### Reliability +I don't think this will introduce any new reliability concerns that weren't +already present. We will likely improve our reliability, as dynamic profiles +that no longer exist will not cause the terminal to crash on startup anymore. + +### Performance, Power, and Efficiency + +By not writing the defaults to disk, we'll theoretically marginally improve the +load and save times for the `profiles.json` file, by simply having a smaller +file to load. However we'll also be doing more work to process the layering of +defaults and user settings, which will likely slightly increase the load times. +Overall, I expect the difference to be negligible due to these factors. + +One potential concern is long-running dynamic profile generators. Because +they'll need to run on startup, they could negatively impact startup time. You +can read more below, in "Dynamic Profile Generators Need to be Enabled". + +### Accessibility +N/A + +## Potential Issues + +### Profiles with the same `guid` as a dynamic profile but not the same `source` + +What happens if the User settings has a profile with a `guid` that matches a +dynamic or default profile, but the user profile doesn't have a matching source? +This could happen trivially easily if the user deletes the `source` key from a +profile that has dynamically generated. + +We could: +1. Treat the profile as an entirely separate profile + - There's lots of other code that assumes each profile has only a unique GUID, + so we'd have to change the GUID of this profile. This would mean writing out + the user settings, which we'd like to avoid. + - We'll still end up generating the entry for the dynamic profile in the + user's settings, so we'll need to write out the user settings anyways. + - This other profile will likely not have a commandline set, so it might not + work at all. +1. Ignore the profile entirely. + - When the dynamic profile generator runs, we're not going to find another + entry in the user profiles with both a matching `guid` and a matching + `source`. So we'll end up creating _another_ entry in the user profiles for + the dynamic profile. + - How could the user know that the profile is being ignored? There's nothing + in the file itself that indicates obviously that this profile is now + invalid. +1. Treat the user settings as part of the dynamic profile + - In this scenario, the user profile continues to exist as part of the dynamic profile. + - When the dynamic profile generator runs, we're not going to find another + entry in the user profiles with both a matching `guid` and a matching + `source`. So we'll end up creating _another_ entry in the user profiles for + the dynamic profile. + - These two entries will each be layered upon the dynamically generated + profile, so the settings in the second profile entry will override + settings from the first. + - If the user disables the generator, or the profile source is removed, the + dynamic profile will cease to exist. However, the profile without the + `source` entry will remain, though likely will not work. + - How do we order these profiles for the user? When we're parsing the user + profiles list to build an ordering of profiles, do we use the first entry as + the index for that profile? +1. (Variant of the above) Treat the profile as part of the dynamic profile, and + re-insert the `source` key. + - This will re-connect the user profile to the dynamic one. + - We'll need to make sure to do this before determining the new dynamic + profiles to add to the user settings. + - Given all the scenarios are going to cause a user settings write anyways, + this isn't terrible. + - If the user _really_ wants to split the profile in their user settings from + the dynamic one, they're free to always generate a new guid _and_ delete the + `source` key. + +Given the drawbacks associated with options 1-3, I propose we choose option 4 as +our solution to this case. + +### Migrating Existing Settings + +I believe that existing `profiles.json` files will smoothly update to this +model, without breaking. While in the new model, the `profiles.json` file can be +much more sparse, users who have existing `profiles.json` files will have full +settings in their user settings. We'll leave their files largely untouched, as +we won't touch keys that have the same values as defaults that are currently in +the `profiles.json` file. Fortunately though, users should be able to remove +much of the boilerplate from their `profiles.json` files, and trim it down just +to their modifications. + +#### Migrating Powershell Core + +Right now, default-generated Powershell Core profiles exist with a stable guid +we've generated for them. However, when we move Powershell Core to being a +dynamically generated profile, we'll have to ensure that we don't create a +duplicated "dynamic" entry for that profile. If we want to convert the existing +Powershell Core profiles into a dynamic profile, we'll need to make sure to add +a `source` key to the profile. Everything else in the profile can remain the +same. Once the `source` is added, we'll know to treat it as a dynamic profile, +and it'll respond dynamically. + +This is actually something that will automatically be covered by the scenario +mentioned above in "Profiles with the same `guid` as a dynamic profile but not +the same `source`". When we encounter the existing Powershell Core profiles that +don't have a `source`, we'll automatically think they're the dynamically +generated ones, and auto-migrate them. + +#### Migrating Existing WSL Profiles + +Similar to the above, so long as we ensure the WSL dynamic profile generator +generates the _same_ GUIDs as it does currently, all the existing WSL profiles +will automatically be migrated to dynamic profiles. + +### Dynamic Profile Generators Need to be Enabled +With the current proposal, profiles that are generated by a dynamic profile +generator _need_ that generator to be enabled for the profile to appear in the +list of profiles. If the generator isn't enabled, then the important parts of +the profile (name, commandline) will never be set, and the profile's settings +from the user settings will be ignored at runtime. + +For generators where the generation of profiles might be a lengthy process, this +could negatively impact startup time. Take for example, some hypothetical +generator that needs to make web requests to generate dynamic profiles. Because +we need the finalized settings to be able to launch the terminal, we'll be stuck +loading until that generator is complete. + +However, if the user disables that generator entirely, we'll never display that +profile to the user, even if they've done that setup before. + +So the trade-off with this design is that non-existent dynamic profiles will +never roam to machines where they don't exist and aren't valid, but the +generators _must_ be enabled to use the dynamic profiles. + +## Future considerations +* It's possible that a very similar layering loading mechanism could be used to + layer per-machine settings with roaming settings. Currently, there's only one + settings file, and it roams to all your devices. This could be problematic, + for example, if one of your machines has a font installed, but another + doesn't. A proposed solution to that problem was to have both roaming settings + and per-machine settings. The code to layer settings from the defaults and the + user settings could be re-used to handle layer the roaming and per-machine + settings. +* What if an extension wants to generate their own dynamic profiles? We've + already outlined a contract that profile generators would have to follow to + behave correctly. It's possible that we could abstract our implementation into + a WinRT interface that extensions could implement, and be triggered just like + other dynamic profile generators. +* **Multiple settings files** - This could enable us to place color schemes into + a seperate file (like `colorschemes.json`) and put keybindings into their own + file as well, and reduce the number of settings in the user's `profiles.json`. + It's unclear if this is something that we need quite yet, but the same + layering functionality that enables this scenario could also enable more than + two sources for settings. +* **Global Default Profile Settings** - Say a user wants to override what the + defaults for a profile are, so that they can set settings for _all_ their + profiles at once? We could maybe introduce a profile in the user settings file + with a special guid set to `"default`, that we look for first, and treat + specially. We wouldn't include it in the list of profiles. When we're creating + profiles, we'll start with that profile as our prototype, instead of using the + default-constructed `Profile`. When we're serializing profiles, we'd again use + that as the point of comparison to check if a setting's value has changed. + There may be more unknowns with this proposal, so I leave it for a future + feature spec. + - We'll also want to make sure that when we're serializing default/dynamic + profiles, we take into account the state from the global defaults, and we + don't duplicate that inormation into the entries for those types of profiles + in the user profiles. +* **Re-ordering profiles** - Under "Solution Design", we provide an algorithm + for decoding the settings. One of the steps mentioned is parsing the user + settings to determine the ordering of the profiles. It's possible in the + future we may want to give the user more control over this ordering. Maybe + we'll want to allow the user to manually index the profiles. Or, as discussed + in issues like #1571, we may want to allow the user to further customize the + new tab dropdown, beyond just the order of profiles. The re-ordering step + would be a great place to add code to support this re-ordering, with whatever + algorithm we eventually land on. Determining such an algorithm is outside the + scope of this spec, however. + +## Resources +N/A + + + + +[#1348]: https://github.com/microsoft/terminal/issues/1348 diff --git a/doc/specs/#976 - VT52 escape sequences.md b/doc/specs/#976 - VT52 escape sequences.md new file mode 100644 index 00000000000..90b2ef60591 --- /dev/null +++ b/doc/specs/#976 - VT52 escape sequences.md @@ -0,0 +1,255 @@ +--- +author: James Holderness @j4james +created on: 2019-07-17 +last updated: 2019-07-28 +issue id: 976 +--- + +# VT52 Escape Sequences + +## Abstract + +This spec outlines the work required to split off the existing VT52 commands from the VT100 implementation, and extend the VT52 support to cover all of the core commands. + +## Inspiration + +The existing VT52 commands aren't currently implemented as a separate mode, so they conflict with sequences defined in the VT100 specification. This is blocking us from adding support for the VT100 Index (IND) escape sequence, which is one of the missing commands required to pass the test of cursor movements in Vttest. + +## Solution Design + +The basic idea is to add support for the [DECANM private mode sequence](https://vt100.net/docs/vt100-ug/chapter3.html#DECANM), which can then be used to switch from the default _ANSI_ mode, to a new _VT52_ mode. Once in _VT52_ mode, there is a separate [_Enter ANSI Mode_ sequence](https://vt100.net/docs/vt100-ug/chapter3.html#VT52ANSI) (`ESC <`) to switch back again. + +In terms of implementation, there are a number of areas of the system that would need to be updated. + +### The State Machine + +In order to implement the VT52 compatibility mode correctly, we'll need to introduce a flag in the `StateMachine` class that indicates the mode that is currently active. When in VT52 mode, certain paths in the state diagram should not be followed - for example, you can't have CSI, OSC, or SS3 escape sequences. There would also need to be an additional state to handle VT52 parameters (for the _Direct Cursor Address_ command). These parameters take a different form to the typical VT100 parameters, as they follow the command character instead of preceding it. + +It would probably be best to introduce a new dispatch method in the `IStateMachineEngine` interface to handle the parsed VT52 sequences, since the existing `ActionEscDispatch` does not support parameters (which are required for the _Direct Cursor Address_ command). I think it would also make for a cleaner implementation to have the VT52 commands separate from the VT100 code, and would likely have less impact on the performance that way. + +### The Terminal Input + +The escape sequences generated by the keyboard for function keys, cursor keys, and the numeric keypad, are not the same in VT52 mode as they are in ANSI mode. So there would need to be a flag in the `TerminalInput` class to keep track of the current mode, and thus be able to generate the appropriate sequences for that mode. + +Technically the VT52 keyboard doesn't map directly to a typical PC keyboard, so we can't always work from the specs in deciding what sequences are required for each key. When in doubt, we should probably be trying to match the key sequences generated by XTerm. The sequences below are based on the default XTerm mappings. + +**Function Keys** + +The functions keys F1 to F4 generate a simple ESC prefix instead of SS3 (or CSI). These correspond with the four function keys on the VT100 keypad. In V52 mode they are not affected by modifiers. + +Key | ANSI mode | VT52 mode +---------------|-----------|----------- +F1 | `SS3 P` | `ESC P` +F2 | `SS3 Q` | `ESC Q` +F3 | `SS3 R` | `ESC R` +F4 | `SS3 S` | `ESC S` + +The function keys F5 to F12 generate the same sequences as they do in ANSI mode, except that they are not affected by modifiers. These correspond with a subset of the top-row functions keys on the VT220, along with the Windows Menu key mapping to the VT220 DO key. + +Key | Sequence +----------------|------------- +F5 | `CSI 1 5 ~` +F6 | `CSI 1 7 ~` +F7 | `CSI 1 8 ~` +F8 | `CSI 1 9 ~` +F9 | `CSI 2 0 ~` +F10 | `CSI 2 1 ~` +F11 | `CSI 2 3 ~` +F12 | `CSI 2 4 ~` +Menu | `CSI 2 9 ~` + +**Cursor and Editing Keys** + +The cursor keys generate a simple ESC prefix instead of CSI or SS3. These correspond with the cursor keys on the VT100, except for Home and End, which are XTerm extensions. In V52 mode, they are not affected by modifiers, nor are they affected by the DECCKM _Cursor Keys_ mode. + +Key | ANSI mode | VT52 mode +-----------------|-----------|----------- +Up | `CSI A` | `ESC A` +Down | `CSI B` | `ESC B` +Right | `CSI C` | `ESC C` +Left | `CSI D` | `ESC D` +End | `CSI F` | `ESC F` +Home | `CSI H` | `ESC H` + +The "editing" keys generate the same sequences as they do in ANSI mode, except that they are not affected by modifiers. These correspond with a subset of the editing keys on the VT220. + +Key | Sequence +----------------|----------- +Ins | `CSI 2 ~` +Del | `CSI 3 ~` +PgUp | `CSI 5 ~` +PgDn | `CSI 6 ~` + +**Numeric Keypad** + +With Num Lock disabled, most of the keys on the numeric keypad function the same as cursor keys or editing keys, but with the addition of a center 5 key. As a described above, the cursor keys generate a simple ESC prefix instead of CSI or SS3, while the editing keys remain unchanged (with the exception of modifiers). + +In V52 mode, most modifiers are ignored, except for Shift, which is the equivalent of enabling Num Lock (i.e. the keys just generate their corresponding digit characters or `.`). With Num Lock enabled, it's the other way arround - the digits are generated by default, while Shift enables the cursor/editing functionality. + +Key | Alias | ANSI mode | VT52 mode +-------------|-------|-----------|----------- +. | Del | `CSI 3 ~` | `CSI 3 ~` +0 | Ins | `CSI 2 ~` | `CSI 2 ~` +1 | End | `CSI F` | `ESC F` +2 | Down | `CSI B` | `ESC B` +3 | PgDn | `CSI 6 ~` | `CSI 6 ~` +4 | Left | `CSI D` | `ESC D` +4 | Clear | `CSI E` | `ESC E` +6 | Right | `CSI C` | `ESC C` +7 | Home | `CSI H` | `ESC H` +8 | Up | `CSI A` | `ESC A` +9 | PgUp | `CSI 5 ~` | `CSI 5 ~` + +When the DECKPAM _Alternate/Application Keypad Mode_ is set, though, the Shift modifier has a different affect on the numeric keypad. The sequences generated now correspond with the VT100/V52 numeric keypad keys. In VT52 mode, these sequences are not affected by any other modifiers, and this mode only applies when Num Lock is disabled. + +Key | Alias | ANSI mode | VT52 mode +-------------|-------|-----------|----------- +. | Del | `SS3 2 n` | `ESC ? n` +0 | Ins | `SS3 2 p` | `ESC ? p` +1 | End | `SS3 2 q` | `ESC ? q ` +2 | Down | `SS3 2 r` | `ESC ? r` +3 | PgDn | `SS3 2 s` | `ESC ? s` +4 | Left | `SS3 2 t` | `ESC ? t` +4 | Clear | `SS3 2 u` | `ESC ? u` +6 | Right | `SS3 2 v` | `ESC ? v` +7 | Home | `SS3 2 w` | `ESC ? w` +8 | Up | `SS3 2 x` | `ESC ? x` +9 | PgUp | `SS3 2 y` | `ESC ? y` + +When the DECKPAM _Alternate/Application Keypad Mode_ is set, the "arithmetic" keys on the numeric keypad are also affected (this includes the Enter key). The sequences generated again correspond with the VT100/VT52 numeric keys (more or less), but this mapping is active even without the Shift modifier (and in VT52 mode all other modifiers are ignored too). As above, the mode only applies when Num Lock is disabled. + +Key | ANSI mode | VT52 mode +-----------------|-----------|----------- +* | `SS3 j` | `ESC ? j` ++ | `SS3 k` | `ESC ? k` +- | `SS3 m` | `ESC ? m` +/ | `SS3 o` | `ESC ? o` +Enter | `SS3 M` | `ESC ? M` + +Note that the DECKPAM _Application Keypad Mode_ is not currently implemented in ANSI mode, so perhaps that needs to be addressed first, before trying to add support for the VT52 _Alternate Keypad Mode_. + +### Changing Modes + +The `_PrivateModeParamsHelper` method in the `AdaptDispatch` class would need to be extended to handle the DECANM mode parameter, and trigger a function to switch to VT52 mode. The typical pattern for this seems to be through a `PrivateXXX` method in the `ConGetSet` interface. Then the `ConhostInternalGetSet` implementation can pass that flag on to the active output buffer's `StateMachine`, and the active input buffer's `TerminalInput` instance. + +Changing back from VT52 mode to ANSI mode would need to be achieved with a separate VT52 command (`ESC <`), since the VT100 CSI mode sequences would no longer be active. This would be handled in the same place as the other VT52 commands, in the `OutputStateMachineEngine`, and then passed on to the mode selection method in the `AdaptDispatch` class described above (essentially the equivalent of the DECANM private mode being set). + +### Additional VT52 Commands + +Most of the missing VT52 functionality can be implemented in terms of existing VT100 methods. + +* The _Cursor Up_ (`ESC A`), _Cursor Down_ (`ESC B`), _Cursor Left_ (`ESC D`), and _Cursor Right_ (`ESC C`) commands are already implemented. +* The _Enter Graphics Mode_ (`ESC F`) and _Exit Graphics Mode_ (`ESC G`) commands can probably use the existing `DesignateCharset` method, although this would require a new `VTCharacterSets` option with a corresponding table of characters (see below). +* The _Reverse Line Feed_ (`ESC I`) command can use the existing `ReverseLineFeed` method. +* The _Erase to End of Display_ (`ESC J`) and _Erase to End of Line_ (`ESC K`) commands can use the existing `EraseInDisplay` and `EraseInLine` methods. +* The _Cursor Home_ (`ESC H`) and _Direct Cursor Address_ (`ESC Y`) commands can probably be implemented using the `CursorPosition` method. Technically the _Direct Cursor Address_ has different rules for the boundary conditions (the CUP command clamps out of range coordinates, while the _Direct Cursor Address_ command ignores them, judged individually - one may be ignored while the other is interpreted). Nobody seems to get that right, though, so it's probably not that big a deal. +* The _Identify_ (`ESC Z`) command may be the only one that doesn't build on existing functionality, but it should be a fairly trivial addition to the `AdaptDispatch` class. For a terminal emulating VT52, the identifying sequence should be `ESC / Z`. +* The _Enter Keypad Mode_ (`ESC =`) and _Exit Keypad Mode_ (`ESC >`) commands can use the existing `SetKeypadMode` method, assuming the `TerminalInput` class already knows to generate different sequences when in VT52 mode (as described in the _Terminal Input_ section above). +* The _Enter ANSI Mode_ (`ESC <`) command can just call through to the new mode selection method in the `AdaptDispatch` class as discussed in the _Changing Modes_ section above. + +There are also a few VT52 print commands, but those are not technically part of the core command set, and considering we don't yet support any of the VT102 print commands, I think they can probably be considered out of scope for now. Briefly they are: + +* _Auto Print_ on (`ESC ^`) and off (`ESC _`) commands. In auto print mode, a display line prints after you move the cursor off the line, or during an auto wrap. +* _Print Controller_ on (`ESC W`) and off (`ESC X`) commands. When enabled, the terminal transmits received characters to the printer without displaying them. +* The _Print Cursor Line_ (`ESC V`) command prints the display line with the cursor. +* The _Print Screen_ (`ESC ]`) command prints the screen (or at least the scrolling region). + +I suspect most, if not all of these, would be direct equivalents of the VT102 print commands, if we ever implemented those. + +### Graphic Mode Character Set + +The table below lists suggested mappings for the _Graphics Mode_ character set, based on the descriptions in the [VT102 User Guide](https://vt100.net/docs/vt102-ug/table5-15.html). + +Note that there is only the one _fraction numerator_ character in Unicode, so superscript digits have instead been used for the numerators 3, 5, and 7. There are also not enough _horizontal scan line_ characters (for the _bar at scan x_ characters), so each of them is used twice to cover the full range. + +ASCII Character |Mapped Glyph |Unicode Value |Spec Description +----------------|---------------|---------------|---------------- +_ | |U+0020 |Blank +` | |U+0020 |Reserved +a |β–ˆ |U+2588 |Solid rectangle +b |β…Ÿ |U+215F |1/ +c |Β³ |U+00B3 |3/ +d |⁡ |U+2075 |5/ +e |⁷ |U+2077 |7/ +f |Β° |U+00B0 |Degrees +g |Β± |U+00B1 |Plus or minus +h |β†’ |U+2192 |Right arrow +i |… |U+2026 |Ellipsis (dots) +j |Γ· |U+00F7 |Divide by +k |↓ |U+2193 |Down arrow +l |⎺ |U+23BA |Bar at scan 0 +m |⎺ |U+23BA |Bar at scan 1 +n |⎻ |U+23BB |Bar at scan 2 +o |⎻ |U+23BB |Bar at scan 3 +p |⎼ |U+23BC |Bar at scan 4 +q |⎼ |U+23BC |Bar at scan 5 +r |⎽ |U+23BD |Bar at scan 6 +s |⎽ |U+23BD |Bar at scan 7 +t |β‚€ |U+2080 |Subscript 0 +u |₁ |U+2081 |Subscript 1 +v |β‚‚ |U+2082 |Subscript 2 +w |₃ |U+2083 |Subscript 3 +x |β‚„ |U+2084 |Subscript 4 +y |β‚… |U+2085 |Subscript 5 +z |₆ |U+2086 |Subscript 6 +{ |₇ |U+2087 |Subscript 7 +\| |β‚ˆ |U+2088 |Subscript 8 +} |₉ |U+2089 |Subscript 9 +\~ |ΒΆ |U+00B6 |Paragraph + +### Testing + +A simple unit test will need to be added to the `AdapterTest` class, to confirm that calls to toggle between the ANSI and VT52 modes in the `AdaptDispatch` class are correctly forwarded to the corresponding `PrivateXXX` handler in the `ConGetSet` interface. + +The majority of the testing would be handled in the `StateMachineExternalTest` class though. These tests would confirm that the various VT52 sequences trigger the expected methods in the `ITermDispatch` interface when VT52 Mode is enabled, and also that they don't do anything when in ANSI mode. + +There shouldn't really be any need for additional tests in the `ScreenBufferTests` class, since we're relying on existing VT100 functionality which should already be tested there. + +For fuzzing support, we'll need to add the DECANM option to the `GeneratePrivateModeParamToken` method in the `VTCommandFuzzer` class, and also probably add two additional token generator methods - one specifically for the _Direct Cursor Address_ command, which requires parameters, and another to handle the remaining parameterless commands. + +In terms of manual testing, it can be useful to run the _Test of VT52 mode_ option in Vttest, and confirm that everything looks correct there. It's also worth going through some of the options in the The _Test of keyboard_ section, since those tests aren't only intended for the later VT models - they do cover the VT52 keyboard as well. + +## UI/UX Design + +There is no additional UI associated with this feature. + +## Capabilities + +### Accessibility + +This should not impact accessibility any more than the existing escape sequences. + +### Security + +This should not introduce any new security issues. + +### Reliability + +This should not introduce any new reliability issues. + +### Compatibility + +This could be a breaking change for code that relies on the few existing VT52 commands being available without a mode change. However, that functionality is non-standard, and has not been around for that long. There is almost certainly more benefit in being able to implement the missing VT100 functionality than there is in retaining that non-standard behaviour. + +### Performance, Power, and Efficiency + +The additional mode flags and associated processing in the `StateMachine` and `TerminalInput` classes could have some performance impact, but that is unlikely to be significant. + +## Potential Issues + +The only negative impacts I can think of would be the potential for breaking changes, and the possible impact on performance, as discussed in the _Compatibility_ and _Performance_ sections above. But as with any new code, there is always the possibility of new bugs being introduced as well. + +## Future considerations + +As mentioned in the _Inspiration_ section, having the VT52 functionality isolated with a new mode would enable us to implement the VT100 Index (IND) escape sequence, which currently conflicts with the VT52 _Cursor Left_ command. + +## Resources + +* [VT52 Mode Control Sequences](https://vt100.net/docs/vt100-ug/chapter3.html#S3.3.5) +* [VT100 ANSI/VT52 Mode (DECANM)](https://vt100.net/docs/vt100-ug/chapter3.html#DECANM) +* [VT100 Index Sequence (IND)](https://vt100.net/docs/vt100-ug/chapter3.html#IND) +* [VTTEST Test Utility](https://invisible-island.net/vttest/) +* [DEC STD 070 Video Systems Reference Manual](https://archive.org/details/bitsavers_decstandar0VideoSystemsReferenceManualDec91_74264381) + + + diff --git a/doc/submitting_code.md b/doc/submitting_code.md index 24442bceb97..2e81141b027 100644 --- a/doc/submitting_code.md +++ b/doc/submitting_code.md @@ -3,7 +3,7 @@ In Openconsole, `dev/main` is the master branch for the repo. -Any branch that begins with `dev/` is recognized by our CI system and will automatically run x86 and amd64 builds and run our unit and feature tests. For feature branchs the pattern we use is `dev//`. ex. `dev/austdi/SomeCoolUnicodeFeature`. The important parts are the dev prefix and your alias. +Any branch that begins with `dev/` is recognized by our CI system and will automatically run x86 and amd64 builds and run our unit and feature tests. For feature branches the pattern we use is `dev//`. ex. `dev/austdi/SomeCoolUnicodeFeature`. The important parts are the dev prefix and your alias. `inbox` is a special branch that coordinates Openconsole code to the main OS repo. @@ -15,12 +15,12 @@ Because we build outside of the OS repo, we need a way to get code back into it ## What to do when cherry-picking to inbox fails -Sometimes VSTS doesn't want to allow a cherry pick to the inbox branch. It might have a valid reason or it might just be finicky. You'll need to complete the merge manually on a local machine. The steps are: +Sometimes VSTS doesn't want to allow a cherry pick to the inbox branch. It might have a valid reason, or it might just be finicky. You'll need to complete the merge manually on a local machine. The steps are: 1. make sure you have pulled the latest commits for the `dev/main` and `inbox` branches 2. make a new branch from inbox 3. cherry-pick the commits from the PR to the newly created branch (this is easier if you squashed your commits when you merged into `dev/main` -4. fix any merge conficts and commit +4. fix any merge conflicts and commit 5. push the new branch to the remote 6. create a new PR of that branch in `inbox` 7. complete PR and continue on to completing the auto-created PR in the OS repo diff --git a/doc/user-docs/UsingJsonSettings.md b/doc/user-docs/UsingJsonSettings.md index 5ad69ca13bc..537bdd82bd7 100644 --- a/doc/user-docs/UsingJsonSettings.md +++ b/doc/user-docs/UsingJsonSettings.md @@ -67,10 +67,12 @@ Example settings include "fontSize" : 9, "guid" : "{58ad8b0c-3ef8-5f4d-bc6f-13e4c00f2530}", "name" : "Debian", - "startingDirectory" : "%USERPROFILE%/wslhome" + "startingDirectory" : "%USERPROFILE%\\wslhome" .... ``` +> πŸ‘‰ **Note**: To use backslashes in any path field, you'll need to escape them following JSON escaping rules (like shown above). As an alternative, you can use forward slashes ("%USERPROFILE%/wslhome"). + The profile GUID is used to reference the default profile in the global settings. The values for background image stretch mode are documented [here](https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.media.stretch) @@ -99,7 +101,7 @@ The schema name can then be referenced in one or more profiles. ### Add a custom background to the WSL Debian terminal profile -1. Download the Debian SVG logo https://www.debian.org/logos/openlogo.svg +1. Download the Debian JPG logo https://www.debian.org/logos/openlogo-100.jpg 2. Put the image in the `$env:LocalAppData\Packages\Microsoft.WindowsTerminal_\RoamingState\` directory (same directory as your `profiles.json` file). @@ -108,9 +110,10 @@ The schema name can then be referenced in one or more profiles. 3. Open your WT json properties file. 4. Under the Debian Linux profile, add the following fields: ```json - "backgroundImage": "ms-appdata:///Roaming/openlogo.jpg", - "backgroundImageOpacity": 0.3, - "backgroundImageStretchMode": "fill", + "backgroundImage": "ms-appdata:///Roaming/openlogo-100.jpg", + "backgroundImageOpacity": 1, + "backgroundImageStretchMode" : "none", + "backgroundImageAlignment" : "topRight", ``` 5. Make sure that `useAcrylic` is `false`. 6. Save the file. @@ -126,3 +129,47 @@ More information about UWP URI schemes [here](https://docs.microsoft.com/en-us/w 1. URL such as `http://open.esa.int/files/2017/03/Mayer_and_Bond_craters_seen_by_SMART-1-350x346.jpg` 2. Local file location such as `C:\Users\Public\Pictures\openlogo.jpg` + +### Adding Copy and Paste Keybindings + +As of [#1093](https://github.com/microsoft/terminal/pull/1093) (first available in Windows Terminal v0.3), the Windows Terminal now +supports copy and paste keyboard shortcuts. However, if you installed and ran +the terminal before that, you won't automatically get the new keybindings added +to your settings. If you'd like to add shortcuts for copy and paste, you can do so by inserting the following objects into your `globals.keybindings` array: + +```json +{ "command": "copy", "keys": ["ctrl+shift+c"] }, +{ "command": "paste", "keys": ["ctrl+shift+v"] } +``` + +This will add copy and paste on ctrl+shift+c +and ctrl+shift+v respectively. + +You can set the keybindings to whatever you'd like. If you prefer +ctrl+c to copy, then set the `keys` to `"ctrl+c"`. + +You can even set multiple keybindings for a single action if you'd like. For example: + +```json + + { + "command" : "paste", + "keys" : + [ + "ctrl+shift+v" + ] + }, + { + "command" : "paste", + "keys" : + [ + "shift+insert" + ] + } +``` + +will bind both ctrl+shift+v and +shift+Insert to `paste`. + +Note: If you set your copy keybinding to `"ctrl+c"`, you'll only be able to send an interrupt to the commandline application using Ctrl+C when there's no text selection. +Additionally, if you set `paste` to `"ctrl+v"`, commandline applications won't be able to read a ctrl+v from the input. For these reasons, we suggest `"ctrl+shift+c"` and `"ctrl+shift+v"` diff --git a/doc/user-docs/index.md b/doc/user-docs/index.md index 6ca50d1804a..ae9a4d09f19 100644 --- a/doc/user-docs/index.md +++ b/doc/user-docs/index.md @@ -36,16 +36,18 @@ default shell is displayed (default shortcut `Ctrl+Shift+1`). ## Running a Different Shell -Note: The following text assumes you have WSL installed. +Note: This section assumes you already have _Windows Subsystem for Linux_ (WSL) installed. For more information, see [the installation guide](https://docs.microsoft.com/en-us/windows/wsl/install-win10). -To choose a different shell (e.g. `cmd.exe` or WSL `bash`) then +Windows Terminal uses PowerShell as its default shell. You can also use Windows Terminal to launch other shells, such as `cmd.exe` or WSL's `bash`: -1. Select the `down` button next to the `+` in the tab bar -2. Choose your new shell from the list (more on how to extend the list in the config section) +1. In the tab bar, click the `⌡` button to view the available shells. +2. Choose your shell from the dropdown list. The new shell session will open in a new tab. + +To customize the shell list, see the _Configuring Windows Terminal_ section below. ## Starting a new PowerShell tab with admin privilege -There is no current plan to support this feature for security reaons. See issue [#623](https://github.com/microsoft/terminal/issues/632) +There is no current plan to support this feature for security reasons. See issue [#623](https://github.com/microsoft/terminal/issues/632) ## Using cut and paste in the Terminal window @@ -65,16 +67,15 @@ Not currently supported "out of the box". See issue [#1060](https://github.com/m ## Configuring Windows Terminal -At the time of writing all Windows Terminal settings are managed via a json file. - -From the `down` button in the top bar select Settings (default shortcut `Ctrl+,`). +All Windows Terminal settings are currently managed using the `profiles.json` file, located within `$env:LocalAppData\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe/RoamingState`. -Your default json editor will open up the Terminal settings file. The file can be found -at `$env:LocalAppData\Packages\Microsoft.WindowsTerminal_/RoamingState` +To open the settings file from Windows Terminal: -An introduction to the the various settings can be found [here](UsingJsonSettings.md). +1. Click the `⌡` button in the top bar. +2. From the dropdown list, click `Settings`. You can also use a shortcut: `Ctrl+,`. +3. Your default `json` editor will open the settings file. -The list of valid settings can be found in the [Profiles.json Documentation](../cascadia/SettingsSchema.md) doc. +For an introduction to the various settings, see [Using Json Settings](UsingJsonSettings.md). The list of valid settings can be found in the [profiles.json documentation](../cascadia/SettingsSchema.md) section. ## Tips and Tricks: diff --git a/samples/ConPTY/MiniTerm/MiniTerm/Processes/ProcessFactory.cs b/samples/ConPTY/MiniTerm/MiniTerm/Processes/ProcessFactory.cs index f54805ba349..57159ce4a55 100644 --- a/samples/ConPTY/MiniTerm/MiniTerm/Processes/ProcessFactory.cs +++ b/samples/ConPTY/MiniTerm/MiniTerm/Processes/ProcessFactory.cs @@ -18,7 +18,7 @@ static class ProcessFactory internal static Process Start(string command, IntPtr attributes, IntPtr hPC) { var startupInfo = ConfigureProcessThread(hPC, attributes); - var processInfo = RunProcess(ref startupInfo, "cmd.exe"); + var processInfo = RunProcess(ref startupInfo, command); return new Process(startupInfo, processInfo); } diff --git a/src/StaticAnalysis.ruleset b/src/StaticAnalysis.ruleset index e9d2a69ed8d..114c0cc5c2f 100644 --- a/src/StaticAnalysis.ruleset +++ b/src/StaticAnalysis.ruleset @@ -1,11 +1,15 @@ ο»Ώ - + - - - + + + + + + + diff --git a/src/buffer/out/AttrRow.cpp b/src/buffer/out/AttrRow.cpp index e9efd8f6037..c97bcbba884 100644 --- a/src/buffer/out/AttrRow.cpp +++ b/src/buffer/out/AttrRow.cpp @@ -46,7 +46,7 @@ void ATTR_ROW::Resize(const size_t newWidth) { // Get the attribute that covers the final column of old width. const auto runPos = FindAttrIndex(_cchRowWidth - 1, nullptr); - auto& run = _list[runPos]; + auto& run = _list.at(runPos); // Extend its length by the additional columns we're adding. run.SetLength(run.GetLength() + newWidth - _cchRowWidth); @@ -60,7 +60,7 @@ void ATTR_ROW::Resize(const size_t newWidth) // Get the attribute that covers the final column of the new width size_t CountOfAttr = 0; const auto runPos = FindAttrIndex(newWidth - 1, &CountOfAttr); - auto& run = _list[runPos]; + auto& run = _list.at(runPos); // CountOfAttr was given to us as "how many columns left from this point forward are covered by the returned run" // So if the original run was B5 covering a 5 size OldWidth and we have a NewWidth of 3 @@ -108,7 +108,7 @@ TextAttribute ATTR_ROW::GetAttrByColumn(const size_t column, { THROW_HR_IF(E_INVALIDARG, column >= _cchRowWidth); const auto runPos = FindAttrIndex(column, pApplies); - return _list[runPos].GetAttributes(); + return _list.at(runPos).GetAttributes(); } // Routine Description: @@ -290,10 +290,10 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt // two elements in our internal list. else if (_list.size() == 2 && newAttrs.at(0).GetLength() == 1) { - auto left = _list.begin(); + const auto left = _list.begin(); if (iStart == left->GetLength() && NewAttr == left->GetAttributes()) { - auto right = left + 1; + const auto right = left + 1; left->IncrementLength(); right->DecrementLength(); diff --git a/src/buffer/out/AttrRowIterator.cpp b/src/buffer/out/AttrRowIterator.cpp index 0462afa144c..c4e735d3e11 100644 --- a/src/buffer/out/AttrRowIterator.cpp +++ b/src/buffer/out/AttrRowIterator.cpp @@ -6,21 +6,21 @@ #include "AttrRowIterator.hpp" #include "AttrRow.hpp" -AttrRowIterator AttrRowIterator::CreateEndIterator(const ATTR_ROW* const attrRow) +AttrRowIterator AttrRowIterator::CreateEndIterator(const ATTR_ROW* const attrRow) noexcept { AttrRowIterator it{ attrRow }; it._setToEnd(); return it; } -AttrRowIterator::AttrRowIterator(const ATTR_ROW* const attrRow) : +AttrRowIterator::AttrRowIterator(const ATTR_ROW* const attrRow) noexcept : _pAttrRow{ attrRow }, _run{ attrRow->_list.cbegin() }, _currentAttributeIndex{ 0 } { } -AttrRowIterator::operator bool() const noexcept +AttrRowIterator::operator bool() const { return _run < _pAttrRow->_list.cend(); } @@ -139,7 +139,7 @@ void AttrRowIterator::_decrement(size_t count) // Routine Description: // - sets fields on the iterator to describe the end() state of the ATTR_ROW -void AttrRowIterator::_setToEnd() +void AttrRowIterator::_setToEnd() noexcept { _run = _pAttrRow->_list.cend(); _currentAttributeIndex = 0; diff --git a/src/buffer/out/AttrRowIterator.hpp b/src/buffer/out/AttrRowIterator.hpp index 5ca7f72c125..c9e053ea247 100644 --- a/src/buffer/out/AttrRowIterator.hpp +++ b/src/buffer/out/AttrRowIterator.hpp @@ -29,11 +29,11 @@ class AttrRowIterator final using pointer = TextAttribute*; using reference = TextAttribute&; - static AttrRowIterator CreateEndIterator(const ATTR_ROW* const attrRow); + static AttrRowIterator CreateEndIterator(const ATTR_ROW* const attrRow) noexcept; - AttrRowIterator(const ATTR_ROW* const attrRow); + AttrRowIterator(const ATTR_ROW* const attrRow) noexcept; - operator bool() const noexcept; + operator bool() const; bool operator==(const AttrRowIterator& it) const; bool operator!=(const AttrRowIterator& it) const; @@ -57,5 +57,5 @@ class AttrRowIterator final void _increment(size_t count); void _decrement(size_t count); - void _setToEnd(); + void _setToEnd() noexcept; }; diff --git a/src/buffer/out/CharRow.cpp b/src/buffer/out/CharRow.cpp index 60a7d0d144c..8ac4ea217da 100644 --- a/src/buffer/out/CharRow.cpp +++ b/src/buffer/out/CharRow.cpp @@ -84,7 +84,7 @@ size_t CharRow::size() const noexcept // - sRowWidth - The width of the row. // Return Value: // - -void CharRow::Reset() +void CharRow::Reset() noexcept { for (auto& cell : _data) { @@ -209,7 +209,7 @@ const DbcsAttribute& CharRow::DbcsAttrAt(const size_t column) const // Note: will throw exception if column is out of bounds DbcsAttribute& CharRow::DbcsAttrAt(const size_t column) { - return const_cast(static_cast(this)->DbcsAttrAt(column)); + return _data.at(column).DbcsAttr(); } // Routine Description: @@ -250,29 +250,6 @@ CharRow::reference CharRow::GlyphAt(const size_t column) return { *this, column }; } -// Routine Description: -// - returns string containing text data exactly how it's stored internally, including doubling of -// leading/trailing cells. -// Arguments: -// - none -// Return Value: -// - text stored in char row -// - Note: will throw exception if out of memory -std::wstring CharRow::GetTextRaw() const -{ - std::wstring wstr; - wstr.reserve(_data.size()); - for (size_t i = 0; i < _data.size(); ++i) - { - auto glyph = GlyphAt(i); - for (auto it = glyph.begin(); it != glyph.end(); ++it) - { - wstr.push_back(*it); - } - } - return wstr; -} - std::wstring CharRow::GetText() const { std::wstring wstr; @@ -280,24 +257,24 @@ std::wstring CharRow::GetText() const for (size_t i = 0; i < _data.size(); ++i) { - auto glyph = GlyphAt(i); + const auto glyph = GlyphAt(i); if (!DbcsAttrAt(i).IsTrailing()) { - for (auto it = glyph.begin(); it != glyph.end(); ++it) + for (const auto wch : glyph) { - wstr.push_back(*it); + wstr.push_back(wch); } } } return wstr; } -UnicodeStorage& CharRow::GetUnicodeStorage() +UnicodeStorage& CharRow::GetUnicodeStorage() noexcept { return _pParent->GetUnicodeStorage(); } -const UnicodeStorage& CharRow::GetUnicodeStorage() const +const UnicodeStorage& CharRow::GetUnicodeStorage() const noexcept { return _pParent->GetUnicodeStorage(); } @@ -308,7 +285,7 @@ const UnicodeStorage& CharRow::GetUnicodeStorage() const // - column - the column to generate the key for // Return Value: // - the COORD key for data access from UnicodeStorage for the column -COORD CharRow::GetStorageKey(const size_t column) const +COORD CharRow::GetStorageKey(const size_t column) const noexcept { return { gsl::narrow(column), _pParent->GetId() }; } diff --git a/src/buffer/out/CharRow.hpp b/src/buffer/out/CharRow.hpp index c9ff3a0ae17..e1b802d065f 100644 --- a/src/buffer/out/CharRow.hpp +++ b/src/buffer/out/CharRow.hpp @@ -53,7 +53,7 @@ class CharRow final void SetDoubleBytePadded(const bool doubleBytePadded) noexcept; bool WasDoubleBytePadded() const noexcept; size_t size() const noexcept; - void Reset(); + void Reset() noexcept; [[nodiscard]] HRESULT Resize(const size_t newSize) noexcept; size_t MeasureLeft() const; size_t MeasureRight() const noexcept; @@ -64,9 +64,6 @@ class CharRow final void ClearGlyph(const size_t column); std::wstring GetText() const; - // other functions implemented at the template class level - std::wstring GetTextRaw() const; - // working with glyphs const reference GlyphAt(const size_t column) const; reference GlyphAt(const size_t column); @@ -78,9 +75,9 @@ class CharRow final iterator end() noexcept; const_iterator cend() const noexcept; - UnicodeStorage& GetUnicodeStorage(); - const UnicodeStorage& GetUnicodeStorage() const; - COORD GetStorageKey(const size_t column) const; + UnicodeStorage& GetUnicodeStorage() noexcept; + const UnicodeStorage& GetUnicodeStorage() const noexcept; + COORD GetStorageKey(const size_t column) const noexcept; void UpdateParent(ROW* const pParent) noexcept; diff --git a/src/buffer/out/CharRowCell.cpp b/src/buffer/out/CharRowCell.cpp index a6c2d2fb66e..d8cdeff429c 100644 --- a/src/buffer/out/CharRowCell.cpp +++ b/src/buffer/out/CharRowCell.cpp @@ -8,13 +8,13 @@ // default glyph value, used for reseting the character data portion of a cell static constexpr wchar_t DefaultValue = UNICODE_SPACE; -CharRowCell::CharRowCell() : +CharRowCell::CharRowCell() noexcept : _wch{ DefaultValue }, _attr{} { } -CharRowCell::CharRowCell(const wchar_t wch, const DbcsAttribute attr) : +CharRowCell::CharRowCell(const wchar_t wch, const DbcsAttribute attr) noexcept : _wch{ wch }, _attr{ attr } { @@ -22,7 +22,7 @@ CharRowCell::CharRowCell(const wchar_t wch, const DbcsAttribute attr) : // Routine Description: // - "erases" the glyph. really sets it back to the default "empty" value -void CharRowCell::EraseChars() +void CharRowCell::EraseChars() noexcept { if (_attr.IsGlyphStored()) { diff --git a/src/buffer/out/CharRowCell.hpp b/src/buffer/out/CharRowCell.hpp index fd8785bafd7..9ce8ee16b45 100644 --- a/src/buffer/out/CharRowCell.hpp +++ b/src/buffer/out/CharRowCell.hpp @@ -27,10 +27,10 @@ Author(s): class CharRowCell final { public: - CharRowCell(); - CharRowCell(const wchar_t wch, const DbcsAttribute attr); + CharRowCell() noexcept; + CharRowCell(const wchar_t wch, const DbcsAttribute attr) noexcept; - void EraseChars(); + void EraseChars() noexcept; void Reset() noexcept; bool IsSpace() const noexcept; diff --git a/src/buffer/out/CharRowCellReference.cpp b/src/buffer/out/CharRowCellReference.cpp index 1f320756bd0..0f8a2e1a574 100644 --- a/src/buffer/out/CharRowCellReference.cpp +++ b/src/buffer/out/CharRowCellReference.cpp @@ -91,6 +91,9 @@ CharRowCellReference::const_iterator CharRowCellReference::begin() const // - get read-only iterator to the end of the glyph data // Return Value: // - end iterator of the glyph data +#pragma warning(push) +#pragma warning(disable : 26481) +// TODO GH 2672: eliminate using pointers raw as begin/end markers in this class CharRowCellReference::const_iterator CharRowCellReference::end() const { if (_cellData().DbcsAttr().IsGlyphStored()) @@ -103,6 +106,7 @@ CharRowCellReference::const_iterator CharRowCellReference::end() const return &_cellData().Char() + 1; } } +#pragma warning(pop) bool operator==(const CharRowCellReference& ref, const std::vector& glyph) { diff --git a/src/buffer/out/CharRowCellReference.hpp b/src/buffer/out/CharRowCellReference.hpp index 64a42a17710..24e2ae45fe9 100644 --- a/src/buffer/out/CharRowCellReference.hpp +++ b/src/buffer/out/CharRowCellReference.hpp @@ -25,7 +25,7 @@ class CharRowCellReference final public: using const_iterator = const wchar_t*; - CharRowCellReference(CharRow& parent, const size_t index) : + CharRowCellReference(CharRow& parent, const size_t index) noexcept : _parent{ parent }, _index{ index } { diff --git a/src/buffer/out/DbcsAttribute.hpp b/src/buffer/out/DbcsAttribute.hpp index f92a707b167..442505052c3 100644 --- a/src/buffer/out/DbcsAttribute.hpp +++ b/src/buffer/out/DbcsAttribute.hpp @@ -63,7 +63,7 @@ class DbcsAttribute final return _glyphStored; } - void SetGlyphStored(const bool stored) + void SetGlyphStored(const bool stored) noexcept { _glyphStored = stored; } diff --git a/src/buffer/out/OutputCell.cpp b/src/buffer/out/OutputCell.cpp index 59dc1c1f8b2..ad30b996bdb 100644 --- a/src/buffer/out/OutputCell.cpp +++ b/src/buffer/out/OutputCell.cpp @@ -11,7 +11,7 @@ static constexpr TextAttribute InvalidTextAttribute{ INVALID_COLOR, INVALID_COLOR }; -OutputCell::OutputCell() : +OutputCell::OutputCell() noexcept : _text{}, _dbcsAttribute{}, _textAttribute{ InvalidTextAttribute }, @@ -111,7 +111,5 @@ void OutputCell::_setFromOutputCellView(const OutputCellView& cell) _dbcsAttribute = cell.DbcsAttr(); _textAttribute = cell.TextAttr(); _behavior = cell.TextAttrBehavior(); - - const auto& view = cell.Chars(); - _text = view; + _text = cell.Chars(); } diff --git a/src/buffer/out/OutputCell.hpp b/src/buffer/out/OutputCell.hpp index 8599ddf7cb3..df64e0b461e 100644 --- a/src/buffer/out/OutputCell.hpp +++ b/src/buffer/out/OutputCell.hpp @@ -25,7 +25,7 @@ Module Name: class InvalidCharInfoConversionException : public std::exception { - const char* what() const noexcept + const char* what() const noexcept override { return "Cannot convert to CHAR_INFO without explicit TextAttribute"; } @@ -34,7 +34,7 @@ class InvalidCharInfoConversionException : public std::exception class OutputCell final { public: - OutputCell(); + OutputCell() noexcept; OutputCell(const std::wstring_view charData, const DbcsAttribute dbcsAttribute, diff --git a/src/buffer/out/OutputCellIterator.cpp b/src/buffer/out/OutputCellIterator.cpp index 2068e73af58..dbd9fb8bb7a 100644 --- a/src/buffer/out/OutputCellIterator.cpp +++ b/src/buffer/out/OutputCellIterator.cpp @@ -17,7 +17,7 @@ static constexpr TextAttribute InvalidTextAttribute{ INVALID_COLOR, INVALID_COLO // Arguments: // - wch - The character to use for filling // - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0. -OutputCellIterator::OutputCellIterator(const wchar_t& wch, const size_t fillLimit) : +OutputCellIterator::OutputCellIterator(const wchar_t& wch, const size_t fillLimit) noexcept : _mode(Mode::Fill), _currentView(s_GenerateView(wch)), _run(), @@ -33,7 +33,7 @@ OutputCellIterator::OutputCellIterator(const wchar_t& wch, const size_t fillLimi // Arguments: // - attr - The color attribute to use for filling // - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0. -OutputCellIterator::OutputCellIterator(const TextAttribute& attr, const size_t fillLimit) : +OutputCellIterator::OutputCellIterator(const TextAttribute& attr, const size_t fillLimit) noexcept : _mode(Mode::Fill), _currentView(s_GenerateView(attr)), _run(), @@ -50,7 +50,7 @@ OutputCellIterator::OutputCellIterator(const TextAttribute& attr, const size_t f // - wch - The character to use for filling // - attr - The color attribute to use for filling // - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0. -OutputCellIterator::OutputCellIterator(const wchar_t& wch, const TextAttribute& attr, const size_t fillLimit) : +OutputCellIterator::OutputCellIterator(const wchar_t& wch, const TextAttribute& attr, const size_t fillLimit) noexcept : _mode(Mode::Fill), _currentView(s_GenerateView(wch, attr)), _run(), @@ -66,7 +66,7 @@ OutputCellIterator::OutputCellIterator(const wchar_t& wch, const TextAttribute& // Arguments: // - charInfo - The legacy character and color data to use for fililng (uses Unicode portion of text data) // - fillLimit - How many times to allow this value to be viewed/filled. Infinite if 0. -OutputCellIterator::OutputCellIterator(const CHAR_INFO& charInfo, const size_t fillLimit) : +OutputCellIterator::OutputCellIterator(const CHAR_INFO& charInfo, const size_t fillLimit) noexcept : _mode(Mode::Fill), _currentView(s_GenerateView(charInfo)), _run(), @@ -116,7 +116,12 @@ OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text, const // razzle cannot distinguish between a std::wstring_view and a std::basic_string_view // NOTE: This one internally casts to wchar_t because Razzle sees WORD and wchar_t as the same type // despite that Visual Studio build can tell the difference. -OutputCellIterator::OutputCellIterator(const std::basic_string_view legacyAttrs, const bool /*unused*/) : +#pragma warning(push) +#pragma warning(suppress : 26490) +// Suppresses reinterpret_cast. We're only doing this because Windows doesn't understand the type difference between wchar_t and DWORD. +// It is not worth trying to separate that out further or risking performance over this particular warning here. +// TODO GH 2673 - Investigate real wchar_t flag in Windows and resolve this audit issue +OutputCellIterator::OutputCellIterator(const std::basic_string_view legacyAttrs, const bool /*unused*/) noexcept : _mode(Mode::LegacyAttr), _currentView(s_GenerateViewLegacyAttr(legacyAttrs.at(0))), _run(std::wstring_view(reinterpret_cast(legacyAttrs.data()), legacyAttrs.size())), @@ -126,12 +131,13 @@ OutputCellIterator::OutputCellIterator(const std::basic_string_view legacy _fillLimit(0) { } +#pragma warning(pop) // Routine Description: // - This is an iterator over legacy cell data. We will use the unicode text and the legacy color attribute. // Arguments: // - charInfos - Multiple cell with unicode text and legacy color data. -OutputCellIterator::OutputCellIterator(const std::basic_string_view charInfos) : +OutputCellIterator::OutputCellIterator(const std::basic_string_view charInfos) noexcept : _mode(Mode::CharInfo), _currentView(s_GenerateView(charInfos.at(0))), _run(charInfos), @@ -315,7 +321,7 @@ OutputCellIterator OutputCellIterator::operator++(int) // - Reference the view to fully-formed output cell data representing the underlying data source. // Return Value: // - Reference to the view -const OutputCellView& OutputCellIterator::operator*() const +const OutputCellView& OutputCellIterator::operator*() const noexcept { return _currentView; } @@ -324,7 +330,7 @@ const OutputCellView& OutputCellIterator::operator*() const // - Get pointer to the view to fully-formed output cell data representing the underlying data source. // Return Value: // - Pointer to the view -const OutputCellView* OutputCellIterator::operator->() const +const OutputCellView* OutputCellIterator::operator->() const noexcept { return &_currentView; } @@ -338,7 +344,7 @@ const OutputCellView* OutputCellIterator::operator->() const // - True if we just turned a lead half into a trailing half (and caller doesn't // need to further update the view). // - False if this wasn't applicable and the caller should update the view. -bool OutputCellIterator::_TryMoveTrailing() +bool OutputCellIterator::_TryMoveTrailing() noexcept { if (_currentView.DbcsAttr().IsLeading()) { @@ -421,7 +427,7 @@ OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view, // - wch - View representing a single UTF-16 character (that can be represented without surrogates) // Return Value: // - Object representing the view into this cell -OutputCellView OutputCellIterator::s_GenerateView(const wchar_t& wch) +OutputCellView OutputCellIterator::s_GenerateView(const wchar_t& wch) noexcept { const auto glyph = std::wstring_view(&wch, 1); @@ -443,7 +449,7 @@ OutputCellView OutputCellIterator::s_GenerateView(const wchar_t& wch) // - attr - View representing a single color // Return Value: // - Object representing the view into this cell -OutputCellView OutputCellIterator::s_GenerateView(const TextAttribute& attr) +OutputCellView OutputCellIterator::s_GenerateView(const TextAttribute& attr) noexcept { return OutputCellView({}, {}, attr, TextAttributeBehavior::StoredOnly); } @@ -458,7 +464,7 @@ OutputCellView OutputCellIterator::s_GenerateView(const TextAttribute& attr) // - attr - View representing a single color // Return Value: // - Object representing the view into this cell -OutputCellView OutputCellIterator::s_GenerateView(const wchar_t& wch, const TextAttribute& attr) +OutputCellView OutputCellIterator::s_GenerateView(const wchar_t& wch, const TextAttribute& attr) noexcept { const auto glyph = std::wstring_view(&wch, 1); @@ -480,12 +486,12 @@ OutputCellView OutputCellIterator::s_GenerateView(const wchar_t& wch, const Text // - legacyAttr - View representing a single legacy color // Return Value: // - Object representing the view into this cell -OutputCellView OutputCellIterator::s_GenerateViewLegacyAttr(const WORD& legacyAttr) +OutputCellView OutputCellIterator::s_GenerateViewLegacyAttr(const WORD& legacyAttr) noexcept { WORD cleanAttr = legacyAttr; WI_ClearAllFlags(cleanAttr, COMMON_LVB_SBCSDBCS); // don't use legacy lead/trailing byte flags for colors - TextAttribute attr(cleanAttr); + const TextAttribute attr(cleanAttr); return s_GenerateView(attr); } @@ -498,7 +504,7 @@ OutputCellView OutputCellIterator::s_GenerateViewLegacyAttr(const WORD& legacyAt // - charInfo - character and attribute pair representing a single cell // Return Value: // - Object representing the view into this cell -OutputCellView OutputCellIterator::s_GenerateView(const CHAR_INFO& charInfo) +OutputCellView OutputCellIterator::s_GenerateView(const CHAR_INFO& charInfo) noexcept { const auto glyph = std::wstring_view(&charInfo.Char.UnicodeChar, 1); diff --git a/src/buffer/out/OutputCellIterator.hpp b/src/buffer/out/OutputCellIterator.hpp index b5b29050760..02f6aefa54e 100644 --- a/src/buffer/out/OutputCellIterator.hpp +++ b/src/buffer/out/OutputCellIterator.hpp @@ -33,14 +33,14 @@ class OutputCellIterator final using pointer = OutputCellView*; using reference = OutputCellView&; - OutputCellIterator(const wchar_t& wch, const size_t fillLimit = 0); - OutputCellIterator(const TextAttribute& attr, const size_t fillLimit = 0); - OutputCellIterator(const wchar_t& wch, const TextAttribute& attr, const size_t fillLimit = 0); - OutputCellIterator(const CHAR_INFO& charInfo, const size_t fillLimit = 0); + OutputCellIterator(const wchar_t& wch, const size_t fillLimit = 0) noexcept; + OutputCellIterator(const TextAttribute& attr, const size_t fillLimit = 0) noexcept; + OutputCellIterator(const wchar_t& wch, const TextAttribute& attr, const size_t fillLimit = 0) noexcept; + OutputCellIterator(const CHAR_INFO& charInfo, const size_t fillLimit = 0) noexcept; OutputCellIterator(const std::wstring_view utf16Text); OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute attribute); - OutputCellIterator(const std::basic_string_view legacyAttributes, const bool unused); - OutputCellIterator(const std::basic_string_view charInfos); + OutputCellIterator(const std::basic_string_view legacyAttributes, const bool unused) noexcept; + OutputCellIterator(const std::basic_string_view charInfos) noexcept; OutputCellIterator(const std::basic_string_view cells); ~OutputCellIterator() = default; @@ -55,8 +55,8 @@ class OutputCellIterator final OutputCellIterator& operator++(); OutputCellIterator operator++(int); - const OutputCellView& operator*() const; - const OutputCellView* operator->() const; + const OutputCellView& operator*() const noexcept; + const OutputCellView* operator->() const noexcept; private: enum class Mode @@ -97,7 +97,7 @@ class OutputCellIterator final TextAttribute _attr; - bool _TryMoveTrailing(); + bool _TryMoveTrailing() noexcept; static OutputCellView s_GenerateView(const std::wstring_view view); @@ -108,11 +108,11 @@ class OutputCellIterator final const TextAttribute attr, const TextAttributeBehavior behavior); - static OutputCellView s_GenerateView(const wchar_t& wch); - static OutputCellView s_GenerateViewLegacyAttr(const WORD& legacyAttr); - static OutputCellView s_GenerateView(const TextAttribute& attr); - static OutputCellView s_GenerateView(const wchar_t& wch, const TextAttribute& attr); - static OutputCellView s_GenerateView(const CHAR_INFO& charInfo); + static OutputCellView s_GenerateView(const wchar_t& wch) noexcept; + static OutputCellView s_GenerateViewLegacyAttr(const WORD& legacyAttr) noexcept; + static OutputCellView s_GenerateView(const TextAttribute& attr) noexcept; + static OutputCellView s_GenerateView(const wchar_t& wch, const TextAttribute& attr) noexcept; + static OutputCellView s_GenerateView(const CHAR_INFO& charInfo) noexcept; static OutputCellView s_GenerateView(const OutputCell& cell); diff --git a/src/buffer/out/OutputCellRect.cpp b/src/buffer/out/OutputCellRect.cpp index 37711c5343b..90385094465 100644 --- a/src/buffer/out/OutputCellRect.cpp +++ b/src/buffer/out/OutputCellRect.cpp @@ -7,7 +7,7 @@ // Routine Description: // - Constucts an empty in-memory region for holding output buffer cell data. -OutputCellRect::OutputCellRect() : +OutputCellRect::OutputCellRect() noexcept : _rows(0), _cols(0) { @@ -64,7 +64,7 @@ OutputCellIterator OutputCellRect::GetRowIter(const size_t row) const // - Pointer to the location in the rectangle that represents the start of the requested row. OutputCell* OutputCellRect::_FindRowOffset(const size_t row) { - return (_storage.data() + (row * _cols)); + return &_storage.at(row * _cols); } // Routine Description: @@ -76,7 +76,7 @@ OutputCell* OutputCellRect::_FindRowOffset(const size_t row) // - Pointer to the location in the rectangle that represents the start of the requested row. const OutputCell* OutputCellRect::_FindRowOffset(const size_t row) const { - return (_storage.data() + (row * _cols)); + return &_storage.at(row * _cols); } // Routine Description: diff --git a/src/buffer/out/OutputCellRect.hpp b/src/buffer/out/OutputCellRect.hpp index ae73232505d..ebdb4883e35 100644 --- a/src/buffer/out/OutputCellRect.hpp +++ b/src/buffer/out/OutputCellRect.hpp @@ -29,7 +29,7 @@ Revision History: class OutputCellRect final { public: - OutputCellRect(); + OutputCellRect() noexcept; OutputCellRect(const size_t rows, const size_t cols); gsl::span GetRow(const size_t row); diff --git a/src/buffer/out/OutputCellView.cpp b/src/buffer/out/OutputCellView.cpp index 434333c030f..cd1f16a7646 100644 --- a/src/buffer/out/OutputCellView.cpp +++ b/src/buffer/out/OutputCellView.cpp @@ -15,7 +15,7 @@ OutputCellView::OutputCellView(const std::wstring_view view, const DbcsAttribute dbcsAttr, const TextAttribute textAttr, - const TextAttributeBehavior behavior) : + const TextAttributeBehavior behavior) noexcept : _view(view), _dbcsAttr(dbcsAttr), _textAttr(textAttr), @@ -27,7 +27,9 @@ OutputCellView::OutputCellView(const std::wstring_view view, // - Returns reference to view over text data // Return Value: // - Reference to UTF-16 character data -const std::wstring_view& OutputCellView::Chars() const noexcept +// C26445 - suppressed to enable the `TextBufferTextIterator::operator->` method which needs a non-temporary memory location holding the wstring_view. +// TODO: GH 2681 - remove this suppression by reconciling the probably bad design of the iterators that leads to this being required. +[[gsl::suppress(26445)]] const std::wstring_view& OutputCellView::Chars() const noexcept { return _view; } diff --git a/src/buffer/out/OutputCellView.hpp b/src/buffer/out/OutputCellView.hpp index 2c425d8088a..06c9b86af21 100644 --- a/src/buffer/out/OutputCellView.hpp +++ b/src/buffer/out/OutputCellView.hpp @@ -28,7 +28,7 @@ class OutputCellView OutputCellView(const std::wstring_view view, const DbcsAttribute dbcsAttr, const TextAttribute textAttr, - const TextAttributeBehavior behavior); + const TextAttributeBehavior behavior) noexcept; const std::wstring_view& Chars() const noexcept; size_t Columns() const noexcept; diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 44ffd941f04..4172374af6d 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -30,14 +30,14 @@ size_t ROW::size() const noexcept return _rowWidth; } -const CharRow& ROW::GetCharRow() const +const CharRow& ROW::GetCharRow() const noexcept { return _charRow; } -CharRow& ROW::GetCharRow() +CharRow& ROW::GetCharRow() noexcept { - return const_cast(static_cast(this)->GetCharRow()); + return _charRow; } const ATTR_ROW& ROW::GetAttrRow() const noexcept @@ -47,7 +47,7 @@ const ATTR_ROW& ROW::GetAttrRow() const noexcept ATTR_ROW& ROW::GetAttrRow() noexcept { - return const_cast(static_cast(this)->GetAttrRow()); + return _attrRow; } SHORT ROW::GetId() const noexcept @@ -132,12 +132,12 @@ RowCellIterator ROW::AsCellIter(const size_t startIndex, const size_t count) con return RowCellIterator(*this, startIndex, count); } -UnicodeStorage& ROW::GetUnicodeStorage() +UnicodeStorage& ROW::GetUnicodeStorage() noexcept { return _pParent->GetUnicodeStorage(); } -const UnicodeStorage& ROW::GetUnicodeStorage() const +const UnicodeStorage& ROW::GetUnicodeStorage() const noexcept { return _pParent->GetUnicodeStorage(); } diff --git a/src/buffer/out/Row.hpp b/src/buffer/out/Row.hpp index 5bf05c858f6..27d2a390928 100644 --- a/src/buffer/out/Row.hpp +++ b/src/buffer/out/Row.hpp @@ -36,8 +36,8 @@ class ROW final size_t size() const noexcept; - const CharRow& GetCharRow() const; - CharRow& GetCharRow(); + const CharRow& GetCharRow() const noexcept; + CharRow& GetCharRow() noexcept; const ATTR_ROW& GetAttrRow() const noexcept; ATTR_ROW& GetAttrRow() noexcept; @@ -54,8 +54,8 @@ class ROW final RowCellIterator AsCellIter(const size_t startIndex) const; RowCellIterator AsCellIter(const size_t startIndex, const size_t count) const; - UnicodeStorage& GetUnicodeStorage(); - const UnicodeStorage& GetUnicodeStorage() const; + UnicodeStorage& GetUnicodeStorage() noexcept; + const UnicodeStorage& GetUnicodeStorage() const noexcept; OutputCellIterator WriteCells(OutputCellIterator it, const size_t index, const bool setWrap, std::optional limitRight = std::nullopt); diff --git a/src/buffer/out/RowCellIterator.cpp b/src/buffer/out/RowCellIterator.cpp index 0381e4d933e..0dedc264a25 100644 --- a/src/buffer/out/RowCellIterator.cpp +++ b/src/buffer/out/RowCellIterator.cpp @@ -37,38 +37,38 @@ bool RowCellIterator::operator!=(const RowCellIterator& it) const noexcept return !(*this == it); } -RowCellIterator& RowCellIterator::operator+=(const ptrdiff_t& movement) +RowCellIterator& RowCellIterator::operator+=(const ptrdiff_t& movement) noexcept { _pos += movement; return (*this); } -RowCellIterator& RowCellIterator::operator++() +RowCellIterator& RowCellIterator::operator++() noexcept { return this->operator+=(1); } -RowCellIterator RowCellIterator::operator++(int) +RowCellIterator RowCellIterator::operator++(int) noexcept { auto temp(*this); operator++(); return temp; } -RowCellIterator RowCellIterator::operator+(const ptrdiff_t& movement) +RowCellIterator RowCellIterator::operator+(const ptrdiff_t& movement) noexcept { auto temp(*this); temp += movement; return temp; } -const OutputCellView& RowCellIterator::operator*() const +const OutputCellView& RowCellIterator::operator*() const noexcept { return _view; } -const OutputCellView* RowCellIterator::operator->() const +const OutputCellView* RowCellIterator::operator->() const noexcept { return &_view; } diff --git a/src/buffer/out/RowCellIterator.hpp b/src/buffer/out/RowCellIterator.hpp index c386bb6afa1..62d6ffc1c14 100644 --- a/src/buffer/out/RowCellIterator.hpp +++ b/src/buffer/out/RowCellIterator.hpp @@ -38,13 +38,13 @@ class RowCellIterator final bool operator==(const RowCellIterator& it) const noexcept; bool operator!=(const RowCellIterator& it) const noexcept; - RowCellIterator& operator+=(const ptrdiff_t& movement); - RowCellIterator& operator++(); - RowCellIterator operator++(int); - RowCellIterator operator+(const ptrdiff_t& movement); + RowCellIterator& operator+=(const ptrdiff_t& movement) noexcept; + RowCellIterator& operator++() noexcept; + RowCellIterator operator++(int) noexcept; + RowCellIterator operator+(const ptrdiff_t& movement) noexcept; - const OutputCellView& operator*() const; - const OutputCellView* operator->() const; + const OutputCellView& operator*() const noexcept; + const OutputCellView* operator->() const noexcept; private: const ROW& _row; diff --git a/src/buffer/out/TextAttribute.cpp b/src/buffer/out/TextAttribute.cpp index 84e100afa30..0c964b848e9 100644 --- a/src/buffer/out/TextAttribute.cpp +++ b/src/buffer/out/TextAttribute.cpp @@ -16,7 +16,7 @@ bool TextAttribute::IsLegacy() const noexcept // - color that should be displayed as the foreground color COLORREF TextAttribute::CalculateRgbForeground(std::basic_string_view colorTable, COLORREF defaultFgColor, - COLORREF defaultBgColor) const + COLORREF defaultBgColor) const noexcept { return _IsReverseVideo() ? _GetRgbBackground(colorTable, defaultBgColor) : _GetRgbForeground(colorTable, defaultFgColor); } @@ -29,7 +29,7 @@ COLORREF TextAttribute::CalculateRgbForeground(std::basic_string_view // - color that should be displayed as the background color COLORREF TextAttribute::CalculateRgbBackground(std::basic_string_view colorTable, COLORREF defaultFgColor, - COLORREF defaultBgColor) const + COLORREF defaultBgColor) const noexcept { return _IsReverseVideo() ? _GetRgbForeground(colorTable, defaultFgColor) : _GetRgbBackground(colorTable, defaultBgColor); } @@ -42,7 +42,7 @@ COLORREF TextAttribute::CalculateRgbBackground(std::basic_string_view // Return Value: // - color that is stored as the foreground color COLORREF TextAttribute::_GetRgbForeground(std::basic_string_view colorTable, - COLORREF defaultColor) const + COLORREF defaultColor) const noexcept { return _foreground.GetColor(colorTable, defaultColor, _isBold); } @@ -55,7 +55,7 @@ COLORREF TextAttribute::_GetRgbForeground(std::basic_string_view color // Return Value: // - color that is stored as the background color COLORREF TextAttribute::_GetRgbBackground(std::basic_string_view colorTable, - COLORREF defaultColor) const + COLORREF defaultColor) const noexcept { return _background.GetColor(colorTable, defaultColor, false); } @@ -75,22 +75,22 @@ WORD TextAttribute::GetMetaAttributes() const noexcept return wMeta; } -void TextAttribute::SetForeground(const COLORREF rgbForeground) +void TextAttribute::SetForeground(const COLORREF rgbForeground) noexcept { _foreground = TextColor(rgbForeground); } -void TextAttribute::SetBackground(const COLORREF rgbBackground) +void TextAttribute::SetBackground(const COLORREF rgbBackground) noexcept { _background = TextColor(rgbBackground); } void TextAttribute::SetFromLegacy(const WORD wLegacy) noexcept { - _wAttrLegacy = static_cast(wLegacy & META_ATTRS); + _wAttrLegacy = gsl::narrow_cast(wLegacy & META_ATTRS); WI_ClearAllFlags(_wAttrLegacy, COMMON_LVB_SBCSDBCS); - BYTE fgIndex = static_cast(wLegacy & FG_ATTRS); - BYTE bgIndex = static_cast(wLegacy & BG_ATTRS) >> 4; + const BYTE fgIndex = gsl::narrow_cast(wLegacy & FG_ATTRS); + const BYTE bgIndex = gsl::narrow_cast(wLegacy & BG_ATTRS) >> 4; _foreground = TextColor(fgIndex); _background = TextColor(bgIndex); } @@ -98,16 +98,16 @@ void TextAttribute::SetFromLegacy(const WORD wLegacy) noexcept void TextAttribute::SetLegacyAttributes(const WORD attrs, const bool setForeground, const bool setBackground, - const bool setMeta) + const bool setMeta) noexcept { if (setForeground) { - BYTE fgIndex = (BYTE)(attrs & FG_ATTRS); + const BYTE fgIndex = gsl::narrow_cast(attrs & FG_ATTRS); _foreground = TextColor(fgIndex); } if (setBackground) { - BYTE bgIndex = (BYTE)(attrs & BG_ATTRS) >> 4; + const BYTE bgIndex = gsl::narrow_cast(attrs & BG_ATTRS) >> 4; _background = TextColor(bgIndex); } if (setMeta) @@ -133,17 +133,17 @@ void TextAttribute::SetIndexedAttributes(const std::optional foregro { if (foreground) { - BYTE fgIndex = (*foreground) & 0xFF; + const BYTE fgIndex = (*foreground) & 0xFF; _foreground = TextColor(fgIndex); } if (background) { - BYTE bgIndex = (*background) & 0xFF; + const BYTE bgIndex = (*background) & 0xFF; _background = TextColor(bgIndex); } } -void TextAttribute::SetColor(const COLORREF rgbColor, const bool fIsForeground) +void TextAttribute::SetColor(const COLORREF rgbColor, const bool fIsForeground) noexcept { if (fIsForeground) { diff --git a/src/buffer/out/TextAttribute.hpp b/src/buffer/out/TextAttribute.hpp index 1a53bbb611e..eb8cc6111df 100644 --- a/src/buffer/out/TextAttribute.hpp +++ b/src/buffer/out/TextAttribute.hpp @@ -39,9 +39,9 @@ class TextAttribute final } constexpr TextAttribute(const WORD wLegacyAttr) noexcept : - _wAttrLegacy{ static_cast(wLegacyAttr & META_ATTRS) }, - _foreground{ static_cast(wLegacyAttr & FG_ATTRS) }, - _background{ static_cast((wLegacyAttr & BG_ATTRS) >> 4) }, + _wAttrLegacy{ gsl::narrow_cast(wLegacyAttr & META_ATTRS) }, + _foreground{ gsl::narrow_cast(wLegacyAttr & FG_ATTRS) }, + _background{ gsl::narrow_cast((wLegacyAttr & BG_ATTRS) >> 4) }, _isBold{ false } { // If we're given lead/trailing byte information with the legacy color, strip it. @@ -59,9 +59,9 @@ class TextAttribute final constexpr WORD GetLegacyAttributes() const noexcept { - BYTE fg = (_foreground.GetIndex() & FG_ATTRS); - BYTE bg = (_background.GetIndex() << 4) & BG_ATTRS; - WORD meta = (_wAttrLegacy & META_ATTRS); + const BYTE fg = (_foreground.GetIndex() & FG_ATTRS); + const BYTE bg = (_background.GetIndex() << 4) & BG_ATTRS; + const WORD meta = (_wAttrLegacy & META_ATTRS); return (fg | bg | meta) | (_isBold ? FOREGROUND_INTENSITY : 0); } @@ -80,20 +80,20 @@ class TextAttribute final constexpr WORD GetLegacyAttributes(const BYTE defaultFgIndex, const BYTE defaultBgIndex) const noexcept { - BYTE fgIndex = _foreground.IsLegacy() ? _foreground.GetIndex() : defaultFgIndex; - BYTE bgIndex = _background.IsLegacy() ? _background.GetIndex() : defaultBgIndex; - BYTE fg = (fgIndex & FG_ATTRS); - BYTE bg = (bgIndex << 4) & BG_ATTRS; - WORD meta = (_wAttrLegacy & META_ATTRS); + const BYTE fgIndex = _foreground.IsLegacy() ? _foreground.GetIndex() : defaultFgIndex; + const BYTE bgIndex = _background.IsLegacy() ? _background.GetIndex() : defaultBgIndex; + const BYTE fg = (fgIndex & FG_ATTRS); + const BYTE bg = (bgIndex << 4) & BG_ATTRS; + const WORD meta = (_wAttrLegacy & META_ATTRS); return (fg | bg | meta) | (_isBold ? FOREGROUND_INTENSITY : 0); } COLORREF CalculateRgbForeground(std::basic_string_view colorTable, COLORREF defaultFgColor, - COLORREF defaultBgColor) const; + COLORREF defaultBgColor) const noexcept; COLORREF CalculateRgbBackground(std::basic_string_view colorTable, COLORREF defaultFgColor, - COLORREF defaultBgColor) const; + COLORREF defaultBgColor) const noexcept; bool IsLeadingByte() const noexcept; bool IsTrailingByte() const noexcept; @@ -110,7 +110,7 @@ class TextAttribute final void SetLegacyAttributes(const WORD attrs, const bool setForeground, const bool setBackground, - const bool setMeta); + const bool setMeta) noexcept; void SetIndexedAttributes(const std::optional foreground, const std::optional background) noexcept; @@ -133,9 +133,9 @@ class TextAttribute final bool IsLegacy() const noexcept; bool IsBold() const noexcept; - void SetForeground(const COLORREF rgbForeground); - void SetBackground(const COLORREF rgbBackground); - void SetColor(const COLORREF rgbColor, const bool fIsForeground); + void SetForeground(const COLORREF rgbForeground) noexcept; + void SetBackground(const COLORREF rgbBackground) noexcept; + void SetColor(const COLORREF rgbColor, const bool fIsForeground) noexcept; void SetDefaultForeground() noexcept; void SetDefaultBackground() noexcept; @@ -150,9 +150,9 @@ class TextAttribute final private: COLORREF _GetRgbForeground(std::basic_string_view colorTable, - COLORREF defaultColor) const; + COLORREF defaultColor) const noexcept; COLORREF _GetRgbBackground(std::basic_string_view colorTable, - COLORREF defaultColor) const; + COLORREF defaultColor) const noexcept; bool _IsReverseVideo() const noexcept; void _SetBoldness(const bool isBold) noexcept; diff --git a/src/buffer/out/TextColor.cpp b/src/buffer/out/TextColor.cpp index 76778a8230b..606afdac148 100644 --- a/src/buffer/out/TextColor.cpp +++ b/src/buffer/out/TextColor.cpp @@ -11,7 +11,7 @@ // - rgbColor: the COLORREF containing the color information for this TextColor // Return Value: // - -void TextColor::SetColor(const COLORREF rgbColor) +void TextColor::SetColor(const COLORREF rgbColor) noexcept { _meta = ColorType::IsRgb; _red = GetRValue(rgbColor); @@ -25,7 +25,7 @@ void TextColor::SetColor(const COLORREF rgbColor) // - index: the index of the colortable we should use for this TextColor. // Return Value: // - -void TextColor::SetIndex(const BYTE index) +void TextColor::SetIndex(const BYTE index) noexcept { _meta = ColorType::IsIndex; _index = index; @@ -38,7 +38,7 @@ void TextColor::SetIndex(const BYTE index) // - // Return Value: // - -void TextColor::SetDefault() +void TextColor::SetDefault() noexcept { _meta = ColorType::IsDefault; } @@ -63,7 +63,7 @@ void TextColor::SetDefault() // - a COLORREF containing the real value of this TextColor. COLORREF TextColor::GetColor(std::basic_string_view colorTable, const COLORREF defaultColor, - bool brighten) const + bool brighten) const noexcept { if (IsDefault()) { @@ -81,9 +81,9 @@ COLORREF TextColor::GetColor(std::basic_string_view colorTable, // If we find a match, return instead the bright version of this color for (size_t i = 0; i < 8; i++) { - if (colorTable[i] == defaultColor) + if (colorTable.at(i) == defaultColor) { - return colorTable[i + 8]; + return colorTable.at(i + 8); } } } @@ -102,12 +102,12 @@ COLORREF TextColor::GetColor(std::basic_string_view colorTable, if (brighten && _index < 8) { FAIL_FAST_IF(colorTable.size() < 16); - FAIL_FAST_IF((size_t)(_index + 8) > (size_t)(colorTable.size())); - return colorTable[_index + 8]; + FAIL_FAST_IF(gsl::narrow_cast(_index) + 8 > colorTable.size()); + return colorTable.at(gsl::narrow_cast(_index) + 8); } else { - return colorTable[_index]; + return colorTable.at(_index); } } } @@ -119,7 +119,7 @@ COLORREF TextColor::GetColor(std::basic_string_view colorTable, // - // Return Value: // - a COLORREF containing our stored value -COLORREF TextColor::_GetRGB() const +COLORREF TextColor::_GetRGB() const noexcept { return RGB(_red, _green, _blue); } diff --git a/src/buffer/out/TextColor.h b/src/buffer/out/TextColor.h index 6ba9284148c..f551f4e863a 100644 --- a/src/buffer/out/TextColor.h +++ b/src/buffer/out/TextColor.h @@ -91,13 +91,13 @@ struct TextColor return _meta == ColorType::IsRgb; } - void SetColor(const COLORREF rgbColor); - void SetIndex(const BYTE index); - void SetDefault(); + void SetColor(const COLORREF rgbColor) noexcept; + void SetIndex(const BYTE index) noexcept; + void SetDefault() noexcept; COLORREF GetColor(std::basic_string_view colorTable, const COLORREF defaultColor, - const bool brighten) const; + const bool brighten) const noexcept; constexpr BYTE GetIndex() const noexcept { @@ -113,7 +113,7 @@ struct TextColor BYTE _green; BYTE _blue; - COLORREF _GetRGB() const; + COLORREF _GetRGB() const noexcept; #ifdef UNIT_TESTING friend class TextBufferTests; diff --git a/src/buffer/out/UnicodeStorage.cpp b/src/buffer/out/UnicodeStorage.cpp index db5dd318255..41f85adcff6 100644 --- a/src/buffer/out/UnicodeStorage.cpp +++ b/src/buffer/out/UnicodeStorage.cpp @@ -4,7 +4,7 @@ #include "precomp.h" #include "UnicodeStorage.hpp" -UnicodeStorage::UnicodeStorage() : +UnicodeStorage::UnicodeStorage() noexcept : _map{} { } @@ -35,7 +35,7 @@ void UnicodeStorage::StoreGlyph(const key_type key, const mapped_type& glyph) // - erases key and its associated data from the storage // Arguments: // - key - the key to remove -void UnicodeStorage::Erase(const key_type key) noexcept +void UnicodeStorage::Erase(const key_type key) { _map.erase(key); } diff --git a/src/buffer/out/UnicodeStorage.hpp b/src/buffer/out/UnicodeStorage.hpp index 1b386da4792..0f2a629bd40 100644 --- a/src/buffer/out/UnicodeStorage.hpp +++ b/src/buffer/out/UnicodeStorage.hpp @@ -47,13 +47,13 @@ class UnicodeStorage final using key_type = typename COORD; using mapped_type = typename std::vector; - UnicodeStorage(); + UnicodeStorage() noexcept; const mapped_type& GetText(const key_type key) const; void StoreGlyph(const key_type key, const mapped_type& glyph); - void Erase(const key_type key) noexcept; + void Erase(const key_type key); void Remap(const std::map& rowMap, const std::optional width); diff --git a/src/buffer/out/cursor.cpp b/src/buffer/out/cursor.cpp index 6cb69597897..0b2f461be4d 100644 --- a/src/buffer/out/cursor.cpp +++ b/src/buffer/out/cursor.cpp @@ -11,7 +11,7 @@ // - Constructor to set default properties for Cursor // Arguments: // - ulSize - The height of the cursor within this buffer -Cursor::Cursor(const ULONG ulSize, TextBuffer& parentBuffer) : +Cursor::Cursor(const ULONG ulSize, TextBuffer& parentBuffer) noexcept : _parentBuffer{ parentBuffer }, _cPosition{ 0 }, _fHasMoved(false), @@ -87,36 +87,36 @@ ULONG Cursor::GetSize() const noexcept return _ulSize; } -void Cursor::SetHasMoved(const bool fHasMoved) +void Cursor::SetHasMoved(const bool fHasMoved) noexcept { _fHasMoved = fHasMoved; } -void Cursor::SetIsVisible(const bool fIsVisible) +void Cursor::SetIsVisible(const bool fIsVisible) noexcept { _fIsVisible = fIsVisible; _RedrawCursor(); } -void Cursor::SetIsOn(const bool fIsOn) +void Cursor::SetIsOn(const bool fIsOn) noexcept { _fIsOn = fIsOn; _RedrawCursorAlways(); } -void Cursor::SetBlinkingAllowed(const bool fBlinkingAllowed) +void Cursor::SetBlinkingAllowed(const bool fBlinkingAllowed) noexcept { _fBlinkingAllowed = fBlinkingAllowed; _RedrawCursorAlways(); } -void Cursor::SetIsDouble(const bool fIsDouble) +void Cursor::SetIsDouble(const bool fIsDouble) noexcept { _fIsDouble = fIsDouble; _RedrawCursor(); } -void Cursor::SetIsConversionArea(const bool fIsConversionArea) +void Cursor::SetIsConversionArea(const bool fIsConversionArea) noexcept { // Functionally the same as "Hide cursor" // Never called with TRUE, it's only used in the creation of a @@ -125,19 +125,19 @@ void Cursor::SetIsConversionArea(const bool fIsConversionArea) _RedrawCursorAlways(); } -void Cursor::SetIsPopupShown(const bool fIsPopupShown) +void Cursor::SetIsPopupShown(const bool fIsPopupShown) noexcept { // Functionally the same as "Hide cursor" _fIsPopupShown = fIsPopupShown; _RedrawCursorAlways(); } -void Cursor::SetDelay(const bool fDelay) +void Cursor::SetDelay(const bool fDelay) noexcept { _fDelay = fDelay; } -void Cursor::SetSize(const ULONG ulSize) +void Cursor::SetSize(const ULONG ulSize) noexcept { _ulSize = ulSize; _RedrawCursor(); @@ -195,7 +195,7 @@ void Cursor::_RedrawCursorAlways() noexcept CATCH_LOG(); } -void Cursor::SetPosition(const COORD cPosition) +void Cursor::SetPosition(const COORD cPosition) noexcept { _RedrawCursor(); _cPosition.X = cPosition.X; @@ -204,50 +204,50 @@ void Cursor::SetPosition(const COORD cPosition) ResetDelayEOLWrap(); } -void Cursor::SetXPosition(const int NewX) +void Cursor::SetXPosition(const int NewX) noexcept { _RedrawCursor(); - _cPosition.X = (SHORT)NewX; + _cPosition.X = gsl::narrow(NewX); _RedrawCursor(); ResetDelayEOLWrap(); } -void Cursor::SetYPosition(const int NewY) +void Cursor::SetYPosition(const int NewY) noexcept { _RedrawCursor(); - _cPosition.Y = (SHORT)NewY; + _cPosition.Y = gsl::narrow(NewY); _RedrawCursor(); ResetDelayEOLWrap(); } -void Cursor::IncrementXPosition(const int DeltaX) +void Cursor::IncrementXPosition(const int DeltaX) noexcept { _RedrawCursor(); - _cPosition.X += (SHORT)DeltaX; + _cPosition.X += gsl::narrow(DeltaX); _RedrawCursor(); ResetDelayEOLWrap(); } -void Cursor::IncrementYPosition(const int DeltaY) +void Cursor::IncrementYPosition(const int DeltaY) noexcept { _RedrawCursor(); - _cPosition.Y += (SHORT)DeltaY; + _cPosition.Y += gsl::narrow(DeltaY); _RedrawCursor(); ResetDelayEOLWrap(); } -void Cursor::DecrementXPosition(const int DeltaX) +void Cursor::DecrementXPosition(const int DeltaX) noexcept { _RedrawCursor(); - _cPosition.X -= (SHORT)DeltaX; + _cPosition.X -= gsl::narrow(DeltaX); _RedrawCursor(); ResetDelayEOLWrap(); } -void Cursor::DecrementYPosition(const int DeltaY) +void Cursor::DecrementYPosition(const int DeltaY) noexcept { _RedrawCursor(); - _cPosition.Y -= (SHORT)DeltaY; + _cPosition.Y -= gsl::narrow(DeltaY); _RedrawCursor(); ResetDelayEOLWrap(); } @@ -262,7 +262,7 @@ void Cursor::DecrementYPosition(const int DeltaY) // - OtherCursor - The cursor to copy properties from // Return Value: // - -void Cursor::CopyProperties(const Cursor& OtherCursor) +void Cursor::CopyProperties(const Cursor& OtherCursor) noexcept { // We shouldn't copy the position as it will be already rearranged by the resize operation. //_cPosition = pOtherCursor->_cPosition; @@ -288,34 +288,34 @@ void Cursor::CopyProperties(const Cursor& OtherCursor) _color = OtherCursor._color; } -void Cursor::DelayEOLWrap(const COORD coordDelayedAt) +void Cursor::DelayEOLWrap(const COORD coordDelayedAt) noexcept { _coordDelayedAt = coordDelayedAt; _fDelayedEolWrap = true; } -void Cursor::ResetDelayEOLWrap() +void Cursor::ResetDelayEOLWrap() noexcept { _coordDelayedAt = { 0 }; _fDelayedEolWrap = false; } -COORD Cursor::GetDelayedAtPosition() const +COORD Cursor::GetDelayedAtPosition() const noexcept { return _coordDelayedAt; } -bool Cursor::IsDelayedEOLWrap() const +bool Cursor::IsDelayedEOLWrap() const noexcept { return _fDelayedEolWrap; } -void Cursor::StartDeferDrawing() +void Cursor::StartDeferDrawing() noexcept { _fDeferCursorRedraw = true; } -void Cursor::EndDeferDrawing() +void Cursor::EndDeferDrawing() noexcept { if (_fHaveDeferredCursorRedraw) { @@ -325,27 +325,27 @@ void Cursor::EndDeferDrawing() _fDeferCursorRedraw = FALSE; } -const CursorType Cursor::GetType() const +const CursorType Cursor::GetType() const noexcept { return _cursorType; } -const bool Cursor::IsUsingColor() const +const bool Cursor::IsUsingColor() const noexcept { return GetColor() != INVALID_COLOR; } -const COLORREF Cursor::GetColor() const +const COLORREF Cursor::GetColor() const noexcept { return _color; } -void Cursor::SetColor(const unsigned int color) +void Cursor::SetColor(const unsigned int color) noexcept { - _color = (COLORREF)color; + _color = gsl::narrow_cast(color); } -void Cursor::SetType(const CursorType type) +void Cursor::SetType(const CursorType type) noexcept { _cursorType = type; } diff --git a/src/buffer/out/cursor.h b/src/buffer/out/cursor.h index 496e96f1ff2..140dcc9492a 100644 --- a/src/buffer/out/cursor.h +++ b/src/buffer/out/cursor.h @@ -28,7 +28,7 @@ class Cursor final public: static const unsigned int s_InvertCursorColor = INVALID_COLOR; - Cursor(const ULONG ulSize, TextBuffer& parentBuffer); + Cursor(const ULONG ulSize, TextBuffer& parentBuffer) noexcept; ~Cursor(); @@ -50,41 +50,41 @@ class Cursor final ULONG GetSize() const noexcept; COORD GetPosition() const noexcept; - const CursorType GetType() const; - const bool IsUsingColor() const; - const COLORREF GetColor() const; - - void StartDeferDrawing(); - void EndDeferDrawing(); - - void SetHasMoved(const bool fHasMoved); - void SetIsVisible(const bool fIsVisible); - void SetIsOn(const bool fIsOn); - void SetBlinkingAllowed(const bool fIsOn); - void SetIsDouble(const bool fIsDouble); - void SetIsConversionArea(const bool fIsConversionArea); - void SetIsPopupShown(const bool fIsPopupShown); - void SetDelay(const bool fDelay); - void SetSize(const ULONG ulSize); + const CursorType GetType() const noexcept; + const bool IsUsingColor() const noexcept; + const COLORREF GetColor() const noexcept; + + void StartDeferDrawing() noexcept; + void EndDeferDrawing() noexcept; + + void SetHasMoved(const bool fHasMoved) noexcept; + void SetIsVisible(const bool fIsVisible) noexcept; + void SetIsOn(const bool fIsOn) noexcept; + void SetBlinkingAllowed(const bool fIsOn) noexcept; + void SetIsDouble(const bool fIsDouble) noexcept; + void SetIsConversionArea(const bool fIsConversionArea) noexcept; + void SetIsPopupShown(const bool fIsPopupShown) noexcept; + void SetDelay(const bool fDelay) noexcept; + void SetSize(const ULONG ulSize) noexcept; void SetStyle(const ULONG ulSize, const COLORREF color, const CursorType type) noexcept; - void SetPosition(const COORD cPosition); - void SetXPosition(const int NewX); - void SetYPosition(const int NewY); - void IncrementXPosition(const int DeltaX); - void IncrementYPosition(const int DeltaY); - void DecrementXPosition(const int DeltaX); - void DecrementYPosition(const int DeltaY); + void SetPosition(const COORD cPosition) noexcept; + void SetXPosition(const int NewX) noexcept; + void SetYPosition(const int NewY) noexcept; + void IncrementXPosition(const int DeltaX) noexcept; + void IncrementYPosition(const int DeltaY) noexcept; + void DecrementXPosition(const int DeltaX) noexcept; + void DecrementYPosition(const int DeltaY) noexcept; - void CopyProperties(const Cursor& OtherCursor); + void CopyProperties(const Cursor& OtherCursor) noexcept; - void DelayEOLWrap(const COORD coordDelayedAt); - void ResetDelayEOLWrap(); - COORD GetDelayedAtPosition() const; - bool IsDelayedEOLWrap() const; + void DelayEOLWrap(const COORD coordDelayedAt) noexcept; + void ResetDelayEOLWrap() noexcept; + COORD GetDelayedAtPosition() const noexcept; + bool IsDelayedEOLWrap() const noexcept; - void SetColor(const unsigned int color); - void SetType(const CursorType type); + void SetColor(const unsigned int color) noexcept; + void SetType(const CursorType type) noexcept; private: TextBuffer& _parentBuffer; diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 094ede52006..a64406f074b 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -6,10 +6,12 @@ #include "textBuffer.hpp" #include "CharRow.hpp" +#include "../types/inc/utils.hpp" #include "../types/inc/convert.hpp" #pragma hdrstop +using namespace Microsoft::Console; using namespace Microsoft::Console::Types; // Routine Description: @@ -47,7 +49,7 @@ TextBuffer::TextBuffer(const COORD screenBufferSize, // - OtherBuffer - The text buffer to copy properties from // Return Value: // - -void TextBuffer::CopyProperties(const TextBuffer& OtherBuffer) +void TextBuffer::CopyProperties(const TextBuffer& OtherBuffer) noexcept { GetCursor().CopyProperties(OtherBuffer.GetCursor()); } @@ -58,9 +60,9 @@ void TextBuffer::CopyProperties(const TextBuffer& OtherBuffer) // - // Return Value: // - Total number of rows in the buffer -UINT TextBuffer::TotalRowCount() const +UINT TextBuffer::TotalRowCount() const noexcept { - return static_cast(_storage.size()); + return gsl::narrow(_storage.size()); } // Routine Description: @@ -76,7 +78,7 @@ const ROW& TextBuffer::GetRowByOffset(const size_t index) const // Rows are stored circularly, so the index you ask for is offset by the start position and mod the total of rows. const size_t offsetIndex = (_firstRow + index) % totalRows; - return _storage[offsetIndex]; + return _storage.at(offsetIndex); } // Routine Description: @@ -88,7 +90,11 @@ const ROW& TextBuffer::GetRowByOffset(const size_t index) const // - reference to the requested row. Asserts if out of bounds. ROW& TextBuffer::GetRowByOffset(const size_t index) { - return const_cast(static_cast(this)->GetRowByOffset(index)); + const size_t totalRows = TotalRowCount(); + + // Rows are stored circularly, so the index you ask for is offset by the start position and mod the total of rows. + const size_t offsetIndex = (_firstRow + index) % totalRows; + return _storage.at(offsetIndex); } // Routine Description: @@ -540,7 +546,7 @@ bool TextBuffer::IncrementCircularBuffer() _renderTarget.TriggerCircling(); // First, clean out the old "first row" as it will become the "last row" of the buffer after the circle is performed. - bool fSuccess = _storage.at(_firstRow).Reset(_currentAttributes); + const bool fSuccess = _storage.at(_firstRow).Reset(_currentAttributes); if (fSuccess) { // Now proceed to increment. @@ -558,30 +564,45 @@ bool TextBuffer::IncrementCircularBuffer() //Routine Description: // - Retrieves the position of the last non-space character on the final line of the text buffer. +// - By default, we search the entire buffer to find the last non-space character //Arguments: // - //Return Value: // - Coordinate position in screen coordinates (offset coordinates, not array index coordinates). COORD TextBuffer::GetLastNonSpaceCharacter() const { - COORD coordEndOfText; - // Always search the whole buffer, by starting at the bottom. - coordEndOfText.Y = GetSize().BottomInclusive(); + return GetLastNonSpaceCharacter(GetSize()); +} - const ROW* pCurrRow = &GetRowByOffset(coordEndOfText.Y); +//Routine Description: +// - Retrieves the position of the last non-space character in the given viewport +// - This is basically an optimized version of GetLastNonSpaceCharacter(), and can be called when +// - we know the last character is within the given viewport (so we don't need to check the entire buffer) +//Arguments: +// - The viewport +//Return value: +// - Coordinate position (relative to the text buffer) +COORD TextBuffer::GetLastNonSpaceCharacter(const Microsoft::Console::Types::Viewport viewport) const +{ + COORD coordEndOfText = { 0 }; + // Search the given viewport by starting at the bottom. + coordEndOfText.Y = viewport.BottomInclusive(); + + const auto& currRow = GetRowByOffset(coordEndOfText.Y); // The X position of the end of the valid text is the Right draw boundary (which is one beyond the final valid character) - coordEndOfText.X = static_cast(pCurrRow->GetCharRow().MeasureRight()) - 1; + coordEndOfText.X = gsl::narrow(currRow.GetCharRow().MeasureRight()) - 1; // If the X coordinate turns out to be -1, the row was empty, we need to search backwards for the real end of text. - bool fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > 0); // this row is empty, and we're not at the top + const auto viewportTop = viewport.Top(); + bool fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > viewportTop); // this row is empty, and we're not at the top while (fDoBackUp) { coordEndOfText.Y--; - pCurrRow = &GetRowByOffset(coordEndOfText.Y); + const auto& backupRow = GetRowByOffset(coordEndOfText.Y); // We need to back up to the previous row if this line is empty, AND there are more rows - coordEndOfText.X = static_cast(pCurrRow->GetCharRow().MeasureRight()) - 1; - fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > 0); + coordEndOfText.X = gsl::narrow(backupRow.GetCharRow().MeasureRight()) - 1; + fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > viewportTop); } // don't allow negative results @@ -623,7 +644,7 @@ COORD TextBuffer::_GetPreviousFromCursor() const return coordPosition; } -const SHORT TextBuffer::GetFirstRowIndex() const +const SHORT TextBuffer::GetFirstRowIndex() const noexcept { return _firstRow; } @@ -632,7 +653,7 @@ const Viewport TextBuffer::GetSize() const return Viewport::FromDimensions({ 0, 0 }, { gsl::narrow(_storage.at(0).size()), gsl::narrow(_storage.size()) }); } -void TextBuffer::_SetFirstRowIndex(const SHORT FirstRowIndex) +void TextBuffer::_SetFirstRowIndex(const SHORT FirstRowIndex) noexcept { _firstRow = FirstRowIndex; } @@ -738,12 +759,12 @@ void TextBuffer::ScrollRows(const SHORT firstRow, const SHORT size, const SHORT _RefreshRowIDs(std::nullopt); } -Cursor& TextBuffer::GetCursor() +Cursor& TextBuffer::GetCursor() noexcept { return _cursor; } -const Cursor& TextBuffer::GetCursor() const +const Cursor& TextBuffer::GetCursor() const noexcept { return _cursor; } @@ -778,7 +799,7 @@ void TextBuffer::Reset() // - newSize - new size of screen. // Return Value: // - Success if successful. Invalid parameter if screen buffer size is unexpected. No memory if allocation failed. -[[nodiscard]] NTSTATUS TextBuffer::ResizeTraditional(const COORD newSize) noexcept +[[nodiscard]] NTSTATUS TextBuffer::ResizeTraditional(const COORD newSize) { RETURN_HR_IF(E_INVALIDARG, newSize.X < 0 || newSize.Y < 0); @@ -795,7 +816,7 @@ void TextBuffer::Reset() // rotate rows until the top row is at index 0 try { - const ROW& newTopRow = _storage[TopRowIndex]; + const ROW& newTopRow = _storage.at(TopRowIndex); while (&newTopRow != &_storage.front()) { _storage.push_back(std::move(_storage.front())); @@ -826,12 +847,12 @@ void TextBuffer::Reset() return S_OK; } -const UnicodeStorage& TextBuffer::GetUnicodeStorage() const +const UnicodeStorage& TextBuffer::GetUnicodeStorage() const noexcept { return _unicodeStorage; } -UnicodeStorage& TextBuffer::GetUnicodeStorage() +UnicodeStorage& TextBuffer::GetUnicodeStorage() noexcept { return _unicodeStorage; } @@ -906,7 +927,7 @@ ROW& TextBuffer::_GetPrevRowNoWrap(const ROW& Row) } THROW_HR_IF(E_FAIL, Row.GetId() == _firstRow); - return _storage[prevRowIndex]; + return _storage.at(prevRowIndex); } // Method Description: @@ -915,7 +936,7 @@ ROW& TextBuffer::_GetPrevRowNoWrap(const ROW& Row) // - // Return Value: // - This buffer's current render target. -Microsoft::Console::Render::IRenderTarget& TextBuffer::GetRenderTarget() +Microsoft::Console::Render::IRenderTarget& TextBuffer::GetRenderTarget() noexcept { return _renderTarget; } @@ -960,9 +981,9 @@ const TextBuffer::TextAndColor TextBuffer::GetTextForClipboard(const bool lineSe std::vector selectionBkAttr; // preallocate to avoid reallocs - selectionText.reserve(highlight.Width() + 2); // + 2 for \r\n if we munged it - selectionFgAttr.reserve(highlight.Width() + 2); - selectionBkAttr.reserve(highlight.Width() + 2); + selectionText.reserve(gsl::narrow(highlight.Width()) + 2); // + 2 for \r\n if we munged it + selectionFgAttr.reserve(gsl::narrow(highlight.Width()) + 2); + selectionBkAttr.reserve(gsl::narrow(highlight.Width()) + 2); // copy char data into the string buffer, skipping trailing bytes while (it) @@ -981,6 +1002,8 @@ const TextBuffer::TextAndColor TextBuffer::GetTextForClipboard(const bool lineSe selectionBkAttr.push_back(CellBkAttr); } } +#pragma warning(suppress : 26444) + // TODO GH 2675: figure out why there's custom construction/destruction happening here it++; } @@ -1028,3 +1051,179 @@ const TextBuffer::TextAndColor TextBuffer::GetTextForClipboard(const bool lineSe return data; } + +// Routine Description: +// - Generates a CF_HTML compliant structure based on the passed in text and color data +// Arguments: +// - rows - the text and color data we will format & encapsulate +// - fontHeightPoints - the unscaled font height +// - fontFaceName - the name of the font used +// - htmlTitle - value used in title tag of html header. Used to name the application +// Return Value: +// - string containing the generated HTML +std::string TextBuffer::GenHTML(const TextAndColor& rows, const int fontHeightPoints, const PCWCHAR fontFaceName, const std::string& htmlTitle) +{ + try + { + // TODO: GH 602 the font name needs to be passed and stored around as an actual bounded type, not an implicit bounds on LF_FACESIZE + const auto faceLength = wcsnlen_s(fontFaceName, LF_FACESIZE); + const std::wstring_view faceNameView{ fontFaceName, faceLength }; + + std::ostringstream htmlBuilder; + + // First we have to add some standard + // HTML boiler plate required for CF_HTML + // as part of the HTML Clipboard format + const std::string htmlHeader = + "" + htmlTitle + ""; + htmlBuilder << htmlHeader; + + htmlBuilder << ""; + + // apply global style in div element + { + htmlBuilder << "
"; + } + + // copy text and info color from buffer + bool hasWrittenAnyText = false; + std::optional fgColor = std::nullopt; + std::optional bkColor = std::nullopt; + for (size_t row = 0; row < rows.text.size(); row++) + { + size_t startOffset = 0; + + if (row != 0) + { + htmlBuilder << "
"; + } + + for (size_t col = 0; col < rows.text.at(row).length(); col++) + { + // do not include \r nor \n as they don't have attributes + // and are not HTML friendly. For line break use '
' instead. + const bool isLastCharInRow = + col == rows.text.at(row).length() - 1 || + rows.text.at(row).at(col + 1) == '\r' || + rows.text.at(row).at(col + 1) == '\n'; + + bool colorChanged = false; + if (!fgColor.has_value() || rows.FgAttr.at(row).at(col) != fgColor.value()) + { + fgColor = rows.FgAttr.at(row).at(col); + colorChanged = true; + } + + if (!bkColor.has_value() || rows.BkAttr.at(row).at(col) != bkColor.value()) + { + bkColor = rows.BkAttr.at(row).at(col); + colorChanged = true; + } + + const auto writeAccumulatedChars = [&](bool includeCurrent) { + if (col > startOffset) + { + // note: this should be escaped (for '<', '>', and '&'), + // however MS Word doesn't appear to support HTML entities + htmlBuilder << ConvertToA(CP_UTF8, std::wstring_view(rows.text.at(row)).substr(startOffset, col - startOffset + includeCurrent)); + startOffset = col; + } + }; + + if (colorChanged) + { + writeAccumulatedChars(false); + + if (hasWrittenAnyText) + { + htmlBuilder << ""; + } + + htmlBuilder << ""; + } + + hasWrittenAnyText = true; + + if (isLastCharInRow) + { + writeAccumulatedChars(true); + break; + } + } + } + + if (hasWrittenAnyText) + { + // last opened span wasn't closed in loop above, so close it now + htmlBuilder << ""; + } + + htmlBuilder << "
"; + + htmlBuilder << ""; + + constexpr std::string_view HtmlFooter = ""; + htmlBuilder << HtmlFooter; + + // once filled with values, there will be exactly 157 bytes in the clipboard header + constexpr size_t ClipboardHeaderSize = 157; + + // these values are byte offsets from start of clipboard + const size_t htmlStartPos = ClipboardHeaderSize; + const size_t htmlEndPos = ClipboardHeaderSize + gsl::narrow(htmlBuilder.tellp()); + const size_t fragStartPos = ClipboardHeaderSize + gsl::narrow(htmlHeader.length()); + const size_t fragEndPos = htmlEndPos - HtmlFooter.length(); + + // header required by HTML 0.9 format + std::ostringstream clipHeaderBuilder; + clipHeaderBuilder << "Version:0.9\r\n"; + clipHeaderBuilder << std::setfill('0'); + clipHeaderBuilder << "StartHTML:" << std::setw(10) << htmlStartPos << "\r\n"; + clipHeaderBuilder << "EndHTML:" << std::setw(10) << htmlEndPos << "\r\n"; + clipHeaderBuilder << "StartFragment:" << std::setw(10) << fragStartPos << "\r\n"; + clipHeaderBuilder << "EndFragment:" << std::setw(10) << fragEndPos << "\r\n"; + clipHeaderBuilder << "StartSelection:" << std::setw(10) << fragStartPos << "\r\n"; + clipHeaderBuilder << "EndSelection:" << std::setw(10) << fragEndPos << "\r\n"; + + return clipHeaderBuilder.str() + htmlBuilder.str(); + } + catch (...) + { + LOG_HR(wil::ResultFromCaughtException()); + return {}; + } +} diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index 8e5aa205158..beb477fbbd6 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -72,7 +72,7 @@ class TextBuffer final ~TextBuffer() = default; // Used for duplicating properties to another text buffer - void CopyProperties(const TextBuffer& OtherBuffer); + void CopyProperties(const TextBuffer& OtherBuffer) noexcept; // row manipulation const ROW& GetRowByOffset(const size_t index) const; @@ -105,17 +105,18 @@ class TextBuffer final bool IncrementCircularBuffer(); COORD GetLastNonSpaceCharacter() const; + COORD GetLastNonSpaceCharacter(const Microsoft::Console::Types::Viewport viewport) const; - Cursor& GetCursor(); - const Cursor& GetCursor() const; + Cursor& GetCursor() noexcept; + const Cursor& GetCursor() const noexcept; - const SHORT GetFirstRowIndex() const; + const SHORT GetFirstRowIndex() const noexcept; const Microsoft::Console::Types::Viewport GetSize() const; void ScrollRows(const SHORT firstRow, const SHORT size, const SHORT delta); - UINT TotalRowCount() const; + UINT TotalRowCount() const noexcept; [[nodiscard]] TextAttribute GetCurrentAttributes() const noexcept; @@ -123,12 +124,12 @@ class TextBuffer final void Reset(); - [[nodiscard]] HRESULT ResizeTraditional(const COORD newSize) noexcept; + [[nodiscard]] HRESULT ResizeTraditional(const COORD newSize); - const UnicodeStorage& GetUnicodeStorage() const; - UnicodeStorage& GetUnicodeStorage(); + const UnicodeStorage& GetUnicodeStorage() const noexcept; + UnicodeStorage& GetUnicodeStorage() noexcept; - Microsoft::Console::Render::IRenderTarget& GetRenderTarget(); + Microsoft::Console::Render::IRenderTarget& GetRenderTarget() noexcept; class TextAndColor { @@ -144,6 +145,11 @@ class TextBuffer final std::function GetForegroundColor, std::function GetBackgroundColor) const; + static std::string GenHTML(const TextAndColor& rows, + const int fontHeightPoints, + const PCWCHAR fontFaceName, + const std::string& htmlTitle); + private: std::deque _storage; Cursor _cursor; @@ -159,7 +165,7 @@ class TextBuffer final Microsoft::Console::Render::IRenderTarget& _renderTarget; - void _SetFirstRowIndex(const SHORT FirstRowIndex); + void _SetFirstRowIndex(const SHORT FirstRowIndex) noexcept; COORD _GetPreviousFromCursor() const; diff --git a/src/buffer/out/textBufferCellIterator.cpp b/src/buffer/out/textBufferCellIterator.cpp index 50948ab6bbd..285a4ec2047 100644 --- a/src/buffer/out/textBufferCellIterator.cpp +++ b/src/buffer/out/textBufferCellIterator.cpp @@ -65,7 +65,7 @@ TextBufferCellIterator::operator bool() const noexcept // - it - The other iterator to compare to this one. // Return Value: // - True if it's the same text buffer and same cell position. False otherwise. -bool TextBufferCellIterator::operator==(const TextBufferCellIterator& it) const noexcept +bool TextBufferCellIterator::operator==(const TextBufferCellIterator& it) const { return _pos == it._pos && &_buffer == &it._buffer && @@ -81,7 +81,7 @@ bool TextBufferCellIterator::operator==(const TextBufferCellIterator& it) const // - it - The other iterator to compare to this one. // Return Value: // - True if it's the same text buffer and different cell position or if they're different buffers. False otherwise. -bool TextBufferCellIterator::operator!=(const TextBufferCellIterator& it) const noexcept +bool TextBufferCellIterator::operator!=(const TextBufferCellIterator& it) const { return !(*this == it); } @@ -212,7 +212,7 @@ void TextBufferCellIterator::_SetPos(const COORD newPos) if (newPos.X != _pos.X) { - const ptrdiff_t diff = newPos.X - _pos.X; + const auto diff = gsl::narrow_cast(newPos.X) - gsl::narrow_cast(_pos.X); _attrIter += diff; } diff --git a/src/buffer/out/textBufferCellIterator.hpp b/src/buffer/out/textBufferCellIterator.hpp index 730154f28ab..d647bd70152 100644 --- a/src/buffer/out/textBufferCellIterator.hpp +++ b/src/buffer/out/textBufferCellIterator.hpp @@ -28,12 +28,10 @@ class TextBufferCellIterator TextBufferCellIterator(const TextBuffer& buffer, COORD pos); TextBufferCellIterator(const TextBuffer& buffer, COORD pos, const Microsoft::Console::Types::Viewport limits); - ~TextBufferCellIterator() = default; - operator bool() const noexcept; - bool operator==(const TextBufferCellIterator& it) const noexcept; - bool operator!=(const TextBufferCellIterator& it) const noexcept; + bool operator==(const TextBufferCellIterator& it) const; + bool operator!=(const TextBufferCellIterator& it) const; TextBufferCellIterator& operator+=(const ptrdiff_t& movement); TextBufferCellIterator& operator-=(const ptrdiff_t& movement); diff --git a/src/buffer/out/textBufferTextIterator.cpp b/src/buffer/out/textBufferTextIterator.cpp index 05c6a4c997d..9687e317584 100644 --- a/src/buffer/out/textBufferTextIterator.cpp +++ b/src/buffer/out/textBufferTextIterator.cpp @@ -16,7 +16,7 @@ using namespace Microsoft::Console::Types; // - Narrows the view of a cell iterator into a text only iterator. // Arguments: // - A cell iterator -TextBufferTextIterator::TextBufferTextIterator(const TextBufferCellIterator& cellIt) : +TextBufferTextIterator::TextBufferTextIterator(const TextBufferCellIterator& cellIt) noexcept : TextBufferCellIterator(cellIt) { } @@ -25,7 +25,8 @@ TextBufferTextIterator::TextBufferTextIterator(const TextBufferCellIterator& cel // - Returns the text information from the text buffer position addressed by this iterator. // Return Value: // - Read only UTF-16 text data -const std::wstring_view TextBufferTextIterator::operator*() const +// TODO GH 2682, fix design so this doesn't have to be suppressed. +[[gsl::suppress(26434)]] const std::wstring_view TextBufferTextIterator::operator*() const noexcept { return _view.Chars(); } @@ -34,7 +35,8 @@ const std::wstring_view TextBufferTextIterator::operator*() const // - Returns the text information from the text buffer position addressed by this iterator. // Return Value: // - Read only UTF-16 text data -const std::wstring_view* TextBufferTextIterator::operator->() const +// TODO GH 2682, fix design so this doesn't have to be suppressed. +[[gsl::suppress(26434)]] const std::wstring_view* TextBufferTextIterator::operator->() const noexcept { return &_view.Chars(); } diff --git a/src/buffer/out/textBufferTextIterator.hpp b/src/buffer/out/textBufferTextIterator.hpp index 845e86e19c8..634745eec63 100644 --- a/src/buffer/out/textBufferTextIterator.hpp +++ b/src/buffer/out/textBufferTextIterator.hpp @@ -22,10 +22,10 @@ class SCREEN_INFORMATION; class TextBufferTextIterator final : public TextBufferCellIterator { public: - TextBufferTextIterator(const TextBufferCellIterator& cellIter); + TextBufferTextIterator(const TextBufferCellIterator& cellIter) noexcept; - const std::wstring_view operator*() const; - const std::wstring_view* operator->() const; + const std::wstring_view operator*() const noexcept; + const std::wstring_view* operator->() const noexcept; protected: #if UNIT_TESTING diff --git a/src/cascadia/CascadiaPackage/CascadiaPackage.wapproj b/src/cascadia/CascadiaPackage/CascadiaPackage.wapproj index 665417e1d18..f4ad9305f38 100644 --- a/src/cascadia/CascadiaPackage/CascadiaPackage.wapproj +++ b/src/cascadia/CascadiaPackage/CascadiaPackage.wapproj @@ -5,7 +5,7 @@ 0 - 2 + 4 10.0.18362.0 @@ -295,7 +295,8 @@ important reasons), that doesn't work for us. --> - <_GenerateProjectPriFileDependsOn>OpenConsoleLiftDesktopBridgePriFiles;$(_GenerateProjectPriFileDependsOn) + + <_GenerateProjectPriFileDependsOn Condition="$(MSBuildVersion) < '16.3.0'">OpenConsoleLiftDesktopBridgePriFiles;$(_GenerateProjectPriFileDependsOn) @@ -305,4 +306,25 @@ - \ No newline at end of file + + + + + + $([MSBuild]::Unescape('$(WapProjBeforeGenerateAppxManifestDependsOn.Replace('_RemoveAllNonWapUWPItems', '_OpenConsoleRemoveAllNonWapUWPItems'))')) + + + + + + + + + + + + + diff --git a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest index 59a2c75e2ca..a8ccfe6c73c 100644 --- a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest @@ -21,7 +21,7 @@ - + diff --git a/src/cascadia/CascadiaPackage/Package.appxmanifest b/src/cascadia/CascadiaPackage/Package.appxmanifest index 347b1962eac..63a5f1b381a 100644 --- a/src/cascadia/CascadiaPackage/Package.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package.appxmanifest @@ -21,7 +21,7 @@ - + diff --git a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp new file mode 100644 index 00000000000..4a70386e33d --- /dev/null +++ b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp @@ -0,0 +1,368 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" + +#include "../TerminalApp/ColorScheme.h" +#include "../TerminalApp/CascadiaSettings.h" + +using namespace Microsoft::Console; +using namespace TerminalApp; +using namespace WEX::Logging; +using namespace WEX::TestExecution; +using namespace WEX::Common; + +namespace TerminalAppLocalTests +{ + // Unfortunately, these tests _WILL NOT_ work in our CI, until we have a lab + // machine available that can run Windows version 18362. + + class SettingsTests + { + // Use a custom manifest to ensure that we can activate winrt types from + // our test. This property will tell taef to manually use this as the + // sxs manifest during this test class. It includes all the cppwinrt + // types we've defined, so if your test is crashing for an unknown + // reason, make sure it's included in that file. + // If you want to do anything XAML-y, you'll need to run yor test in a + // packaged context. See TabTests.cpp for more details on that. + BEGIN_TEST_CLASS(SettingsTests) + TEST_CLASS_PROPERTY(L"ActivationContext", L"TerminalApp.LocalTests.manifest") + END_TEST_CLASS() + + TEST_METHOD(TryCreateWinRTType); + TEST_METHOD(ValidateProfilesExist); + TEST_METHOD(ValidateDefaultProfileExists); + TEST_METHOD(ValidateDuplicateProfiles); + TEST_METHOD(ValidateManyWarnings); + + TEST_CLASS_SETUP(ClassSetup) + { + reader = std::unique_ptr(Json::CharReaderBuilder::CharReaderBuilder().newCharReader()); + return true; + } + Json::Value VerifyParseSucceeded(std::string content); + + private: + std::unique_ptr reader; + }; + + Json::Value SettingsTests::VerifyParseSucceeded(std::string content) + { + Json::Value root; + std::string errs; + const bool parseResult = reader->parse(content.c_str(), content.c_str() + content.size(), &root, &errs); + VERIFY_IS_TRUE(parseResult, winrt::to_hstring(errs).c_str()); + return root; + } + + void SettingsTests::TryCreateWinRTType() + { + winrt::Microsoft::Terminal::Settings::TerminalSettings settings{}; + VERIFY_IS_NOT_NULL(settings); + auto oldFontSize = settings.FontSize(); + settings.FontSize(oldFontSize + 5); + auto newFontSize = settings.FontSize(); + VERIFY_ARE_NOT_EQUAL(oldFontSize, newFontSize); + } + + void SettingsTests::ValidateProfilesExist() + { + const std::string settingsWithProfiles{ R"( + { + "profiles": [ + { + "name" : "profile0" + } + ] + })" }; + + const std::string settingsWithoutProfiles{ R"( + { + "defaultProfile": "{6239a42c-1de4-49a3-80bd-e8fdd045185c}" + })" }; + + const std::string settingsWithEmptyProfiles{ R"( + { + "profiles": [] + })" }; + + { + // Case 1: Good settings + const auto settingsObject = VerifyParseSucceeded(settingsWithProfiles); + auto settings = CascadiaSettings::FromJson(settingsObject); + settings->_ValidateProfilesExist(); + } + { + // Case 2: Bad settings + const auto settingsObject = VerifyParseSucceeded(settingsWithoutProfiles); + auto settings = CascadiaSettings::FromJson(settingsObject); + bool caughtExpectedException = false; + try + { + settings->_ValidateProfilesExist(); + } + catch (const ::TerminalApp::SettingsException& ex) + { + VERIFY_IS_TRUE(ex.Error() == ::TerminalApp::SettingsLoadErrors::NoProfiles); + caughtExpectedException = true; + } + VERIFY_IS_TRUE(caughtExpectedException); + } + { + // Case 3: Bad settings + const auto settingsObject = VerifyParseSucceeded(settingsWithEmptyProfiles); + auto settings = CascadiaSettings::FromJson(settingsObject); + bool caughtExpectedException = false; + try + { + settings->_ValidateProfilesExist(); + } + catch (const ::TerminalApp::SettingsException& ex) + { + VERIFY_IS_TRUE(ex.Error() == ::TerminalApp::SettingsLoadErrors::NoProfiles); + caughtExpectedException = true; + } + VERIFY_IS_TRUE(caughtExpectedException); + } + } + + void SettingsTests::ValidateDefaultProfileExists() + { + const std::string goodProfiles{ R"( + { + "globals": { + "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}" + }, + "profiles": [ + { + "name" : "profile0", + "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}" + }, + { + "name" : "profile0", + "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}" + } + ] + })" }; + + const std::string badProfiles{ R"( + { + "globals": { + "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}" + }, + "profiles": [ + { + "name" : "profile0", + "guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}" + }, + { + "name" : "profile1", + "guid": "{6239a42c-4444-49a3-80bd-e8fdd045185c}" + } + ] + })" }; + + const std::string noDefaultAtAll{ R"( + { + "globals": { + "alwaysShowTabs": true + }, + "profiles": [ + { + "name" : "profile0", + "guid": "{6239a42c-5555-49a3-80bd-e8fdd045185c}" + }, + { + "name" : "profile1", + "guid": "{6239a42c-6666-49a3-80bd-e8fdd045185c}" + } + ] + })" }; + + { + // Case 1: Good settings + Log::Comment(NoThrowString().Format( + L"Testing a pair of profiles with unique guids, and the defaultProfile is one of those guids")); + const auto settingsObject = VerifyParseSucceeded(goodProfiles); + auto settings = CascadiaSettings::FromJson(settingsObject); + settings->_ValidateDefaultProfileExists(); + VERIFY_ARE_EQUAL(static_cast(0), settings->_warnings.size()); + VERIFY_ARE_EQUAL(static_cast(2), settings->_profiles.size()); + VERIFY_ARE_EQUAL(settings->_globals.GetDefaultProfile(), settings->_profiles.at(0).GetGuid()); + } + { + // Case 2: Bad settings + Log::Comment(NoThrowString().Format( + L"Testing a pair of profiles with unique guids, but the defaultProfile is NOT one of those guids")); + const auto settingsObject = VerifyParseSucceeded(badProfiles); + auto settings = CascadiaSettings::FromJson(settingsObject); + settings->_ValidateDefaultProfileExists(); + VERIFY_ARE_EQUAL(static_cast(1), settings->_warnings.size()); + VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingDefaultProfile, settings->_warnings.at(0)); + + VERIFY_ARE_EQUAL(static_cast(2), settings->_profiles.size()); + VERIFY_ARE_EQUAL(settings->_globals.GetDefaultProfile(), settings->_profiles.at(0).GetGuid()); + } + { + // Case 2: Bad settings + Log::Comment(NoThrowString().Format( + L"Testing a pair of profiles with unique guids, and no defaultProfile at all")); + const auto settingsObject = VerifyParseSucceeded(badProfiles); + auto settings = CascadiaSettings::FromJson(settingsObject); + settings->_ValidateDefaultProfileExists(); + VERIFY_ARE_EQUAL(static_cast(1), settings->_warnings.size()); + VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingDefaultProfile, settings->_warnings.at(0)); + + VERIFY_ARE_EQUAL(static_cast(2), settings->_profiles.size()); + VERIFY_ARE_EQUAL(settings->_globals.GetDefaultProfile(), settings->_profiles.at(0).GetGuid()); + } + } + + void SettingsTests::ValidateDuplicateProfiles() + { + const std::string goodProfiles{ R"( + { + "profiles": [ + { + "name" : "profile0", + "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}" + }, + { + "name" : "profile0", + "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}" + } + ] + })" }; + + const std::string badProfiles{ R"( + { + "profiles": [ + { + "name" : "profile0", + "guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}" + }, + { + "name" : "profile1", + "guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}" + } + ] + })" }; + + const std::string veryBadProfiles{ R"( + { + "profiles": [ + { + "name" : "profile0", + "guid": "{6239a42c-4444-49a3-80bd-e8fdd045185c}" + }, + { + "name" : "profile1", + "guid": "{6239a42c-5555-49a3-80bd-e8fdd045185c}" + }, + { + "name" : "profile2", + "guid": "{6239a42c-4444-49a3-80bd-e8fdd045185c}" + }, + { + "name" : "profile3", + "guid": "{6239a42c-4444-49a3-80bd-e8fdd045185c}" + }, + { + "name" : "profile4", + "guid": "{6239a42c-6666-49a3-80bd-e8fdd045185c}" + }, + { + "name" : "profile5", + "guid": "{6239a42c-5555-49a3-80bd-e8fdd045185c}" + }, + { + "name" : "profile6", + "guid": "{6239a42c-7777-49a3-80bd-e8fdd045185c}" + } + ] + })" }; + + { + // Case 1: Good settings + Log::Comment(NoThrowString().Format( + L"Testing a pair of profiles with unique guids")); + const auto settingsObject = VerifyParseSucceeded(goodProfiles); + auto settings = CascadiaSettings::FromJson(settingsObject); + settings->_ValidateNoDuplicateProfiles(); + VERIFY_ARE_EQUAL(static_cast(0), settings->_warnings.size()); + VERIFY_ARE_EQUAL(static_cast(2), settings->_profiles.size()); + } + { + // Case 2: Bad settings + Log::Comment(NoThrowString().Format( + L"Testing a pair of profiles with the same guid")); + const auto settingsObject = VerifyParseSucceeded(badProfiles); + auto settings = CascadiaSettings::FromJson(settingsObject); + + settings->_ValidateNoDuplicateProfiles(); + + VERIFY_ARE_EQUAL(static_cast(1), settings->_warnings.size()); + VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::DuplicateProfile, settings->_warnings.at(0)); + + VERIFY_ARE_EQUAL(static_cast(1), settings->_profiles.size()); + VERIFY_ARE_EQUAL(L"profile0", settings->_profiles.at(0).GetName()); + } + { + // Case 3: Very bad settings + Log::Comment(NoThrowString().Format( + L"Testing a set of profiles, many of which with duplicated guids")); + const auto settingsObject = VerifyParseSucceeded(veryBadProfiles); + auto settings = CascadiaSettings::FromJson(settingsObject); + settings->_ValidateNoDuplicateProfiles(); + VERIFY_ARE_EQUAL(static_cast(1), settings->_warnings.size()); + VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::DuplicateProfile, settings->_warnings.at(0)); + + VERIFY_ARE_EQUAL(static_cast(4), settings->_profiles.size()); + VERIFY_ARE_EQUAL(L"profile0", settings->_profiles.at(0).GetName()); + VERIFY_ARE_EQUAL(L"profile1", settings->_profiles.at(1).GetName()); + VERIFY_ARE_EQUAL(L"profile4", settings->_profiles.at(2).GetName()); + VERIFY_ARE_EQUAL(L"profile6", settings->_profiles.at(3).GetName()); + } + } + + void SettingsTests::ValidateManyWarnings() + { + const std::string badProfiles{ R"( + { + "globals": { + "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}" + }, + "profiles": [ + { + "name" : "profile0", + "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}" + }, + { + "name" : "profile1", + "guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}" + }, + { + "name" : "profile2", + "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}" + } + ] + })" }; + + // Case 2: Bad settings + Log::Comment(NoThrowString().Format( + L"Testing a pair of profiles with the same guid")); + const auto settingsObject = VerifyParseSucceeded(badProfiles); + auto settings = CascadiaSettings::FromJson(settingsObject); + + settings->_ValidateSettings(); + + VERIFY_ARE_EQUAL(static_cast(2), settings->_warnings.size()); + VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::DuplicateProfile, settings->_warnings.at(0)); + VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingDefaultProfile, settings->_warnings.at(1)); + + VERIFY_ARE_EQUAL(static_cast(2), settings->_profiles.size()); + VERIFY_ARE_EQUAL(settings->_globals.GetDefaultProfile(), settings->_profiles.at(0).GetGuid()); + } + +} diff --git a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp new file mode 100644 index 00000000000..4a1f692139e --- /dev/null +++ b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" + +#include "../TerminalApp/ColorScheme.h" +#include "../TerminalApp/Tab.h" + +using namespace Microsoft::Console; +using namespace TerminalApp; +using namespace WEX::Logging; +using namespace WEX::TestExecution; + +namespace TerminalAppLocalTests +{ + // Unfortunately, these tests _WILL NOT_ work in our CI, until we have a lab + // machine available that can run Windows version 18362. + + class TabTests + { + // For this set of tests, we need to activate some XAML content. To do + // that, we need to be able to activate Xaml Islands(XI), using the Xaml + // Hosting APIs. Because XI looks at the manifest of the exe running, we + // can't just use the TerminalApp.Unit.Tests.manifest as our + // ActivationContext. XI is going to inspect `te.exe`s manifest to try + // and find the maxversiontested property, but te.exe hasn't set that. + // Instead, this test will run as a UAP application, as a packaged + // centenial (win32) app. We'll specify our own AppxManifest, so that + // we'll be able to also load all the dll's for the types we've defined + // (and want to use here). This does come with a minor caveat, as + // deploying the appx takes a bit, so use sparingly (though it will + // deploy once per class when used like this.) + BEGIN_TEST_CLASS(TabTests) + TEST_CLASS_PROPERTY(L"RunAs", L"UAP") + TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TerminalApp.LocalTests.AppxManifest.xml") + END_TEST_CLASS() + + // These four tests act as canary tests. If one of them fails, then they + // can help you identify if something much lower in the stack has + // failed. + TEST_METHOD(TryInitXamlIslands); + TEST_METHOD(TryCreateLocalWinRTType); + TEST_METHOD(TryCreateXamlObjects); + TEST_METHOD(TryCreateTab); + + TEST_CLASS_SETUP(ClassSetup) + { + winrt::init_apartment(winrt::apartment_type::single_threaded); + // Initialize the Xaml Hosting Manager + _manager = winrt::Windows::UI::Xaml::Hosting::WindowsXamlManager::InitializeForCurrentThread(); + _source = winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource{}; + + return true; + } + + private: + winrt::Windows::UI::Xaml::Hosting::WindowsXamlManager _manager{ nullptr }; + winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource _source{ nullptr }; + }; + + void TabTests::TryInitXamlIslands() + { + // Ensures that XAML Islands was initialized correctly + VERIFY_IS_NOT_NULL(_manager); + VERIFY_IS_NOT_NULL(_source); + } + + void TabTests::TryCreateLocalWinRTType() + { + // Verify we can create a WinRT type we authored + // Just creating it is enough to know that everything is working. + winrt::Microsoft::Terminal::Settings::TerminalSettings settings{}; + VERIFY_IS_NOT_NULL(settings); + auto oldFontSize = settings.FontSize(); + settings.FontSize(oldFontSize + 5); + auto newFontSize = settings.FontSize(); + VERIFY_ARE_NOT_EQUAL(oldFontSize, newFontSize); + } + + void TabTests::TryCreateXamlObjects() + { + // Verify we can create a some XAML objects + // Just creating all of them is enough to know that everything is working. + winrt::Windows::UI::Xaml::Controls::UserControl controlRoot; + VERIFY_IS_NOT_NULL(controlRoot); + winrt::Windows::UI::Xaml::Controls::Grid root; + VERIFY_IS_NOT_NULL(root); + winrt::Windows::UI::Xaml::Controls::SwapChainPanel swapChainPanel; + VERIFY_IS_NOT_NULL(swapChainPanel); + winrt::Windows::UI::Xaml::Controls::Primitives::ScrollBar scrollBar; + VERIFY_IS_NOT_NULL(scrollBar); + } + + void TabTests::TryCreateTab() + { + // Just try creating all of: + // 1. one of our pure c++ types (Profile) + // 2. one of our c++winrt types (TermControl) + // 3. one of our types that uses MUX/Xaml (Tab). + // Just creating all of them is enough to know that everything is working. + const auto profileGuid{ Utils::CreateGuid() }; + winrt::Microsoft::Terminal::TerminalControl::TermControl term{}; + VERIFY_IS_NOT_NULL(term); + + auto newTab = std::make_shared(profileGuid, term); + + VERIFY_IS_NOT_NULL(newTab); + } + +} diff --git a/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.AppxManifest.prototype.xml b/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.AppxManifest.prototype.xml new file mode 100644 index 00000000000..c297907488f --- /dev/null +++ b/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.AppxManifest.prototype.xml @@ -0,0 +1,52 @@ + + + + + + + + TerminalApp.LocalTests.Package Host Process + Microsoft Corp. + taef.png + TAEF Packaged Cwa FullTrust Application Host Process + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.manifest b/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.manifest new file mode 100644 index 00000000000..a447bc8fdc2 --- /dev/null +++ b/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.manifest @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + PerMonitorV2 + true + + + diff --git a/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj b/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj new file mode 100644 index 00000000000..554737e67e8 --- /dev/null +++ b/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj @@ -0,0 +1,176 @@ +ο»Ώ + + + + + + + + + + + + + + + Create + + + + NotUsing + + + + + + + + + + + + + + + + + + {CA5CAD1A-b11c-4ddb-a4fe-c3afae9b5506} + Win32Proj + TerminalAppLocalTests + LocalTests_TerminalApp + TerminalApp.LocalTests + 10.0.18362.0 + 10.0.18362.0 + + + + + + ..;$(OpenConsoleDir)\dep\jsoncpp\json;$(OpenConsoleDir)src\inc;$(OpenConsoleDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalApp\lib\Generated Files";%(AdditionalIncludeDirectories) + precomp.h + + + 4702;%(DisableSpecificWarnings) + + + WindowsApp.lib;%(AdditionalDependencies) + + + + + true + true + + + + + + + + + + $(OpenConsoleDir)\bin\$(Platform)\$(Configuration)\$(ProjectName)\ + $(OpenConsoleDir)\obj\$(Platform)\$(Configuration)\$(ProjectName)\ + + + + <_CppWinrtBinRoot>"$(OpenConsoleDir)$(Platform)\$(Configuration)\" + + x86 + $(Platform) + <_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.2.190611001-prerelease\runtimes\win10-$(Native-Platform)\native\" + + + + + + + + + + + + + $(BeforeLinkTargets); + _LocalTestsGenerateCombinedManifests; + _LocalTestsBuildAppxManifest; + _LocalTestsCopyDependencies; + + + + + + + + + + + + + + + + + + + + <_ContinueOnError Condition="'$(BuildingProject)' == 'true'">true + <_ContinueOnError Condition="'$(BuildingProject)' != 'true'">false + + + + + + + + + + + + + + + + + + + + diff --git a/src/cascadia/LocalTests_TerminalApp/precomp.cpp b/src/cascadia/LocalTests_TerminalApp/precomp.cpp new file mode 100644 index 00000000000..6a6fa8e5af1 --- /dev/null +++ b/src/cascadia/LocalTests_TerminalApp/precomp.cpp @@ -0,0 +1,4 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" \ No newline at end of file diff --git a/src/cascadia/LocalTests_TerminalApp/precomp.h b/src/cascadia/LocalTests_TerminalApp/precomp.h new file mode 100644 index 00000000000..5c19aff512f --- /dev/null +++ b/src/cascadia/LocalTests_TerminalApp/precomp.h @@ -0,0 +1,53 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- precomp.h + +Abstract: +- Contains external headers to include in the precompile phase of console build process. +- Avoid including internal project headers. Instead include them only in the classes that need them (helps with test project building). + +Author(s): +- Carlos Zamora (cazamor) April 2019 +--*/ + +#pragma once + +// This includes support libraries from the CRT, STL, WIL, and GSL +#include "LibraryIncludes.h" +// This is inexplicable, but for whatever reason, cppwinrt conflicts with the +// SDK definition of this function, so the only fix is to undef it. +// from WinBase.h +// Windows::UI::Xaml::Media::Animation::IStoryboard::GetCurrentTime +#ifdef GetCurrentTime +#undef GetCurrentTime +#endif + +#include +#include +#include "consoletaeftemplates.hpp" + +// Needed just for XamlIslands to work at all: +#include +#include +#include +#include + +// Common includes for most tests: +#include "../../inc/argb.h" +#include "../../inc/conattrs.hpp" +#include "../../types/inc/utils.hpp" +#include "../../inc/DefaultSettings.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include diff --git a/src/cascadia/TerminalApp/ActionArgs.cpp b/src/cascadia/TerminalApp/ActionArgs.cpp new file mode 100644 index 00000000000..ff3e2298185 --- /dev/null +++ b/src/cascadia/TerminalApp/ActionArgs.cpp @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" + +#include "ActionArgs.h" + +#include "ActionEventArgs.g.cpp" +#include "CopyTextArgs.g.cpp" +#include "NewTabWithProfileArgs.g.cpp" +#include "SwitchToTabArgs.g.cpp" +#include "ResizePaneArgs.g.cpp" +#include "MoveFocusArgs.g.cpp" diff --git a/src/cascadia/TerminalApp/ActionArgs.h b/src/cascadia/TerminalApp/ActionArgs.h new file mode 100644 index 00000000000..3bd56fe0d1b --- /dev/null +++ b/src/cascadia/TerminalApp/ActionArgs.h @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +// HEY YOU: When adding ActionArgs types, make sure to add the corresponding +// *.g.cpp to ActionArgs.cpp! +#include "ActionEventArgs.g.h" +#include "CopyTextArgs.g.h" +#include "NewTabWithProfileArgs.g.h" +#include "SwitchToTabArgs.g.h" +#include "ResizePaneArgs.g.h" +#include "MoveFocusArgs.g.h" + +#include "../../cascadia/inc/cppwinrt_utils.h" + +// Notes on defining ActionArgs and ActionEventArgs: +// * All properties specific to an action should be defined as an ActionArgs +// class that implements IActionArgs +// * ActionEventArgs holds a single IActionArgs. For events that don't need +// additional args, this can be nullptr. + +namespace winrt::TerminalApp::implementation +{ + struct ActionEventArgs : public ActionEventArgsT + { + ActionEventArgs() = default; + ActionEventArgs(const TerminalApp::IActionArgs& args) : + _ActionArgs{ args } {}; + GETSET_PROPERTY(IActionArgs, ActionArgs, nullptr); + GETSET_PROPERTY(bool, Handled, false); + }; + + struct CopyTextArgs : public CopyTextArgsT + { + CopyTextArgs() = default; + GETSET_PROPERTY(bool, TrimWhitespace, false); + }; + + struct NewTabWithProfileArgs : public NewTabWithProfileArgsT + { + NewTabWithProfileArgs() = default; + GETSET_PROPERTY(int32_t, ProfileIndex, 0); + }; + + struct SwitchToTabArgs : public SwitchToTabArgsT + { + SwitchToTabArgs() = default; + GETSET_PROPERTY(int32_t, TabIndex, 0); + }; + + struct ResizePaneArgs : public ResizePaneArgsT + { + ResizePaneArgs() = default; + GETSET_PROPERTY(TerminalApp::Direction, Direction, TerminalApp::Direction::Left); + }; + + struct MoveFocusArgs : public MoveFocusArgsT + { + MoveFocusArgs() = default; + GETSET_PROPERTY(TerminalApp::Direction, Direction, TerminalApp::Direction::Left); + }; + +} + +namespace winrt::TerminalApp::factory_implementation +{ + BASIC_FACTORY(ActionEventArgs); +} diff --git a/src/cascadia/TerminalApp/ActionArgs.idl b/src/cascadia/TerminalApp/ActionArgs.idl new file mode 100644 index 00000000000..58be7ea26b6 --- /dev/null +++ b/src/cascadia/TerminalApp/ActionArgs.idl @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace TerminalApp +{ + // An empty interface must specify an explicit [uuid] to ensure uniqueness. + // We also manually have to specify a "version" attribute to make the compiler happy. + [uuid("191C2BDE-1A60-4BAB-9765-D850F0EF2CAC")][version(1)] interface IActionArgs{}; + + interface IActionEventArgs + { + Boolean Handled; + IActionArgs ActionArgs { get; }; + }; + + enum Direction + { + Left = 0, + Right, + Up, + Down + }; + + [default_interface] runtimeclass ActionEventArgs : IActionEventArgs + { + ActionEventArgs(IActionArgs args); + }; + + [default_interface] runtimeclass CopyTextArgs : IActionArgs + { + Boolean TrimWhitespace { get; }; + }; + + [default_interface] runtimeclass NewTabWithProfileArgs : IActionArgs + { + Int32 ProfileIndex { get; }; + }; + + [default_interface] runtimeclass SwitchToTabArgs : IActionArgs + { + Int32 TabIndex { get; }; + }; + + [default_interface] runtimeclass ResizePaneArgs : IActionArgs + { + Direction Direction { get; }; + }; + + [default_interface] runtimeclass MoveFocusArgs : IActionArgs + { + Direction Direction { get; }; + }; + +} diff --git a/src/cascadia/TerminalApp/App.cpp b/src/cascadia/TerminalApp/App.cpp index 37bbad4640d..dc156bef5c5 100644 --- a/src/cascadia/TerminalApp/App.cpp +++ b/src/cascadia/TerminalApp/App.cpp @@ -6,42 +6,116 @@ #include #include "App.g.cpp" -#include "TerminalPage.h" using namespace winrt::Windows::ApplicationModel::DataTransfer; using namespace winrt::Windows::UI::Xaml; -using namespace winrt::Windows::UI::Text; using namespace winrt::Windows::UI::Core; using namespace winrt::Windows::System; using namespace winrt::Microsoft::Terminal; using namespace winrt::Microsoft::Terminal::Settings; using namespace winrt::Microsoft::Terminal::TerminalControl; -using namespace winrt::Microsoft::Terminal::TerminalConnection; using namespace ::TerminalApp; -// Note: Generate GUID using TlgGuid.exe tool -TRACELOGGING_DEFINE_PROVIDER( - g_hTerminalAppProvider, - "Microsoft.Windows.Terminal.App", - // {24a1622f-7da7-5c77-3303-d850bd1ab2ed} - (0x24a1622f, 0x7da7, 0x5c77, 0x33, 0x03, 0xd8, 0x50, 0xbd, 0x1a, 0xb2, 0xed), - TraceLoggingOptionMicrosoftTelemetry()); - namespace winrt { namespace MUX = Microsoft::UI::Xaml; using IInspectable = Windows::Foundation::IInspectable; } +// clang-format off +// !!! IMPORTANT !!! +// Make sure that these keys are in the same order as the +// SettingsLoadWarnings/Errors enum is! +static const std::array settingsLoadWarningsLabels { + L"MissingDefaultProfileText", + L"DuplicateProfileText" +}; +static const std::array settingsLoadErrorsLabels { + L"NoProfilesText" +}; +// clang-format on + +// Function Description: +// - General-purpose helper for looking up a localized string for a +// warning/error. First will look for the given key in the provided map of +// keys->strings, where the values in the map are ResourceKeys. If it finds +// one, it will lookup the localized string from that ResourceKey. +// - If it does not find a key, it'll return an empty string +// Arguments: +// - key: the value to use to look for a resource key in the given map +// - map: A map of keys->Resource keys. +// - loader: the ScopedResourceLoader to use to look up the localized string. +// Return Value: +// - the localized string for the given type, if it exists. +template +static winrt::hstring _GetMessageText(uint32_t index, std::array keys, ScopedResourceLoader& loader) +{ + if (index < keys.size()) + { + return loader.GetLocalizedString(keys.at(index)); + } + return {}; +} + +// Function Description: +// - Gets the text from our ResourceDictionary for the given +// SettingsLoadWarning. If there is no such text, we'll return nullptr. +// - The warning should have an entry in settingsLoadWarningsLabels. +// Arguments: +// - warning: the SettingsLoadWarnings value to get the localized text for. +// - loader: the ScopedResourceLoader to use to look up the localized string. +// Return Value: +// - localized text for the given warning +static winrt::hstring _GetWarningText(::TerminalApp::SettingsLoadWarnings warning, ScopedResourceLoader& loader) +{ + return _GetMessageText(static_cast(warning), settingsLoadWarningsLabels, loader); +} + +// Function Description: +// - Gets the text from our ResourceDictionary for the given +// SettingsLoadError. If there is no such text, we'll return nullptr. +// - The warning should have an entry in settingsLoadErrorsLabels. +// Arguments: +// - error: the SettingsLoadErrors value to get the localized text for. +// - loader: the ScopedResourceLoader to use to look up the localized string. +// Return Value: +// - localized text for the given error +static winrt::hstring _GetErrorText(::TerminalApp::SettingsLoadErrors error, ScopedResourceLoader& loader) +{ + return _GetMessageText(static_cast(error), settingsLoadErrorsLabels, loader); +} + +// Function Description: +// - Creates a Run of text to display an error message. The text is yellow or +// red for dark/light theme, respectively. +// Arguments: +// - text: The text of the error message. +// - resources: The application's resource loader. +// Return Value: +// - The fully styled text run. +static Documents::Run _BuildErrorRun(const winrt::hstring& text, const ResourceDictionary& resources) +{ + Documents::Run textRun; + textRun.Text(text); + + // Color the text red (light theme) or yellow (dark theme) based on the system theme + winrt::IInspectable key = winrt::box_value(L"ErrorTextBrush"); + if (resources.HasKey(key)) + { + winrt::IInspectable g = resources.Lookup(key); + auto brush = g.try_as(); + textRun.Foreground(brush); + } + + return textRun; +} + namespace winrt::TerminalApp::implementation { App::App() : - _settings{}, - _tabs{}, - _loadedInitialSettings{ false }, - _settingsLoadedResult{ S_OK }, _dialogLock{}, - _resourceLoader{ L"TerminalApp/Resources" } + _loadedInitialSettings{ false }, + _settingsLoadedResult{ S_OK } { // For your own sanity, it's better to do setup outside the ctor. // If you do any setup in the ctor that ends up throwing an exception, @@ -51,6 +125,13 @@ namespace winrt::TerminalApp::implementation // Initialize will become protected or be deleted when GH#1339 (workaround for MSFT:22116519) are fixed. Initialize(); + + _resourceLoader = std::make_shared(L"TerminalApp/Resources"); + + // The TerminalPage has to be constructed during our construction, to + // make sure that there's a terminal page for callers of + // SetTitleBarContent + _root = winrt::make_self(_resourceLoader); } // Method Description: @@ -67,59 +148,22 @@ namespace winrt::TerminalApp::implementation // Assert that we've already loaded our settings. We have to do // this as a MTA, before the app is Create()'d WINRT_ASSERT(_loadedInitialSettings); - TraceLoggingRegister(g_hTerminalAppProvider); - - /* !!! TODO - This is not the correct way to host a XAML page. This exists today because we valued - getting a .xaml over tearing out all of the terminal logic and splitting it across App - and Page. - The work to clarify the boundary between app global state and "terminal page" state - is tracked in GH#1878. - */ - auto terminalPage = winrt::make_self(); - _root = terminalPage.as(); - _tabContent = terminalPage->TabContent(); - _tabRow = terminalPage->TabRow(); - _tabView = _tabRow.TabView(); - _newTabButton = _tabRow.NewTabButton(); - - if (_settings->GlobalSettings().GetShowTabsInTitlebar()) - { - // Remove the TabView from the page. We'll hang on to it, we need to - // put it in the titlebar. - uint32_t index = 0; - if (terminalPage->Root().Children().IndexOf(_tabRow, index)) - { - terminalPage->Root().Children().RemoveAt(index); - } - // Inform the host that our titlebar content has changed. - _setTitleBarContentHandlers(*this, _tabRow); - } + _root->ShowDialog({ this, &App::_ShowDialog }); - // Event Bindings (Early) - _newTabButton.Click([this](auto&&, auto&&) { - this->_OpenNewTab(std::nullopt); - }); - _tabView.SelectionChanged({ this, &App::_OnTabSelectionChanged }); - _tabView.TabClosing({ this, &App::_OnTabClosing }); - _tabView.Items().VectorChanged({ this, &App::_OnTabItemsChanged }); - _root.Loaded({ this, &App::_OnLoaded }); - - _CreateNewTabFlyout(); - _OpenNewTab(std::nullopt); - - _tabContent.SizeChanged({ this, &App::_OnContentSizeChanged }); + _root->SetSettings(_settings, false); + _root->Loaded({ this, &App::_OnLoaded }); + _root->Create(); _ApplyTheme(_settings->GlobalSettings().GetRequestedTheme()); - } - App::~App() - { - if (g_hTerminalAppProvider) - { - TraceLoggingUnregister(g_hTerminalAppProvider); - } + TraceLoggingWrite( + g_hTerminalAppProvider, + "AppCreated", + TraceLoggingDescription("Event emitted when the application is started"), + TraceLoggingBool(_settings->GlobalSettings().GetShowTabsInTitlebar(), "TabsInTitlebar"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); } // Method Description: @@ -129,12 +173,9 @@ namespace winrt::TerminalApp::implementation // - Only one dialog can be visible at a time. If another dialog is visible // when this is called, nothing happens. // Arguments: - // - titleElement: the element to use as the title of this ContentDialog - // - contentElement: the element to use as the content of this ContentDialog - // - closeButtonText: The string to use on the close button - fire_and_forget App::_ShowDialog(const IInspectable& titleElement, - const IInspectable& contentElement, - const winrt::hstring& closeButtonText) + // sender: unused + // dialog: the dialog object that is going to show up + fire_and_forget App::_ShowDialog(const winrt::Windows::Foundation::IInspectable& sender, winrt::Windows::UI::Xaml::Controls::ContentDialog dialog) { // DON'T release this lock in a wil::scope_exit. The scope_exit will get // called when we await, which is not what we want. @@ -145,15 +186,10 @@ namespace winrt::TerminalApp::implementation return; } - Controls::ContentDialog dialog; - dialog.Title(titleElement); - dialog.Content(contentElement); - dialog.CloseButtonText(closeButtonText); - // IMPORTANT: This is necessary as documented in the ContentDialog MSDN docs. // Since we're hosting the dialog in a Xaml island, we need to connect it to the // xaml tree somehow. - dialog.XamlRoot(_root.XamlRoot()); + dialog.XamlRoot(_root->XamlRoot()); // IMPORTANT: Set the requested theme of the dialog, because the // PopupRoot isn't directly in the Xaml tree of our root. So the dialog @@ -164,94 +200,93 @@ namespace winrt::TerminalApp::implementation Controls::ContentDialogResult result = co_await dialog.ShowAsync(Controls::ContentDialogPlacement::Popup); // After the dialog is dismissed, the dialog lock (held by `lock`) will - // be released so another can be shown. + // be released so another can be shown } // Method Description: - // - Show a ContentDialog with a single "Ok" button to dismiss. Looks up the - // the title and text from our Resources using the provided keys. + // - Displays a dialog for errors found while loading or validating the + // settings. Uses the resources under the provided title and content keys + // as the title and first content of the dialog, then also displays a + // message for whatever exception was found while validating the settings. // - Only one dialog can be visible at a time. If another dialog is visible // when this is called, nothing happens. See _ShowDialog for details // Arguments: // - titleKey: The key to use to lookup the title text from our resources. // - contentKey: The key to use to lookup the content text from our resources. - void App::_ShowOkDialog(const winrt::hstring& titleKey, - const winrt::hstring& contentKey) + void App::_ShowLoadErrorsDialog(const winrt::hstring& titleKey, + const winrt::hstring& contentKey, + HRESULT settingsLoadedResult) { - auto title = _resourceLoader.GetLocalizedString(titleKey); - auto message = _resourceLoader.GetLocalizedString(contentKey); - auto buttonText = _resourceLoader.GetLocalizedString(L"Ok"); + auto title = _resourceLoader->GetLocalizedString(titleKey); + auto buttonText = _resourceLoader->GetLocalizedString(L"Ok"); - _ShowDialog(winrt::box_value(title), winrt::box_value(message), buttonText); + Controls::TextBlock warningsTextBlock; + // Make sure you can copy-paste + warningsTextBlock.IsTextSelectionEnabled(true); + // Make sure the lines of text wrap + warningsTextBlock.TextWrapping(TextWrapping::Wrap); + + winrt::Windows::UI::Xaml::Documents::Run errorRun; + const auto errorLabel = _resourceLoader->GetLocalizedString(contentKey); + errorRun.Text(errorLabel); + warningsTextBlock.Inlines().Append(errorRun); + + if (FAILED(settingsLoadedResult)) + { + if (!_settingsLoadExceptionText.empty()) + { + warningsTextBlock.Inlines().Append(_BuildErrorRun(_settingsLoadExceptionText, Resources())); + } + } + + // Add a note that we're using the default settings in this case. + winrt::Windows::UI::Xaml::Documents::Run usingDefaultsRun; + const auto usingDefaultsText = _resourceLoader->GetLocalizedString(L"UsingDefaultSettingsText"); + usingDefaultsRun.Text(usingDefaultsText); + warningsTextBlock.Inlines().Append(usingDefaultsRun); + + Controls::ContentDialog dialog; + dialog.Title(winrt::box_value(title)); + dialog.Content(winrt::box_value(warningsTextBlock)); + dialog.CloseButtonText(buttonText); + + _ShowDialog(nullptr, dialog); } // Method Description: - // - Show a dialog with "About" information. Displays the app's Display - // Name, version, getting started link, documentation link, and release - // Notes link. - void App::_ShowAboutDialog() + // - Displays a dialog for warnings found while loading or validating the + // settings. Displays messages for whatever warnings were found while + // validating the settings. + // - Only one dialog can be visible at a time. If another dialog is visible + // when this is called, nothing happens. See _ShowDialog for details + void App::_ShowLoadWarningsDialog() { - const auto title = _resourceLoader.GetLocalizedString(L"AboutTitleText"); - const auto versionLabel = _resourceLoader.GetLocalizedString(L"VersionLabelText"); - const auto gettingStartedLabel = _resourceLoader.GetLocalizedString(L"GettingStartedLabelText"); - const auto documentationLabel = _resourceLoader.GetLocalizedString(L"DocumentationLabelText"); - const auto releaseNotesLabel = _resourceLoader.GetLocalizedString(L"ReleaseNotesLabelText"); - const auto gettingStartedUriValue = _resourceLoader.GetLocalizedString(L"GettingStartedUriValue"); - const auto documentationUriValue = _resourceLoader.GetLocalizedString(L"DocumentationUriValue"); - const auto releaseNotesUriValue = _resourceLoader.GetLocalizedString(L"ReleaseNotesUriValue"); - const auto package = winrt::Windows::ApplicationModel::Package::Current(); - const auto packageName = package.DisplayName(); - const auto version = package.Id().Version(); - winrt::Windows::UI::Xaml::Documents::Run about; - winrt::Windows::UI::Xaml::Documents::Run gettingStarted; - winrt::Windows::UI::Xaml::Documents::Run documentation; - winrt::Windows::UI::Xaml::Documents::Run releaseNotes; - winrt::Windows::UI::Xaml::Documents::Hyperlink gettingStartedLink; - winrt::Windows::UI::Xaml::Documents::Hyperlink documentationLink; - winrt::Windows::UI::Xaml::Documents::Hyperlink releaseNotesLink; - std::wstringstream aboutTextStream; - - gettingStarted.Text(gettingStartedLabel); - documentation.Text(documentationLabel); - releaseNotes.Text(releaseNotesLabel); - - winrt::Windows::Foundation::Uri gettingStartedUri{ gettingStartedUriValue }; - winrt::Windows::Foundation::Uri documentationUri{ documentationUriValue }; - winrt::Windows::Foundation::Uri releaseNotesUri{ releaseNotesUriValue }; - - gettingStartedLink.NavigateUri(gettingStartedUri); - documentationLink.NavigateUri(documentationUri); - releaseNotesLink.NavigateUri(releaseNotesUri); - - gettingStartedLink.Inlines().Append(gettingStarted); - documentationLink.Inlines().Append(documentation); - releaseNotesLink.Inlines().Append(releaseNotes); - - // Format our about text. It will look like the following: - // - // Version: ... - // Getting Started - // Documentation - // Release Notes - - aboutTextStream << packageName.c_str() << L"\n"; - - aboutTextStream << versionLabel.c_str() << L" "; - aboutTextStream << version.Major << L"." << version.Minor << L"." << version.Build << L"." << version.Revision << L"\n"; - - winrt::hstring aboutText{ aboutTextStream.str() }; - about.Text(aboutText); - - const auto buttonText = _resourceLoader.GetLocalizedString(L"Ok"); - - Controls::TextBlock aboutTextBlock; - aboutTextBlock.Inlines().Append(about); - aboutTextBlock.Inlines().Append(gettingStartedLink); - aboutTextBlock.Inlines().Append(documentationLink); - aboutTextBlock.Inlines().Append(releaseNotesLink); - aboutTextBlock.IsTextSelectionEnabled(true); - - _ShowDialog(winrt::box_value(title), aboutTextBlock, buttonText); + auto title = _resourceLoader->GetLocalizedString(L"SettingsValidateErrorTitle"); + auto buttonText = _resourceLoader->GetLocalizedString(L"Ok"); + + Controls::TextBlock warningsTextBlock; + // Make sure you can copy-paste + warningsTextBlock.IsTextSelectionEnabled(true); + // Make sure the lines of text wrap + warningsTextBlock.TextWrapping(TextWrapping::Wrap); + + const auto& warnings = _settings->GetWarnings(); + for (const auto& warning : warnings) + { + // Try looking up the warning message key for each warning. + const auto warningText = _GetWarningText(warning, *_resourceLoader); + if (!warningText.empty()) + { + warningsTextBlock.Inlines().Append(_BuildErrorRun(warningText, Resources())); + } + } + + Controls::ContentDialog dialog; + dialog.Title(winrt::box_value(title)); + dialog.Content(winrt::box_value(warningsTextBlock)); + dialog.CloseButtonText(buttonText); + + _ShowDialog(nullptr, dialog); } // Method Description: @@ -269,7 +304,11 @@ namespace winrt::TerminalApp::implementation { const winrt::hstring titleKey = L"InitialJsonParseErrorTitle"; const winrt::hstring textKey = L"InitialJsonParseErrorText"; - _ShowOkDialog(titleKey, textKey); + _ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult); + } + else if (_settingsLoadedResult == S_FALSE) + { + _ShowLoadWarningsDialog(); } } @@ -310,222 +349,32 @@ namespace winrt::TerminalApp::implementation return _settings->GlobalSettings().GetShowTabsInTitlebar(); } - // Method Description: - // - Builds the flyout (dropdown) attached to the new tab button, and - // attaches it to the button. Populates the flyout with one entry per - // Profile, displaying the profile's name. Clicking each flyout item will - // open a new tab with that profile. - // Below the profiles are the static menu items: settings, feedback - void App::_CreateNewTabFlyout() - { - auto newTabFlyout = Controls::MenuFlyout{}; - auto keyBindings = _settings->GetKeybindings(); - - const GUID defaultProfileGuid = _settings->GlobalSettings().GetDefaultProfile(); - for (int profileIndex = 0; profileIndex < _settings->GetProfiles().size(); profileIndex++) - { - const auto& profile = _settings->GetProfiles()[profileIndex]; - auto profileMenuItem = Controls::MenuFlyoutItem{}; - - // add the keyboard shortcuts for the first 9 profiles - if (profileIndex < 9) - { - // enum value for ShortcutAction::NewTabProfileX; 0==NewTabProfile0 - auto profileKeyChord = keyBindings.GetKeyBinding(static_cast(profileIndex + static_cast(ShortcutAction::NewTabProfile0))); - - // make sure we find one to display - if (profileKeyChord) - { - _SetAcceleratorForMenuItem(profileMenuItem, profileKeyChord); - } - } - - auto profileName = profile.GetName(); - winrt::hstring hName{ profileName }; - profileMenuItem.Text(hName); - - // If there's an icon set for this profile, set it as the icon for - // this flyout item. - if (profile.HasIcon()) - { - profileMenuItem.Icon(_GetIconFromProfile(profile)); - } - - if (profile.GetGuid() == defaultProfileGuid) - { - // Contrast the default profile with others in font weight. - profileMenuItem.FontWeight(FontWeights::Bold()); - } - - profileMenuItem.Click([this, profileIndex](auto&&, auto&&) { - this->_OpenNewTab({ profileIndex }); - }); - newTabFlyout.Items().Append(profileMenuItem); - } - - // add menu separator - auto separatorItem = Controls::MenuFlyoutSeparator{}; - newTabFlyout.Items().Append(separatorItem); - - // add static items - { - // Create the settings button. - auto settingsItem = Controls::MenuFlyoutItem{}; - settingsItem.Text(_resourceLoader.GetLocalizedString(L"SettingsMenuItem")); - - Controls::SymbolIcon ico{}; - ico.Symbol(Controls::Symbol::Setting); - settingsItem.Icon(ico); - - settingsItem.Click({ this, &App::_SettingsButtonOnClick }); - newTabFlyout.Items().Append(settingsItem); - - auto settingsKeyChord = keyBindings.GetKeyBinding(ShortcutAction::OpenSettings); - if (settingsKeyChord) - { - _SetAcceleratorForMenuItem(settingsItem, settingsKeyChord); - } - - // Create the feedback button. - auto feedbackFlyout = Controls::MenuFlyoutItem{}; - feedbackFlyout.Text(_resourceLoader.GetLocalizedString(L"FeedbackMenuItem")); - - Controls::FontIcon feedbackIco{}; - feedbackIco.Glyph(L"\xE939"); - feedbackIco.FontFamily(Media::FontFamily{ L"Segoe MDL2 Assets" }); - feedbackFlyout.Icon(feedbackIco); - - feedbackFlyout.Click({ this, &App::_FeedbackButtonOnClick }); - newTabFlyout.Items().Append(feedbackFlyout); - - // Create the about button. - auto aboutFlyout = Controls::MenuFlyoutItem{}; - aboutFlyout.Text(_resourceLoader.GetLocalizedString(L"AboutMenuItem")); - - Controls::SymbolIcon aboutIco{}; - aboutIco.Symbol(Controls::Symbol::Help); - aboutFlyout.Icon(aboutIco); - - aboutFlyout.Click({ this, &App::_AboutButtonOnClick }); - newTabFlyout.Items().Append(aboutFlyout); - } - - _newTabButton.Flyout(newTabFlyout); - } - - // Function Description: - // - Called when the settings button is clicked. ShellExecutes the settings - // file, as to open it in the default editor for .json files. Does this in - // a background thread, as to not hang/crash the UI thread. - fire_and_forget LaunchSettings() - { - // This will switch the execution of the function to a background (not - // UI) thread. This is IMPORTANT, because the Windows.Storage API's - // (used for retrieving the path to the file) will crash on the UI - // thread, because the main thread is a STA. - co_await winrt::resume_background(); - - const auto settingsPath = CascadiaSettings::GetSettingsPath(); - - HINSTANCE res = ShellExecute(nullptr, nullptr, settingsPath.c_str(), nullptr, nullptr, SW_SHOW); - if (static_cast(reinterpret_cast(res)) <= 32) - { - ShellExecute(nullptr, nullptr, L"notepad", settingsPath.c_str(), nullptr, SW_SHOW); - } - } - - // Method Description: - // - Called when the settings button is clicked. Launches a background - // thread to open the settings file in the default JSON editor. - // Arguments: - // - - // Return Value: - // - - void App::_SettingsButtonOnClick(const IInspectable&, - const RoutedEventArgs&) - { - LaunchSettings(); - } - - // Method Description: - // - Called when the feedback button is clicked. Launches github in your - // default browser, navigated to the "issues" page of the Terminal repo. - void App::_FeedbackButtonOnClick(const IInspectable&, - const RoutedEventArgs&) - { - const auto feedbackUriValue = _resourceLoader.GetLocalizedString(L"FeedbackUriValue"); - - winrt::Windows::System::Launcher::LaunchUriAsync({ feedbackUriValue }); - } - - // Method Description: - // - Called when the about button is clicked. See _ShowAboutDialog for more info. - // Arguments: - // - - // Return Value: - // - - void App::_AboutButtonOnClick(const IInspectable&, - const RoutedEventArgs&) - { - _ShowAboutDialog(); - } - - // Method Description: - // - Register our event handlers with the given keybindings object. This - // should be done regardless of what the events are actually bound to - - // this simply ensures the AppKeyBindings object will call us correctly - // for each event. - // Arguments: - // - bindings: A AppKeyBindings object to wire up with our event handlers - void App::_HookupKeyBindings(TerminalApp::AppKeyBindings bindings) noexcept - { - // Hook up the KeyBinding object's events to our handlers. - // They should all be hooked up here, regardless of whether or not - // there's an actual keychord for them. - bindings.NewTab([this]() { _OpenNewTab(std::nullopt); }); - bindings.DuplicateTab([this]() { _DuplicateTabViewItem(); }); - bindings.CloseTab([this]() { _CloseFocusedTab(); }); - bindings.ClosePane([this]() { _CloseFocusedPane(); }); - bindings.NewTabWithProfile([this](const auto index) { _OpenNewTab({ index }); }); - bindings.ScrollUp([this]() { _Scroll(-1); }); - bindings.ScrollDown([this]() { _Scroll(1); }); - bindings.NextTab([this]() { _SelectNextTab(true); }); - bindings.PrevTab([this]() { _SelectNextTab(false); }); - bindings.SplitVertical([this]() { _SplitVertical(std::nullopt); }); - bindings.SplitHorizontal([this]() { _SplitHorizontal(std::nullopt); }); - bindings.ScrollUpPage([this]() { _ScrollPage(-1); }); - bindings.ScrollDownPage([this]() { _ScrollPage(1); }); - bindings.SwitchToTab([this](const auto index) { _SelectTab({ index }); }); - bindings.OpenSettings([this]() { _OpenSettings(); }); - bindings.ResizePane([this](const auto direction) { _ResizePane(direction); }); - bindings.MoveFocus([this](const auto direction) { _MoveFocus(direction); }); - bindings.CopyText([this](const auto trimWhitespace) { _CopyText(trimWhitespace); }); - bindings.PasteText([this]() { _PasteText(); }); - } - // Method Description: // - Attempt to load the settings. If we fail for any reason, returns an error. - // Arguments: - // - saveOnLoad: If true, after loading the settings, we should re-write - // them to the file, to make sure the schema is updated. See - // `CascadiaSettings::LoadAll` for details. // Return Value: // - S_OK if we successfully parsed the settings, otherwise an appropriate HRESULT. - [[nodiscard]] HRESULT App::_TryLoadSettings(const bool saveOnLoad) noexcept + [[nodiscard]] HRESULT App::_TryLoadSettings() noexcept { HRESULT hr = E_FAIL; try { - auto newSettings = CascadiaSettings::LoadAll(saveOnLoad); + auto newSettings = CascadiaSettings::LoadAll(); _settings = std::move(newSettings); - hr = S_OK; + const auto& warnings = _settings->GetWarnings(); + hr = warnings.size() == 0 ? S_OK : S_FALSE; } catch (const winrt::hresult_error& e) { hr = e.code(); + _settingsLoadExceptionText = e.message(); LOG_HR(hr); } + catch (const ::TerminalApp::SettingsException& ex) + { + hr = E_INVALIDARG; + _settingsLoadExceptionText = _GetErrorText(ex.Error(), *_resourceLoader); + } catch (...) { hr = wil::ResultFromCaughtException(); @@ -543,6 +392,15 @@ namespace winrt::TerminalApp::implementation // happening during startup, it'll need to happen on a background thread. void App::LoadSettings() { + auto start = std::chrono::high_resolution_clock::now(); + + TraceLoggingWrite( + g_hTerminalAppProvider, + "SettingsLoadStarted", + TraceLoggingDescription("Event emitted before loading the settings"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); + // Attempt to load the settings. // If it fails, // - use Default settings, @@ -551,7 +409,7 @@ namespace winrt::TerminalApp::implementation // we should display the loading error. // * We can't display the error now, because we might not have a // UI yet. We'll display the error in _OnLoaded. - _settingsLoadedResult = _TryLoadSettings(true); + _settingsLoadedResult = _TryLoadSettings(); if (FAILED(_settingsLoadedResult)) { @@ -559,7 +417,16 @@ namespace winrt::TerminalApp::implementation _settings->CreateDefaults(); } - _HookupKeyBindings(_settings->GetKeybindings()); + auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration delta = end - start; + + TraceLoggingWrite( + g_hTerminalAppProvider, + "SettingsLoadComplete", + TraceLoggingDescription("Event emitted when loading the settings is finished"), + TraceLoggingFloat64(delta.count(), "Duration"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); _loadedInitialSettings = true; @@ -631,113 +498,37 @@ namespace winrt::TerminalApp::implementation // - don't change the settings (and don't actually apply the new settings) // - don't persist them. // - display a loading error - _settingsLoadedResult = _TryLoadSettings(false); + _settingsLoadedResult = _TryLoadSettings(); if (FAILED(_settingsLoadedResult)) { - _root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() { + _root->Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() { const winrt::hstring titleKey = L"ReloadJsonParseErrorTitle"; const winrt::hstring textKey = L"ReloadJsonParseErrorText"; - _ShowOkDialog(titleKey, textKey); + _ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult); }); return; } + else if (_settingsLoadedResult == S_FALSE) + { + _root->Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() { + _ShowLoadWarningsDialog(); + }); + } // Here, we successfully reloaded the settings, and created a new // TerminalSettings object. - // Re-wire the keybindings to their handlers, as we'll have created a - // new AppKeyBindings object. - _HookupKeyBindings(_settings->GetKeybindings()); - - // Refresh UI elements - - auto profiles = _settings->GetProfiles(); - for (auto& profile : profiles) - { - const GUID profileGuid = profile.GetGuid(); - TerminalSettings settings = _settings->MakeSettings(profileGuid); - - for (auto& tab : _tabs) - { - // Attempt to reload the settings of any panes with this profile - tab->UpdateSettings(settings, profileGuid); - } - } + // Update the settings in TerminalPage + _root->SetSettings(_settings, true); - // Update the icon of the tab for the currently focused profile in that tab. - for (auto& tab : _tabs) - { - _UpdateTabIcon(tab); - _UpdateTitle(tab); - } - - _root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() { + _root->Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() { // Refresh the UI theme _ApplyTheme(_settings->GlobalSettings().GetRequestedTheme()); - - // repopulate the new tab button's flyout with entries for each - // profile, which might have changed - _CreateNewTabFlyout(); }); } - // Method Description: - // - Get the icon of the currently focused terminal control, and set its - // tab's icon to that icon. - // Arguments: - // - tab: the Tab to update the title for. - void App::_UpdateTabIcon(std::shared_ptr tab) - { - const auto lastFocusedProfileOpt = tab->GetFocusedProfile(); - if (lastFocusedProfileOpt.has_value()) - { - const auto lastFocusedProfile = lastFocusedProfileOpt.value(); - - auto tabViewItem = tab->GetTabViewItem(); - tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this, lastFocusedProfile, tabViewItem]() { - // _GetIconFromProfile has to run on the main thread - const auto* const matchingProfile = _settings->FindProfile(lastFocusedProfile); - if (matchingProfile) - { - tabViewItem.Icon(App::_GetIconFromProfile(*matchingProfile)); - } - }); - } - } - - // Method Description: - // - Get the title of the currently focused terminal control, and set it's - // tab's text to that text. If this tab is the focused tab, then also - // bubble this title to any listeners of our TitleChanged event. - // Arguments: - // - tab: the Tab to update the title for. - void App::_UpdateTitle(std::shared_ptr tab) - { - auto newTabTitle = tab->GetFocusedTitle(); - const auto lastFocusedProfileOpt = tab->GetFocusedProfile(); - if (lastFocusedProfileOpt.has_value()) - { - const auto lastFocusedProfile = lastFocusedProfileOpt.value(); - const auto* const matchingProfile = _settings->FindProfile(lastFocusedProfile); - - const auto tabTitle = matchingProfile->GetTabTitle(); - - // Checks if tab title has been set in the profile settings and - // updates accordingly. - - const auto newActualTitle = tabTitle.empty() ? newTabTitle : tabTitle; - - tab->SetTabText(winrt::to_hstring(newActualTitle.data())); - if (_settings->GlobalSettings().GetShowTitleInTitlebar() && - tab->IsFocused()) - { - _titleChangeHandlers(newActualTitle); - } - } - } - // Method Description: // - Update the current theme of the application. This will trigger our // RequestedThemeChanged event, to have our host change the theme of the @@ -752,408 +543,7 @@ namespace winrt::TerminalApp::implementation UIElement App::GetRoot() noexcept { - return _root; - } - - void App::_SetFocusedTabIndex(int tabIndex) - { - // GH#1117: This is a workaround because _tabView.SelectedIndex(tabIndex) - // sometimes set focus to an incorrect tab after removing some tabs - auto tab = _tabs.at(tabIndex); - _tabView.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [tab, this]() { - auto tabViewItem = tab->GetTabViewItem(); - _tabView.SelectedItem(tabViewItem); - }); - } - - // Method Description: - // - Handle changes in tab layout. - void App::_UpdateTabView() - { - // Show tabs when there's more than 1, or the user has chosen to always - // show the tab bar. - const bool isVisible = _settings->GlobalSettings().GetShowTabsInTitlebar() || - (_tabs.size() > 1) || - _settings->GlobalSettings().GetAlwaysShowTabs(); - - // collapse/show the tabs themselves - _tabView.Visibility(isVisible ? Visibility::Visible : Visibility::Collapsed); - - // collapse/show the row that the tabs are in. - // NaN is the special value XAML uses for "Auto" sizing. - _tabRow.Height(isVisible ? NAN : 0); - } - - // Method Description: - // - Open a new tab. This will create the TerminalControl hosting the - // terminal, and add a new Tab to our list of tabs. The method can - // optionally be provided a profile index, which will be used to create - // a tab using the profile in that index. - // If no index is provided, the default profile will be used. - // Arguments: - // - profileIndex: an optional index into the list of profiles to use to - // initialize this tab up with. - void App::_OpenNewTab(std::optional profileIndex) - { - GUID profileGuid; - - if (profileIndex) - { - const auto realIndex = profileIndex.value(); - const auto profiles = _settings->GetProfiles(); - - // If we don't have that many profiles, then do nothing. - if (realIndex >= profiles.size()) - { - return; - } - - const auto& selectedProfile = profiles[realIndex]; - profileGuid = selectedProfile.GetGuid(); - } - else - { - // Getting Guid for default profile - const auto globalSettings = _settings->GlobalSettings(); - profileGuid = globalSettings.GetDefaultProfile(); - } - - TerminalSettings settings = _settings->MakeSettings(profileGuid); - _CreateNewTabFromSettings(profileGuid, settings); - - const int tabCount = static_cast(_tabs.size()); - TraceLoggingWrite( - g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider - "TabInformation", - TraceLoggingDescription("Event emitted upon new tab creation in TerminalApp"), - TraceLoggingInt32(tabCount, "TabCount", "Count of tabs curently opened in TerminalApp"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); - } - - // Function Description: - // - Copies and processes the text data from the Windows Clipboard. - // Does some of this in a background thread, as to not hang/crash the UI thread. - // Arguments: - // - eventArgs: the PasteFromClipboard event sent from the TermControl - fire_and_forget PasteFromClipboard(PasteFromClipboardEventArgs eventArgs) - { - const DataPackageView data = Clipboard::GetContent(); - - // This will switch the execution of the function to a background (not - // UI) thread. This is IMPORTANT, because the getting the clipboard data - // will crash on the UI thread, because the main thread is a STA. - co_await winrt::resume_background(); - - hstring text = L""; - if (data.Contains(StandardDataFormats::Text())) - { - text = co_await data.GetTextAsync(); - } - eventArgs.HandleClipboardData(text); - } - - // Method Description: - // - Connects event handlers to the TermControl for events that we want to - // handle. This includes: - // * the Copy and Paste events, for setting and retrieving clipboard data - // on the right thread - // * the TitleChanged event, for changing the text of the tab - // * the GotFocus event, for changing the title/icon in the tab when a new - // control is focused - // Arguments: - // - term: The newly created TermControl to connect the events for - // - hostingTab: The Tab that's hosting this TermControl instance - void App::_RegisterTerminalEvents(TermControl term, std::shared_ptr hostingTab) - { - // Add an event handler when the terminal's selection wants to be copied. - // When the text buffer data is retrieved, we'll copy the data into the Clipboard - term.CopyToClipboard({ this, &App::_CopyToClipboardHandler }); - - // Add an event handler when the terminal wants to paste data from the Clipboard. - term.PasteFromClipboard({ this, &App::_PasteFromClipboardHandler }); - - // Don't capture a strong ref to the tab. If the tab is removed as this - // is called, we don't really care anymore about handling the event. - std::weak_ptr weakTabPtr = hostingTab; - term.TitleChanged([this, weakTabPtr](auto newTitle) { - auto tab = weakTabPtr.lock(); - if (!tab) - { - return; - } - // The title of the control changed, but not necessarily the title - // of the tab. Get the title of the focused pane of the tab, and set - // the tab's text to the focused panes' text. - _UpdateTitle(tab); - }); - - term.GetControl().GotFocus([this, weakTabPtr](auto&&, auto&&) { - auto tab = weakTabPtr.lock(); - if (!tab) - { - return; - } - // Update the focus of the tab's panes - tab->UpdateFocus(); - - // Possibly update the title of the tab, window to match the newly - // focused pane. - _UpdateTitle(tab); - - // Possibly update the icon of the tab. - _UpdateTabIcon(tab); - }); - } - - // Method Description: - // - Creates a new tab with the given settings. If the tab bar is not being - // currently displayed, it will be shown. - // Arguments: - // - settings: the TerminalSettings object to use to create the TerminalControl with. - void App::_CreateNewTabFromSettings(GUID profileGuid, TerminalSettings settings) - { - // Initialize the new tab - - // Create a connection based on the values in our settings object. - const auto connection = _CreateConnectionFromSettings(profileGuid, settings); - - TermControl term{ settings, connection }; - - // Add the new tab to the list of our tabs. - auto newTab = _tabs.emplace_back(std::make_shared(profileGuid, term)); - - const auto* const profile = _settings->FindProfile(profileGuid); - - // Hookup our event handlers to the new terminal - _RegisterTerminalEvents(term, newTab); - - auto tabViewItem = newTab->GetTabViewItem(); - _tabView.Items().Append(tabViewItem); - - // Set this profile's tab to the icon the user specified - if (profile != nullptr && profile->HasIcon()) - { - tabViewItem.Icon(_GetIconFromProfile(*profile)); - } - - tabViewItem.PointerPressed({ this, &App::_OnTabClick }); - - // When the tab is closed, remove it from our list of tabs. - newTab->Closed([tabViewItem, this]() { - _tabView.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [tabViewItem, this]() { - _RemoveTabViewItem(tabViewItem); - }); - }); - - // This is one way to set the tab's selected background color. - // tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), a Brush?); - - // This kicks off TabView::SelectionChanged, in response to which we'll attach the terminal's - // Xaml control to the Xaml root. - _tabView.SelectedItem(tabViewItem); - } - - // Method Description: - // - Returns the index in our list of tabs of the currently focused tab. If - // no tab is currently selected, returns -1. - // Return Value: - // - the index of the currently focused tab if there is one, else -1 - int App::_GetFocusedTabIndex() const - { - // GH#1117: This is a workaround because _tabView.SelectedIndex() - // sometimes return incorrect result after removing some tabs - uint32_t focusedIndex; - if (_tabView.Items().IndexOf(_tabView.SelectedItem(), focusedIndex)) - { - return focusedIndex; - } - return -1; - } - - void App::_OpenSettings() - { - LaunchSettings(); - } - - // Method Description: - // - Close the currently focused tab. Focus will move to the left, if possible. - void App::_CloseFocusedTab() - { - int focusedTabIndex = _GetFocusedTabIndex(); - std::shared_ptr focusedTab{ _tabs[focusedTabIndex] }; - _RemoveTabViewItem(focusedTab->GetTabViewItem()); - } - - // Method Description: - // - Close the currently focused pane. If the pane is the last pane in the - // tab, the tab will also be closed. This will happen when we handle the - // tab's Closed event. - void App::_CloseFocusedPane() - { - int focusedTabIndex = _GetFocusedTabIndex(); - std::shared_ptr focusedTab{ _tabs[focusedTabIndex] }; - focusedTab->ClosePane(); - } - - // Method Description: - // - Move the viewport of the terminal of the currently focused tab up or - // down a number of lines. Negative values of `delta` will move the - // view up, and positive values will move the viewport down. - // Arguments: - // - delta: a number of lines to move the viewport relative to the current viewport. - void App::_Scroll(int delta) - { - int focusedTabIndex = _GetFocusedTabIndex(); - _tabs[focusedTabIndex]->Scroll(delta); - } - - // Method Description: - // - Move the viewport of the terminal of the currently focused tab up or - // down a page. The page length will be dependent on the terminal view height. - // Negative values of `delta` will move the view up by one page, and positive values - // will move the viewport down by one page. - // Arguments: - // - delta: The direction to move the view relative to the current viewport(it - // is clamped between -1 and 1) - void App::_ScrollPage(int delta) - { - delta = std::clamp(delta, -1, 1); - const auto focusedTabIndex = _GetFocusedTabIndex(); - const auto control = _GetFocusedControl(); - const auto termHeight = control.GetViewHeight(); - _tabs[focusedTabIndex]->Scroll(termHeight * delta); - } - - // Method Description: - // - Attempt to move a separator between panes, as to resize each child on - // either size of the separator. See Pane::ResizePane for details. - // - Moves a separator on the currently focused tab. - // Arguments: - // - direction: The direction to move the separator in. - // Return Value: - // - - void App::_ResizePane(const Direction& direction) - { - const auto focusedTabIndex = _GetFocusedTabIndex(); - _tabs[focusedTabIndex]->ResizePane(direction); - } - - // Method Description: - // - Attempt to move focus between panes, as to focus the child on - // the other side of the separator. See Pane::NavigateFocus for details. - // - Moves the focus of the currently focused tab. - // Arguments: - // - direction: The direction to move the focus in. - // Return Value: - // - - void App::_MoveFocus(const Direction& direction) - { - const auto focusedTabIndex = _GetFocusedTabIndex(); - _tabs[focusedTabIndex]->NavigateFocus(direction); - } - - // Method Description: - // - Copy text from the focused terminal to the Windows Clipboard - // Arguments: - // - trimTrailingWhitespace: enable removing any whitespace from copied selection - // and get text to appear on separate lines. - void App::_CopyText(const bool trimTrailingWhitespace) - { - const auto control = _GetFocusedControl(); - control.CopySelectionToClipboard(trimTrailingWhitespace); - } - - // Method Description: - // - Paste text from the Windows Clipboard to the focused terminal - void App::_PasteText() - { - const auto control = _GetFocusedControl(); - control.PasteTextFromClipboard(); - } - - // Method Description: - // - Sets focus to the tab to the right or left the currently selected tab. - void App::_SelectNextTab(const bool bMoveRight) - { - int focusedTabIndex = _GetFocusedTabIndex(); - auto tabCount = _tabs.size(); - // Wraparound math. By adding tabCount and then calculating modulo tabCount, - // we clamp the values to the range [0, tabCount) while still supporting moving - // leftward from 0 to tabCount - 1. - _SetFocusedTabIndex( - static_cast((tabCount + focusedTabIndex + (bMoveRight ? 1 : -1)) % tabCount)); - } - - // Method Description: - // - Sets focus to the desired tab. - void App::_SelectTab(const int tabIndex) - { - if (tabIndex >= 0 && tabIndex < _tabs.size()) - { - _SetFocusedTabIndex(tabIndex); - } - } - - // Method Description: - // - Responds to the TabView control's Selection Changed event (to move a - // new terminal control into focus.) - // Arguments: - // - sender: the control that originated this event - // - eventArgs: the event's constituent arguments - void App::_OnTabSelectionChanged(const IInspectable& sender, const Controls::SelectionChangedEventArgs& eventArgs) - { - auto tabView = sender.as(); - auto selectedIndex = tabView.SelectedIndex(); - - // Unfocus all the tabs. - for (auto tab : _tabs) - { - tab->SetFocused(false); - } - - if (selectedIndex >= 0) - { - try - { - auto tab = _tabs.at(selectedIndex); - - _tabContent.Children().Clear(); - _tabContent.Children().Append(tab->GetRootElement()); - - tab->SetFocused(true); - _titleChangeHandlers(GetTitle()); - } - CATCH_LOG(); - } - } - - // Method Description: - // - Responds to the TabView control's Tab Closing event by removing - // the indicated tab from the set and focusing another one. - // The event is cancelled so App maintains control over the - // items in the tabview. - // Arguments: - // - sender: the control that originated this event - // - eventArgs: the event's constituent arguments - void App::_OnTabClosing(const IInspectable& sender, const MUX::Controls::TabViewTabClosingEventArgs& eventArgs) - { - const auto tabViewItem = eventArgs.Item(); - _RemoveTabViewItem(tabViewItem); - - // If we don't cancel the event, the TabView will remove the item itself. - eventArgs.Cancel(true); - } - - // Method Description: - // - Responds to changes in the TabView's item list by changing the tabview's - // visibility. - // Arguments: - // - sender: the control that originated this event - // - eventArgs: the event's constituent arguments - void App::_OnTabItemsChanged(const IInspectable& sender, const Windows::Foundation::Collections::IVectorChangedEventArgs& eventArgs) - { - _UpdateTabView(); + return _root.as(); } // Method Description: @@ -1163,331 +553,62 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - the title of the focused control if there is one, else "Windows Terminal" - hstring App::GetTitle() + hstring App::Title() { - if (_settings->GlobalSettings().GetShowTitleInTitlebar()) + if (_root) { - auto selectedIndex = _tabView.SelectedIndex(); - if (selectedIndex >= 0) - { - try - { - if (auto focusedControl{ _GetFocusedControl() }) - { - return focusedControl.Title(); - } - } - CATCH_LOG(); - } + return _root->Title(); } return { L"Windows Terminal" }; } // Method Description: - // - Additional responses to clicking on a TabView's item. Currently, just remove tab with middle click - // Arguments: - // - sender: the control that originated this event (TabViewItem) - // - eventArgs: the event's constituent arguments - void App::_OnTabClick(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs) - { - if (eventArgs.GetCurrentPoint(_root).Properties().IsMiddleButtonPressed()) - { - _RemoveTabViewItem(sender); - eventArgs.Handled(true); - } - } - - // Method Description: - // - Duplicates the current focused tab - void App::_DuplicateTabViewItem() - { - const int& focusedTabIndex = _GetFocusedTabIndex(); - const auto& _tab = _tabs.at(focusedTabIndex); - - const auto& profileGuid = _tab->GetFocusedProfile(); - const auto& settings = _settings->MakeSettings(profileGuid); - - _CreateNewTabFromSettings(profileGuid.value(), settings); - } - - // Method Description: - // - Removes the tab (both TerminalControl and XAML) + // - Used to tell the app that the titlebar has been clicked. The App won't + // actually recieve any clicks in the titlebar area, so this is a helper + // to clue the app in that a click has happened. The App will use this as + // a indicator that it needs to dismiss any open flyouts. // Arguments: - // - tabViewItem: the TabViewItem in the TabView that is being removed. - void App::_RemoveTabViewItem(const IInspectable& tabViewItem) - { - // To close the window here, we need to close the hosting window. - if (_tabs.size() == 1) - { - _lastTabClosedHandlers(); - } - uint32_t tabIndexFromControl = 0; - _tabView.Items().IndexOf(tabViewItem, tabIndexFromControl); - auto focusedTabIndex = _GetFocusedTabIndex(); - - // Removing the tab from the collection will destroy its control and disconnect its connection. - _tabs.erase(_tabs.begin() + tabIndexFromControl); - _tabView.Items().RemoveAt(tabIndexFromControl); - - if (tabIndexFromControl == focusedTabIndex) - { - if (focusedTabIndex >= _tabs.size()) - { - focusedTabIndex = static_cast(_tabs.size()) - 1; - } - - if (focusedTabIndex < 0) - { - focusedTabIndex = 0; - } - - _SelectTab(focusedTabIndex); - } - } - - // Method Description: - // - Gets a colored IconElement for the profile in question. If the profile - // has an `icon` set in the settings, this will return an icon with that - // image in it. Otherwise it will return a nullptr-initialized - // IconElement. - // Arguments: - // - profile: the profile to get the icon from + // - // Return Value: - // - an IconElement for the profile's icon, if it has one. - Controls::IconElement App::_GetIconFromProfile(const Profile& profile) - { - if (profile.HasIcon()) - { - std::wstring path{ profile.GetIconPath() }; - const auto envExpandedPath{ wil::ExpandEnvironmentStringsW(path.data()) }; - winrt::hstring iconPath{ envExpandedPath }; - winrt::Windows::Foundation::Uri iconUri{ iconPath }; - Controls::BitmapIconSource iconSource; - // Make sure to set this to false, so we keep the RGB data of the - // image. Otherwise, the icon will be white for all the - // non-transparent pixels in the image. - iconSource.ShowAsMonochrome(false); - iconSource.UriSource(iconUri); - Controls::IconSourceElement elem; - elem.IconSource(iconSource); - return elem; - } - else - { - return { nullptr }; - } - } - - winrt::Microsoft::Terminal::TerminalControl::TermControl App::_GetFocusedControl() - { - int focusedTabIndex = _GetFocusedTabIndex(); - auto focusedTab = _tabs[focusedTabIndex]; - return focusedTab->GetFocusedTerminalControl(); - } - - // Method Description: - // - Vertically split the focused pane, and place the given TermControl into - // the newly created pane. - // Arguments: - // - profile: The profile GUID to associate with the newly created pane. If - // this is nullopt, use the default profile. - void App::_SplitVertical(const std::optional& profileGuid) - { - _SplitPane(Pane::SplitState::Vertical, profileGuid); - } - - // Method Description: - // - Horizontally split the focused pane and place the given TermControl - // into the newly created pane. - // Arguments: - // - profile: The profile GUID to associate with the newly created pane. If - // this is nullopt, use the default profile. - void App::_SplitHorizontal(const std::optional& profileGuid) - { - _SplitPane(Pane::SplitState::Horizontal, profileGuid); - } - - // Method Description: - // - Split the focused pane either horizontally or vertically, and place the - // given TermControl into the newly created pane. - // - If splitType == SplitState::None, this method does nothing. - // Arguments: - // - splitType: one value from the Pane::SplitState enum, indicating how the - // new pane should be split from its parent. - // - profile: The profile GUID to associate with the newly created pane. If - // this is nullopt, use the default profile. - void App::_SplitPane(const Pane::SplitState splitType, const std::optional& profileGuid) + // - + void App::TitlebarClicked() { - // Do nothing if we're requesting no split. - if (splitType == Pane::SplitState::None) + if (_root) { - return; + _root->TitlebarClicked(); } - - const auto realGuid = profileGuid ? profileGuid.value() : - _settings->GlobalSettings().GetDefaultProfile(); - const auto controlSettings = _settings->MakeSettings(realGuid); - - const auto controlConnection = _CreateConnectionFromSettings(realGuid, controlSettings); - - TermControl newControl{ controlSettings, controlConnection }; - - const int focusedTabIndex = _GetFocusedTabIndex(); - auto focusedTab = _tabs[focusedTabIndex]; - - // Hookup our event handlers to the new terminal - _RegisterTerminalEvents(newControl, focusedTab); - - return splitType == Pane::SplitState::Horizontal ? focusedTab->AddHorizontalSplit(realGuid, newControl) : - focusedTab->AddVerticalSplit(realGuid, newControl); } - // Method Description: - // - Called when our tab content size changes. This updates each tab with - // the new size, so they have a chance to update each of their panes with - // the new size. - // Arguments: - // - e: the SizeChangedEventArgs with the new size of the tab content area. - // Return Value: - // - - void App::_OnContentSizeChanged(const IInspectable& /*sender*/, Windows::UI::Xaml::SizeChangedEventArgs const& e) + // Methods that proxy typed event handlers through TerminalPage + winrt::event_token App::SetTitleBarContent(Windows::Foundation::TypedEventHandler const& handler) { - const auto newSize = e.NewSize(); - for (auto& tab : _tabs) - { - tab->ResizeContent(newSize); - } + return _root->SetTitleBarContent(handler); } - - // Method Description: - // - Place `copiedData` into the clipboard as text. Triggered when a - // terminal control raises it's CopyToClipboard event. - // Arguments: - // - copiedData: the new string content to place on the clipboard. - void App::_CopyToClipboardHandler(const winrt::hstring& copiedData) + void App::SetTitleBarContent(winrt::event_token const& token) noexcept { - _root.Dispatcher().RunAsync(CoreDispatcherPriority::High, [copiedData]() { - DataPackage dataPack = DataPackage(); - dataPack.RequestedOperation(DataPackageOperation::Copy); - dataPack.SetText(copiedData); - Clipboard::SetContent(dataPack); - - // TODO: MSFT 20642290 and 20642291 - // rtf copy and html copy - }); + return _root->SetTitleBarContent(token); } - // Method Description: - // - Fires an async event to get data from the clipboard, and paste it to - // the terminal. Triggered when the Terminal Control requests clipboard - // data with it's PasteFromClipboard event. - // Arguments: - // - eventArgs: the PasteFromClipboard event sent from the TermControl - void App::_PasteFromClipboardHandler(const IInspectable& /*sender*/, - const PasteFromClipboardEventArgs& eventArgs) + winrt::event_token App::TitleChanged(Windows::Foundation::TypedEventHandler const& handler) { - _root.Dispatcher().RunAsync(CoreDispatcherPriority::High, [eventArgs]() { - PasteFromClipboard(eventArgs); - }); + return _root->TitleChanged(handler); } - - // Method Description: - // - Handles the special case of providing a text override for the UI shortcut due to VK_OEM issue. - // Looks at the flags from the KeyChord modifiers and provides a concatenated string value of all - // in the same order that XAML would put them as well. - // Return Value: - // - a string representation of the key modifiers for the shortcut - //NOTE: This needs to be localized with https://github.com/microsoft/terminal/issues/794 if XAML framework issue not resolved before then - static std::wstring _FormatOverrideShortcutText(Settings::KeyModifiers modifiers) + void App::TitleChanged(winrt::event_token const& token) noexcept { - std::wstring buffer{ L"" }; - - if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Ctrl)) - { - buffer += L"Ctrl+"; - } - if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Shift)) - { - buffer += L"Shift+"; - } - if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Alt)) - { - buffer += L"Alt+"; - } - - return buffer; + return _root->TitleChanged(token); } - // Method Description: - // - Takes a MenuFlyoutItem and a corresponding KeyChord value and creates the accelerator for UI display. - // Takes into account a special case for an error condition for a comma - // Arguments: - // - MenuFlyoutItem that will be displayed, and a KeyChord to map an accelerator - void App::_SetAcceleratorForMenuItem(Windows::UI::Xaml::Controls::MenuFlyoutItem& menuItem, const winrt::Microsoft::Terminal::Settings::KeyChord& keyChord) + winrt::event_token App::LastTabClosed(Windows::Foundation::TypedEventHandler const& handler) { -#ifdef DEP_MICROSOFT_UI_XAML_708_FIXED - // work around https://github.com/microsoft/microsoft-ui-xaml/issues/708 in case of VK_OEM_COMMA - if (keyChord.Vkey() != VK_OEM_COMMA) - { - // use the XAML shortcut to give us the automatic capabilities - auto menuShortcut = Windows::UI::Xaml::Input::KeyboardAccelerator{}; - - // TODO: Modify this when https://github.com/microsoft/terminal/issues/877 is resolved - menuShortcut.Key(static_cast(keyChord.Vkey())); - - // inspect the modifiers from the KeyChord and set the flags int he XAML value - auto modifiers = AppKeyBindings::ConvertVKModifiers(keyChord.Modifiers()); - - // add the modifiers to the shortcut - menuShortcut.Modifiers(modifiers); - - // add to the menu - menuItem.KeyboardAccelerators().Append(menuShortcut); - } - else // we've got a comma, so need to just use the alternate method -#endif - { - // extract the modifier and key to a nice format - auto overrideString = _FormatOverrideShortcutText(keyChord.Modifiers()); - auto mappedCh = MapVirtualKeyW(keyChord.Vkey(), MAPVK_VK_TO_CHAR); - if (mappedCh != 0) - { - menuItem.KeyboardAcceleratorTextOverride(overrideString + gsl::narrow_cast(mappedCh)); - } - } + return _root->LastTabClosed(handler); } - - // Method Description: - // - Creates a new connection based on the profile settings - // Arguments: - // - the profile GUID we want the settings from - // - the terminal settings - // Return value: - // - the desired connection - TerminalConnection::ITerminalConnection App::_CreateConnectionFromSettings(GUID profileGuid, winrt::Microsoft::Terminal::Settings::TerminalSettings settings) + void App::LastTabClosed(winrt::event_token const& token) noexcept { - const auto* const profile = _settings->FindProfile(profileGuid); - TerminalConnection::ITerminalConnection connection{ nullptr }; - // The Azure connection has a boost dependency, and boost does not support ARM64 - // so we make sure that we do not try to compile the Azure connection code if we are in ARM64 (we would get build errors otherwise) -#ifndef _M_ARM64 - if (profile->HasConnectionType() && profile->GetConnectionType() == AzureConnectionType) - { - connection = TerminalConnection::AzureConnection(settings.InitialRows(), settings.InitialCols()); - } - else -#endif - { - connection = TerminalConnection::ConhostConnection(settings.Commandline(), settings.StartingDirectory(), settings.InitialRows(), settings.InitialCols(), winrt::guid()); - } - return connection; + return _root->LastTabClosed(token); } // -------------------------------- 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(App, TitleChanged, _titleChangeHandlers, TerminalControl::TitleChangedEventArgs); - DEFINE_EVENT(App, LastTabClosed, _lastTabClosedHandlers, winrt::TerminalApp::LastTabClosedEventArgs); - DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(App, SetTitleBarContent, _setTitleBarContentHandlers, TerminalApp::App, winrt::Windows::UI::Xaml::UIElement); DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(App, RequestedThemeChanged, _requestedThemeChangedHandlers, TerminalApp::App, winrt::Windows::UI::Xaml::ElementTheme); } diff --git a/src/cascadia/TerminalApp/App.h b/src/cascadia/TerminalApp/App.h index 390f195507c..8d901b756e4 100644 --- a/src/cascadia/TerminalApp/App.h +++ b/src/cascadia/TerminalApp/App.h @@ -5,9 +5,9 @@ #include "Tab.h" #include "CascadiaSettings.h" +#include "TerminalPage.h" #include "App.g.h" #include "App.base.h" -#include "ScopedResourceLoader.h" #include "../../cascadia/inc/cppwinrt_utils.h" #include @@ -25,8 +25,7 @@ namespace winrt::TerminalApp::implementation { public: App(); - - Windows::UI::Xaml::UIElement GetRoot() noexcept; + ~App() = default; void Create(); void LoadSettings(); @@ -34,14 +33,15 @@ namespace winrt::TerminalApp::implementation Windows::Foundation::Point GetLaunchDimensions(uint32_t dpi); bool GetShowTabsInTitlebar(); - ~App(); + Windows::UI::Xaml::UIElement GetRoot() noexcept; - hstring GetTitle(); + hstring Title(); + void TitlebarClicked(); // -------------------------------- WinRT Events --------------------------------- - DECLARE_EVENT(TitleChanged, _titleChangeHandlers, winrt::Microsoft::Terminal::TerminalControl::TitleChangedEventArgs); - DECLARE_EVENT(LastTabClosed, _lastTabClosedHandlers, winrt::TerminalApp::LastTabClosedEventArgs); - DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(SetTitleBarContent, _setTitleBarContentHandlers, TerminalApp::App, winrt::Windows::UI::Xaml::UIElement); + DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(TitleChanged, _titleChangeHandlers, winrt::Windows::Foundation::IInspectable, winrt::hstring); + DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(LastTabClosed, _lastTabClosedHandlers, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::LastTabClosedEventArgs); + DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(SetTitleBarContent, _setTitleBarContentHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::UIElement); DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(RequestedThemeChanged, _requestedThemeChangedHandlers, TerminalApp::App, winrt::Windows::UI::Xaml::ElementTheme); private: @@ -49,99 +49,44 @@ namespace winrt::TerminalApp::implementation // the ctor, you're going to have a bad time. It'll mysteriously fail to // activate the app. // ALSO: If you add any UIElements as roots here, make sure they're - // updated in _ApplyTheme. The two roots currently are _root and _tabRow - // (which is a root when the tabs are in the titlebar.) - Windows::UI::Xaml::Controls::Control _root{ nullptr }; - Microsoft::UI::Xaml::Controls::TabView _tabView{ nullptr }; - TerminalApp::TabRowControl _tabRow{ nullptr }; - Windows::UI::Xaml::Controls::Grid _tabContent{ nullptr }; - Windows::UI::Xaml::Controls::SplitButton _newTabButton{ nullptr }; + // updated in _ApplyTheme. The root currently is _root. + winrt::com_ptr _root{ nullptr }; - std::vector> _tabs; + std::shared_ptr<::TerminalApp::CascadiaSettings> _settings{ nullptr }; - std::unique_ptr<::TerminalApp::CascadiaSettings> _settings; + std::shared_ptr _resourceLoader{ nullptr }; HRESULT _settingsLoadedResult; + winrt::hstring _settingsLoadExceptionText{}; bool _loadedInitialSettings; - std::shared_mutex _dialogLock; - - ScopedResourceLoader _resourceLoader; wil::unique_folder_change_reader_nothrow _reader; + std::shared_mutex _dialogLock; + std::atomic _settingsReloadQueued{ false }; - void _CreateNewTabFlyout(); + fire_and_forget _ShowDialog(const winrt::Windows::Foundation::IInspectable& sender, winrt::Windows::UI::Xaml::Controls::ContentDialog dialog); + void _ShowLoadErrorsDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey, HRESULT settingsLoadedResult); + void _ShowLoadWarningsDialog(); - fire_and_forget _ShowDialog(const winrt::Windows::Foundation::IInspectable& titleElement, - const winrt::Windows::Foundation::IInspectable& contentElement, - const winrt::hstring& closeButtonText); - void _ShowOkDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey); - void _ShowAboutDialog(); + void _OnLoaded(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); - [[nodiscard]] HRESULT _TryLoadSettings(const bool saveOnLoad) noexcept; + [[nodiscard]] HRESULT _TryLoadSettings() noexcept; void _LoadSettings(); void _OpenSettings(); - - void _HookupKeyBindings(TerminalApp::AppKeyBindings bindings) noexcept; - void _RegisterSettingsChange(); fire_and_forget _DispatchReloadSettings(); void _ReloadSettings(); - void _SettingsButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); - void _FeedbackButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); - void _AboutButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); - - void _UpdateTabView(); - void _UpdateTabIcon(std::shared_ptr tab); - void _UpdateTitle(std::shared_ptr tab); - - void _RegisterTerminalEvents(Microsoft::Terminal::TerminalControl::TermControl term, std::shared_ptr hostingTab); - - void _CreateNewTabFromSettings(GUID profileGuid, winrt::Microsoft::Terminal::Settings::TerminalSettings settings); - winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(GUID profileGuid, winrt::Microsoft::Terminal::Settings::TerminalSettings settings); - - void _OpenNewTab(std::optional profileIndex); - void _DuplicateTabViewItem(); - void _CloseFocusedTab(); - void _CloseFocusedPane(); - void _SelectNextTab(const bool bMoveRight); - void _SelectTab(const int tabIndex); - - void _SetFocusedTabIndex(int tabIndex); - int _GetFocusedTabIndex() const; - - void _Scroll(int delta); - void _CopyText(const bool trimTrailingWhitespace); - void _PasteText(); - void _SplitVertical(const std::optional& profileGuid); - void _SplitHorizontal(const std::optional& profileGuid); - void _SplitPane(const Pane::SplitState splitType, const std::optional& profileGuid); - - // Todo: add more event implementations here - // MSFT:20641986: Add keybindings for New Window - void _ScrollPage(int delta); - void _ResizePane(const Direction& direction); - void _MoveFocus(const Direction& direction); - - void _OnLoaded(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); - void _OnTabSelectionChanged(const IInspectable& sender, const Windows::UI::Xaml::Controls::SelectionChangedEventArgs& eventArgs); - void _OnTabClosing(const IInspectable& sender, const Microsoft::UI::Xaml::Controls::TabViewTabClosingEventArgs& eventArgs); - void _OnTabItemsChanged(const IInspectable& sender, const Windows::Foundation::Collections::IVectorChangedEventArgs& eventArgs); - void _OnTabClick(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs); - void _OnContentSizeChanged(const IInspectable& sender, Windows::UI::Xaml::SizeChangedEventArgs const& e); - - void _RemoveTabViewItem(const IInspectable& tabViewItem); - void _ApplyTheme(const Windows::UI::Xaml::ElementTheme& newTheme); static Windows::UI::Xaml::Controls::IconElement _GetIconFromProfile(const ::TerminalApp::Profile& profile); winrt::Microsoft::Terminal::TerminalControl::TermControl _GetFocusedControl(); - void _CopyToClipboardHandler(const winrt::hstring& copiedData); + void _CopyToClipboardHandler(const IInspectable& sender, const winrt::Microsoft::Terminal::TerminalControl::CopyToClipboardEventArgs& copiedData); void _PasteFromClipboardHandler(const IInspectable& sender, const Microsoft::Terminal::TerminalControl::PasteFromClipboardEventArgs& eventArgs); static void _SetAcceleratorForMenuItem(Windows::UI::Xaml::Controls::MenuFlyoutItem& menuItem, const winrt::Microsoft::Terminal::Settings::KeyChord& keyChord); diff --git a/src/cascadia/TerminalApp/App.idl b/src/cascadia/TerminalApp/App.idl index 3fe919a697d..9e8981b1d33 100644 --- a/src/cascadia/TerminalApp/App.idl +++ b/src/cascadia/TerminalApp/App.idl @@ -21,14 +21,15 @@ namespace TerminalApp Windows.UI.Xaml.UIElement GetRoot(); + String Title { get; }; + Windows.Foundation.Point GetLaunchDimensions(UInt32 dpi); Boolean GetShowTabsInTitlebar(); + void TitlebarClicked(); - event Microsoft.Terminal.TerminalControl.TitleChangedEventArgs TitleChanged; - event LastTabClosedEventArgs LastTabClosed; - event Windows.Foundation.TypedEventHandler SetTitleBarContent; + event Windows.Foundation.TypedEventHandler SetTitleBarContent; + event Windows.Foundation.TypedEventHandler TitleChanged; + event Windows.Foundation.TypedEventHandler LastTabClosed; event Windows.Foundation.TypedEventHandler RequestedThemeChanged; - - String GetTitle(); } } diff --git a/src/cascadia/TerminalApp/App.xaml b/src/cascadia/TerminalApp/App.xaml index 4cb4d0eea66..7b5ce85ed39 100644 --- a/src/cascadia/TerminalApp/App.xaml +++ b/src/cascadia/TerminalApp/App.xaml @@ -44,6 +44,11 @@ the MIT License. See LICENSE in the project root for license information. --> + + + diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp new file mode 100644 index 00000000000..1abce6291d3 --- /dev/null +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -0,0 +1,183 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "App.h" + +#include "TerminalPage.h" +#include "Utils.h" + +using namespace winrt::Windows::ApplicationModel::DataTransfer; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Text; +using namespace winrt::Windows::UI::Core; +using namespace winrt::Windows::System; +using namespace winrt::Microsoft::Terminal; +using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Microsoft::Terminal::TerminalControl; +using namespace winrt::Microsoft::Terminal::TerminalConnection; +using namespace ::TerminalApp; + +namespace winrt +{ + namespace MUX = Microsoft::UI::Xaml; + using IInspectable = Windows::Foundation::IInspectable; +} + +namespace winrt::TerminalApp::implementation +{ + void TerminalPage::_HandleNewTab(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + _OpenNewTab(std::nullopt); + args.Handled(true); + } + void TerminalPage::_HandleOpenNewTabDropdown(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + _OpenNewTabDropdown(); + args.Handled(true); + } + + void TerminalPage::_HandleDuplicateTab(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + _DuplicateTabViewItem(); + args.Handled(true); + } + + void TerminalPage::_HandleCloseTab(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + _CloseFocusedTab(); + args.Handled(true); + } + + void TerminalPage::_HandleClosePane(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + _CloseFocusedPane(); + args.Handled(true); + } + + void TerminalPage::_HandleScrollUp(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + _Scroll(-1); + args.Handled(true); + } + + void TerminalPage::_HandleScrollDown(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + _Scroll(1); + args.Handled(true); + } + + void TerminalPage::_HandleNextTab(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + _SelectNextTab(true); + args.Handled(true); + } + + void TerminalPage::_HandlePrevTab(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + _SelectNextTab(false); + args.Handled(true); + } + + void TerminalPage::_HandleSplitVertical(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + _SplitVertical(std::nullopt); + args.Handled(true); + } + + void TerminalPage::_HandleSplitHorizontal(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + _SplitHorizontal(std::nullopt); + args.Handled(true); + } + + void TerminalPage::_HandleScrollUpPage(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + _ScrollPage(-1); + args.Handled(true); + } + + void TerminalPage::_HandleScrollDownPage(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + _ScrollPage(1); + args.Handled(true); + } + + void TerminalPage::_HandleOpenSettings(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + _OpenSettings(); + args.Handled(true); + } + + void TerminalPage::_HandlePasteText(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + _PasteText(); + args.Handled(true); + } + + void TerminalPage::_HandleNewTabWithProfile(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + if (const auto& realArgs = args.ActionArgs().try_as()) + { + _OpenNewTab({ realArgs.ProfileIndex() }); + args.Handled(true); + } + } + + void TerminalPage::_HandleSwitchToTab(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + if (const auto& realArgs = args.ActionArgs().try_as()) + { + const auto handled = _SelectTab({ realArgs.TabIndex() }); + args.Handled(handled); + } + } + + void TerminalPage::_HandleResizePane(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + if (const auto& realArgs = args.ActionArgs().try_as()) + { + _ResizePane(realArgs.Direction()); + args.Handled(true); + } + } + + void TerminalPage::_HandleMoveFocus(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + if (const auto& realArgs = args.ActionArgs().try_as()) + { + _MoveFocus(realArgs.Direction()); + args.Handled(true); + } + } + + void TerminalPage::_HandleCopyText(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + if (const auto& realArgs = args.ActionArgs().try_as()) + { + const auto handled = _CopyText(realArgs.TrimWhitespace()); + args.Handled(handled); + } + } + +} diff --git a/src/cascadia/TerminalApp/AppKeyBindings.cpp b/src/cascadia/TerminalApp/AppKeyBindings.cpp index 916e086f268..7e35e139eac 100644 --- a/src/cascadia/TerminalApp/AppKeyBindings.cpp +++ b/src/cascadia/TerminalApp/AppKeyBindings.cpp @@ -9,7 +9,6 @@ using namespace winrt::Microsoft::Terminal; using namespace winrt::TerminalApp; -using namespace winrt::Windows::Data::Json; namespace winrt::TerminalApp::implementation { @@ -47,143 +46,337 @@ namespace winrt::TerminalApp::implementation switch (action) { case ShortcutAction::CopyText: - _CopyTextHandlers(true); - return true; + { + auto args = winrt::make_self(); + args->TrimWhitespace(true); + auto eventArgs = winrt::make_self(*args); + _CopyTextHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::CopyTextWithoutNewlines: - _CopyTextHandlers(false); - return true; + { + auto args = winrt::make_self(); + args->TrimWhitespace(false); + auto eventArgs = winrt::make_self(*args); + _CopyTextHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::PasteText: - _PasteTextHandlers(); - return true; + { + auto eventArgs = winrt::make_self(); + _PasteTextHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::NewTab: - _NewTabHandlers(); - return true; + { + auto eventArgs = winrt::make_self(); + _NewTabHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } + case ShortcutAction::OpenNewTabDropdown: + { + auto eventArgs = winrt::make_self(); + _OpenNewTabDropdownHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::DuplicateTab: - _DuplicateTabHandlers(); - return true; + { + auto eventArgs = winrt::make_self(); + _DuplicateTabHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::OpenSettings: - _OpenSettingsHandlers(); - return true; + { + auto eventArgs = winrt::make_self(); + _OpenSettingsHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::NewTabProfile0: - _NewTabWithProfileHandlers(0); - return true; + { + auto args = winrt::make_self(); + args->ProfileIndex(0); + auto eventArgs = winrt::make_self(*args); + _NewTabWithProfileHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::NewTabProfile1: - _NewTabWithProfileHandlers(1); - return true; + { + auto args = winrt::make_self(); + args->ProfileIndex(1); + auto eventArgs = winrt::make_self(*args); + _NewTabWithProfileHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::NewTabProfile2: - _NewTabWithProfileHandlers(2); - return true; + { + auto args = winrt::make_self(); + args->ProfileIndex(2); + auto eventArgs = winrt::make_self(*args); + _NewTabWithProfileHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::NewTabProfile3: - _NewTabWithProfileHandlers(3); - return true; + { + auto args = winrt::make_self(); + args->ProfileIndex(3); + auto eventArgs = winrt::make_self(*args); + _NewTabWithProfileHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::NewTabProfile4: - _NewTabWithProfileHandlers(4); - return true; + { + auto args = winrt::make_self(); + args->ProfileIndex(4); + auto eventArgs = winrt::make_self(*args); + _NewTabWithProfileHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::NewTabProfile5: - _NewTabWithProfileHandlers(5); - return true; + { + auto args = winrt::make_self(); + args->ProfileIndex(5); + auto eventArgs = winrt::make_self(*args); + _NewTabWithProfileHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::NewTabProfile6: - _NewTabWithProfileHandlers(6); - return true; + { + auto args = winrt::make_self(); + args->ProfileIndex(6); + auto eventArgs = winrt::make_self(*args); + _NewTabWithProfileHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::NewTabProfile7: - _NewTabWithProfileHandlers(7); - return true; + { + auto args = winrt::make_self(); + args->ProfileIndex(7); + auto eventArgs = winrt::make_self(*args); + _NewTabWithProfileHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::NewTabProfile8: - _NewTabWithProfileHandlers(8); - return true; + { + auto args = winrt::make_self(); + args->ProfileIndex(8); + auto eventArgs = winrt::make_self(*args); + _NewTabWithProfileHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::NewWindow: - _NewWindowHandlers(); - return true; + { + auto eventArgs = winrt::make_self(); + _NewWindowHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::CloseWindow: - _CloseWindowHandlers(); - return true; + { + auto eventArgs = winrt::make_self(); + _CloseWindowHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::CloseTab: - _CloseTabHandlers(); - return true; + { + auto eventArgs = winrt::make_self(); + _CloseTabHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::ClosePane: - _ClosePaneHandlers(); - return true; + { + auto eventArgs = winrt::make_self(); + _ClosePaneHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::ScrollUp: - _ScrollUpHandlers(); - return true; + { + auto eventArgs = winrt::make_self(); + _ScrollUpHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::ScrollDown: - _ScrollDownHandlers(); - return true; + { + auto eventArgs = winrt::make_self(); + _ScrollDownHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::ScrollUpPage: - _ScrollUpPageHandlers(); - return true; + { + auto eventArgs = winrt::make_self(); + _ScrollUpPageHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::ScrollDownPage: - _ScrollDownPageHandlers(); - return true; + { + auto eventArgs = winrt::make_self(); + _ScrollDownPageHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::NextTab: - _NextTabHandlers(); - return true; + { + auto eventArgs = winrt::make_self(); + _NextTabHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::PrevTab: - _PrevTabHandlers(); - return true; + { + auto eventArgs = winrt::make_self(); + _PrevTabHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::SplitVertical: - _SplitVerticalHandlers(); - return true; + { + auto eventArgs = winrt::make_self(); + _SplitVerticalHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::SplitHorizontal: - _SplitHorizontalHandlers(); - return true; + { + auto eventArgs = winrt::make_self(); + _SplitHorizontalHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::SwitchToTab0: - _SwitchToTabHandlers(0); - return true; + { + auto args = winrt::make_self(); + args->TabIndex(0); + auto eventArgs = winrt::make_self(*args); + _SwitchToTabHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::SwitchToTab1: - _SwitchToTabHandlers(1); - return true; + { + auto args = winrt::make_self(); + args->TabIndex(1); + auto eventArgs = winrt::make_self(*args); + _SwitchToTabHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::SwitchToTab2: - _SwitchToTabHandlers(2); - return true; + { + auto args = winrt::make_self(); + args->TabIndex(2); + auto eventArgs = winrt::make_self(*args); + _SwitchToTabHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::SwitchToTab3: - _SwitchToTabHandlers(3); - return true; + { + auto args = winrt::make_self(); + args->TabIndex(3); + auto eventArgs = winrt::make_self(*args); + _SwitchToTabHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::SwitchToTab4: - _SwitchToTabHandlers(4); - return true; + { + auto args = winrt::make_self(); + args->TabIndex(4); + auto eventArgs = winrt::make_self(*args); + _SwitchToTabHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::SwitchToTab5: - _SwitchToTabHandlers(5); - return true; + { + auto args = winrt::make_self(); + args->TabIndex(5); + auto eventArgs = winrt::make_self(*args); + _SwitchToTabHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::SwitchToTab6: - _SwitchToTabHandlers(6); - return true; + { + auto args = winrt::make_self(); + args->TabIndex(6); + auto eventArgs = winrt::make_self(*args); + _SwitchToTabHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::SwitchToTab7: - _SwitchToTabHandlers(7); - return true; + { + auto args = winrt::make_self(); + args->TabIndex(7); + auto eventArgs = winrt::make_self(*args); + _SwitchToTabHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::SwitchToTab8: - _SwitchToTabHandlers(8); - return true; + { + auto args = winrt::make_self(); + args->TabIndex(8); + auto eventArgs = winrt::make_self(*args); + _SwitchToTabHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::ResizePaneLeft: - _ResizePaneHandlers(Direction::Left); - return true; + { + auto args = winrt::make_self(); + args->Direction(Direction::Left); + auto eventArgs = winrt::make_self(*args); + _ResizePaneHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::ResizePaneRight: - _ResizePaneHandlers(Direction::Right); - return true; + { + auto args = winrt::make_self(); + args->Direction(Direction::Right); + auto eventArgs = winrt::make_self(*args); + _ResizePaneHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::ResizePaneUp: - _ResizePaneHandlers(Direction::Up); - return true; + { + auto args = winrt::make_self(); + args->Direction(Direction::Up); + auto eventArgs = winrt::make_self(*args); + _ResizePaneHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::ResizePaneDown: - _ResizePaneHandlers(Direction::Down); - return true; + { + auto args = winrt::make_self(); + args->Direction(Direction::Down); + auto eventArgs = winrt::make_self(*args); + _ResizePaneHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::MoveFocusLeft: - _MoveFocusHandlers(Direction::Left); - return true; + { + auto args = winrt::make_self(); + args->Direction(Direction::Left); + auto eventArgs = winrt::make_self(*args); + _MoveFocusHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::MoveFocusRight: - _MoveFocusHandlers(Direction::Right); - return true; + { + auto args = winrt::make_self(); + args->Direction(Direction::Right); + auto eventArgs = winrt::make_self(*args); + _MoveFocusHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::MoveFocusUp: - _MoveFocusHandlers(Direction::Up); - return true; + { + auto args = winrt::make_self(); + args->Direction(Direction::Up); + auto eventArgs = winrt::make_self(*args); + _MoveFocusHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } case ShortcutAction::MoveFocusDown: - _MoveFocusHandlers(Direction::Down); - return true; + { + auto args = winrt::make_self(); + args->Direction(Direction::Down); + auto eventArgs = winrt::make_self(*args); + _MoveFocusHandlers(*this, *eventArgs); + return eventArgs->Handled(); + } default: return false; } @@ -214,31 +407,4 @@ namespace winrt::TerminalApp::implementation return keyModifiers; } - - // -------------------------------- Events --------------------------------- - // clang-format off - DEFINE_EVENT(AppKeyBindings, CopyText, _CopyTextHandlers, TerminalApp::CopyTextEventArgs); - DEFINE_EVENT(AppKeyBindings, PasteText, _PasteTextHandlers, TerminalApp::PasteTextEventArgs); - DEFINE_EVENT(AppKeyBindings, NewTab, _NewTabHandlers, TerminalApp::NewTabEventArgs); - DEFINE_EVENT(AppKeyBindings, DuplicateTab, _DuplicateTabHandlers, TerminalApp::DuplicateTabEventArgs); - DEFINE_EVENT(AppKeyBindings, NewTabWithProfile, _NewTabWithProfileHandlers, TerminalApp::NewTabWithProfileEventArgs); - DEFINE_EVENT(AppKeyBindings, NewWindow, _NewWindowHandlers, TerminalApp::NewWindowEventArgs); - DEFINE_EVENT(AppKeyBindings, CloseWindow, _CloseWindowHandlers, TerminalApp::CloseWindowEventArgs); - DEFINE_EVENT(AppKeyBindings, CloseTab, _CloseTabHandlers, TerminalApp::CloseTabEventArgs); - DEFINE_EVENT(AppKeyBindings, ClosePane, _ClosePaneHandlers, TerminalApp::ClosePaneEventArgs); - DEFINE_EVENT(AppKeyBindings, SwitchToTab, _SwitchToTabHandlers, TerminalApp::SwitchToTabEventArgs); - DEFINE_EVENT(AppKeyBindings, NextTab, _NextTabHandlers, TerminalApp::NextTabEventArgs); - DEFINE_EVENT(AppKeyBindings, PrevTab, _PrevTabHandlers, TerminalApp::PrevTabEventArgs); - DEFINE_EVENT(AppKeyBindings, SplitVertical, _SplitVerticalHandlers, TerminalApp::SplitVerticalEventArgs); - DEFINE_EVENT(AppKeyBindings, SplitHorizontal, _SplitHorizontalHandlers, TerminalApp::SplitHorizontalEventArgs); - DEFINE_EVENT(AppKeyBindings, IncreaseFontSize, _IncreaseFontSizeHandlers, TerminalApp::IncreaseFontSizeEventArgs); - DEFINE_EVENT(AppKeyBindings, DecreaseFontSize, _DecreaseFontSizeHandlers, TerminalApp::DecreaseFontSizeEventArgs); - DEFINE_EVENT(AppKeyBindings, ScrollUp, _ScrollUpHandlers, TerminalApp::ScrollUpEventArgs); - DEFINE_EVENT(AppKeyBindings, ScrollDown, _ScrollDownHandlers, TerminalApp::ScrollDownEventArgs); - DEFINE_EVENT(AppKeyBindings, ScrollUpPage, _ScrollUpPageHandlers, TerminalApp::ScrollUpPageEventArgs); - DEFINE_EVENT(AppKeyBindings, ScrollDownPage, _ScrollDownPageHandlers, TerminalApp::ScrollDownPageEventArgs); - DEFINE_EVENT(AppKeyBindings, OpenSettings, _OpenSettingsHandlers, TerminalApp::OpenSettingsEventArgs); - DEFINE_EVENT(AppKeyBindings, ResizePane, _ResizePaneHandlers, TerminalApp::ResizePaneEventArgs); - DEFINE_EVENT(AppKeyBindings, MoveFocus, _MoveFocusHandlers, TerminalApp::MoveFocusEventArgs); - // clang-format on } diff --git a/src/cascadia/TerminalApp/AppKeyBindings.h b/src/cascadia/TerminalApp/AppKeyBindings.h index 0e178065ec8..0ad405378ef 100644 --- a/src/cascadia/TerminalApp/AppKeyBindings.h +++ b/src/cascadia/TerminalApp/AppKeyBindings.h @@ -4,6 +4,7 @@ #pragma once #include "AppKeyBindings.g.h" +#include "ActionArgs.h" #include "..\inc\cppwinrt_utils.h" namespace winrt::TerminalApp::implementation @@ -39,29 +40,30 @@ namespace winrt::TerminalApp::implementation static Windows::System::VirtualKeyModifiers ConvertVKModifiers(winrt::Microsoft::Terminal::Settings::KeyModifiers modifiers); // clang-format off - DECLARE_EVENT(CopyText, _CopyTextHandlers, TerminalApp::CopyTextEventArgs); - DECLARE_EVENT(PasteText, _PasteTextHandlers, TerminalApp::PasteTextEventArgs); - DECLARE_EVENT(NewTab, _NewTabHandlers, TerminalApp::NewTabEventArgs); - DECLARE_EVENT(DuplicateTab, _DuplicateTabHandlers, TerminalApp::DuplicateTabEventArgs); - DECLARE_EVENT(NewTabWithProfile, _NewTabWithProfileHandlers, TerminalApp::NewTabWithProfileEventArgs); - DECLARE_EVENT(NewWindow, _NewWindowHandlers, TerminalApp::NewWindowEventArgs); - DECLARE_EVENT(CloseWindow, _CloseWindowHandlers, TerminalApp::CloseWindowEventArgs); - DECLARE_EVENT(CloseTab, _CloseTabHandlers, TerminalApp::CloseTabEventArgs); - DECLARE_EVENT(ClosePane, _ClosePaneHandlers, TerminalApp::ClosePaneEventArgs); - DECLARE_EVENT(SwitchToTab, _SwitchToTabHandlers, TerminalApp::SwitchToTabEventArgs); - DECLARE_EVENT(NextTab, _NextTabHandlers, TerminalApp::NextTabEventArgs); - DECLARE_EVENT(PrevTab, _PrevTabHandlers, TerminalApp::PrevTabEventArgs); - DECLARE_EVENT(SplitVertical, _SplitVerticalHandlers, TerminalApp::SplitVerticalEventArgs); - DECLARE_EVENT(SplitHorizontal, _SplitHorizontalHandlers, TerminalApp::SplitHorizontalEventArgs); - DECLARE_EVENT(IncreaseFontSize, _IncreaseFontSizeHandlers, TerminalApp::IncreaseFontSizeEventArgs); - DECLARE_EVENT(DecreaseFontSize, _DecreaseFontSizeHandlers, TerminalApp::DecreaseFontSizeEventArgs); - DECLARE_EVENT(ScrollUp, _ScrollUpHandlers, TerminalApp::ScrollUpEventArgs); - DECLARE_EVENT(ScrollDown, _ScrollDownHandlers, TerminalApp::ScrollDownEventArgs); - DECLARE_EVENT(ScrollUpPage, _ScrollUpPageHandlers, TerminalApp::ScrollUpPageEventArgs); - DECLARE_EVENT(ScrollDownPage, _ScrollDownPageHandlers, TerminalApp::ScrollDownPageEventArgs); - DECLARE_EVENT(OpenSettings, _OpenSettingsHandlers, TerminalApp::OpenSettingsEventArgs); - DECLARE_EVENT(ResizePane, _ResizePaneHandlers, TerminalApp::ResizePaneEventArgs); - DECLARE_EVENT(MoveFocus, _MoveFocusHandlers, TerminalApp::MoveFocusEventArgs); + TYPED_EVENT(CopyText, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(PasteText, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(NewTab, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(OpenNewTabDropdown,TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(DuplicateTab, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(NewTabWithProfile, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(NewWindow, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(CloseWindow, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(CloseTab, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(ClosePane, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(SwitchToTab, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(NextTab, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(PrevTab, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(SplitVertical, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(SplitHorizontal, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(IncreaseFontSize, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(DecreaseFontSize, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(ScrollUp, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(ScrollDown, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(ScrollUpPage, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(ScrollDownPage, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(OpenSettings, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(ResizePane, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); + TYPED_EVENT(MoveFocus, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs); // clang-format on private: diff --git a/src/cascadia/TerminalApp/AppKeyBindings.idl b/src/cascadia/TerminalApp/AppKeyBindings.idl index de195214de9..8786d44ab17 100644 --- a/src/cascadia/TerminalApp/AppKeyBindings.idl +++ b/src/cascadia/TerminalApp/AppKeyBindings.idl @@ -1,22 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import "../ActionArgs.idl"; namespace TerminalApp { - enum Direction - { - Left = 0, - Right, - Up, - Down - }; - enum ShortcutAction { CopyText = 0, CopyTextWithoutNewlines, PasteText, NewTab, + OpenNewTabDropdown, DuplicateTab, NewTabProfile0, NewTabProfile1, @@ -61,30 +55,6 @@ namespace TerminalApp OpenSettings }; - delegate void CopyTextEventArgs(Boolean trimWhitespace); - delegate void PasteTextEventArgs(); - delegate void NewTabEventArgs(); - delegate void DuplicateTabEventArgs(); - delegate void NewTabWithProfileEventArgs(Int32 profileIndex); - delegate void NewWindowEventArgs(); - delegate void CloseWindowEventArgs(); - delegate void CloseTabEventArgs(); - delegate void ClosePaneEventArgs(); - delegate void NextTabEventArgs(); - delegate void PrevTabEventArgs(); - delegate void SplitVerticalEventArgs(); - delegate void SplitHorizontalEventArgs(); - delegate void SwitchToTabEventArgs(Int32 profileIndex); - delegate void IncreaseFontSizeEventArgs(); - delegate void DecreaseFontSizeEventArgs(); - delegate void ScrollUpEventArgs(); - delegate void ScrollDownEventArgs(); - delegate void ScrollUpPageEventArgs(); - delegate void ScrollDownPageEventArgs(); - delegate void OpenSettingsEventArgs(); - delegate void ResizePaneEventArgs(Direction direction); - delegate void MoveFocusEventArgs(Direction direction); - [default_interface] runtimeclass AppKeyBindings : Microsoft.Terminal.Settings.IKeyBindings { AppKeyBindings(); @@ -92,28 +62,29 @@ namespace TerminalApp void SetKeyBinding(ShortcutAction action, Microsoft.Terminal.Settings.KeyChord chord); Microsoft.Terminal.Settings.KeyChord GetKeyBinding(ShortcutAction action); - event CopyTextEventArgs CopyText; - event PasteTextEventArgs PasteText; - event NewTabEventArgs NewTab; - event DuplicateTabEventArgs DuplicateTab; - event NewTabWithProfileEventArgs NewTabWithProfile; - event NewWindowEventArgs NewWindow; - event CloseWindowEventArgs CloseWindow; - event CloseTabEventArgs CloseTab; - event ClosePaneEventArgs ClosePane; - event SwitchToTabEventArgs SwitchToTab; - event NextTabEventArgs NextTab; - event PrevTabEventArgs PrevTab; - event SplitVerticalEventArgs SplitVertical; - event SplitHorizontalEventArgs SplitHorizontal; - event IncreaseFontSizeEventArgs IncreaseFontSize; - event DecreaseFontSizeEventArgs DecreaseFontSize; - event ScrollUpEventArgs ScrollUp; - event ScrollDownEventArgs ScrollDown; - event ScrollUpPageEventArgs ScrollUpPage; - event ScrollDownPageEventArgs ScrollDownPage; - event OpenSettingsEventArgs OpenSettings; - event ResizePaneEventArgs ResizePane; - event MoveFocusEventArgs MoveFocus; + event Windows.Foundation.TypedEventHandler CopyText; + event Windows.Foundation.TypedEventHandler PasteText; + event Windows.Foundation.TypedEventHandler NewTab; + event Windows.Foundation.TypedEventHandler OpenNewTabDropdown; + event Windows.Foundation.TypedEventHandler DuplicateTab; + event Windows.Foundation.TypedEventHandler NewTabWithProfile; + event Windows.Foundation.TypedEventHandler NewWindow; + event Windows.Foundation.TypedEventHandler CloseWindow; + event Windows.Foundation.TypedEventHandler CloseTab; + event Windows.Foundation.TypedEventHandler ClosePane; + event Windows.Foundation.TypedEventHandler SwitchToTab; + event Windows.Foundation.TypedEventHandler NextTab; + event Windows.Foundation.TypedEventHandler PrevTab; + event Windows.Foundation.TypedEventHandler SplitVertical; + event Windows.Foundation.TypedEventHandler SplitHorizontal; + event Windows.Foundation.TypedEventHandler IncreaseFontSize; + event Windows.Foundation.TypedEventHandler DecreaseFontSize; + event Windows.Foundation.TypedEventHandler ScrollUp; + event Windows.Foundation.TypedEventHandler ScrollDown; + event Windows.Foundation.TypedEventHandler ScrollUpPage; + event Windows.Foundation.TypedEventHandler ScrollDownPage; + event Windows.Foundation.TypedEventHandler OpenSettings; + event Windows.Foundation.TypedEventHandler ResizePane; + event Windows.Foundation.TypedEventHandler MoveFocus; } } diff --git a/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp b/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp index 796f5957edd..4e0e752976c 100644 --- a/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp +++ b/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp @@ -17,6 +17,7 @@ static constexpr std::string_view CopyTextKey{ "copy" }; static constexpr std::string_view CopyTextWithoutNewlinesKey{ "copyTextWithoutNewlines" }; static constexpr std::string_view PasteTextKey{ "paste" }; static constexpr std::string_view NewTabKey{ "newTab" }; +static constexpr std::string_view OpenNewTabDropdownKey{ "openNewTabDropdown" }; static constexpr std::string_view DuplicateTabKey{ "duplicateTab" }; static constexpr std::string_view NewTabWithProfile0Key{ "newTabProfile0" }; static constexpr std::string_view NewTabWithProfile1Key{ "newTabProfile1" }; @@ -74,6 +75,7 @@ static const std::map> commandName { CopyTextWithoutNewlinesKey, ShortcutAction::CopyTextWithoutNewlines }, { PasteTextKey, ShortcutAction::PasteText }, { NewTabKey, ShortcutAction::NewTab }, + { OpenNewTabDropdownKey, ShortcutAction::OpenNewTabDropdown }, { DuplicateTabKey, ShortcutAction::DuplicateTab }, { NewTabWithProfile0Key, ShortcutAction::NewTabProfile0 }, { NewTabWithProfile1Key, ShortcutAction::NewTabProfile1 }, diff --git a/src/cascadia/TerminalApp/CascadiaSettings.cpp b/src/cascadia/TerminalApp/CascadiaSettings.cpp index 5224ad6ba94..3a98b5c9b85 100644 --- a/src/cascadia/TerminalApp/CascadiaSettings.cpp +++ b/src/cascadia/TerminalApp/CascadiaSettings.cpp @@ -9,6 +9,7 @@ #include "CascadiaSettings.h" #include "../../types/inc/utils.hpp" #include "../../inc/DefaultSettings.h" +#include "winrt/Microsoft.Terminal.TerminalConnection.h" using namespace winrt::Microsoft::Terminal::Settings; using namespace ::TerminalApp; @@ -239,26 +240,13 @@ void CascadiaSettings::_CreateDefaultProfiles() powershellProfile.SetDefaultBackground(POWERSHELL_BLUE); powershellProfile.SetUseAcrylic(false); - // The Azure connection has a boost dependency, and boost does not support ARM64 - // so we don't create a default profile for the Azure cloud shell if we're in ARM64 -#ifndef _M_ARM64 - auto azureCloudShellProfile{ _CreateDefaultProfile(L"Azure Cloud Shell") }; - azureCloudShellProfile.SetCommandline(L"Azure"); - azureCloudShellProfile.SetStartingDirectory(DEFAULT_STARTING_DIRECTORY); - azureCloudShellProfile.SetColorScheme({ L"Solarized Dark" }); - azureCloudShellProfile.SetAcrylicOpacity(0.85); - azureCloudShellProfile.SetUseAcrylic(true); - azureCloudShellProfile.SetCloseOnExit(false); - azureCloudShellProfile.SetConnectionType(AzureConnectionType); -#endif - // If the user has installed PowerShell Core, we add PowerShell Core as a default. // PowerShell Core default folder is "%PROGRAMFILES%\PowerShell\[Version]\". std::filesystem::path psCoreCmdline{}; if (_isPowerShellCoreInstalled(psCoreCmdline)) { auto pwshProfile{ _CreateDefaultProfile(L"PowerShell Core") }; - pwshProfile.SetCommandline(psCoreCmdline); + pwshProfile.SetCommandline(std::move(psCoreCmdline)); pwshProfile.SetStartingDirectory(DEFAULT_STARTING_DIRECTORY); pwshProfile.SetColorScheme({ L"Campbell" }); @@ -274,9 +262,20 @@ void CascadiaSettings::_CreateDefaultProfiles() _profiles.emplace_back(powershellProfile); _profiles.emplace_back(cmdProfile); -#ifndef _M_ARM64 - _profiles.emplace_back(azureCloudShellProfile); -#endif + + if (winrt::Microsoft::Terminal::TerminalConnection::AzureConnection::IsAzureConnectionAvailable()) + { + auto azureCloudShellProfile{ _CreateDefaultProfile(L"Azure Cloud Shell") }; + azureCloudShellProfile.SetCommandline(L"Azure"); + azureCloudShellProfile.SetStartingDirectory(DEFAULT_STARTING_DIRECTORY); + azureCloudShellProfile.SetColorScheme({ L"Vintage" }); + azureCloudShellProfile.SetAcrylicOpacity(0.6); + azureCloudShellProfile.SetUseAcrylic(true); + azureCloudShellProfile.SetCloseOnExit(false); + azureCloudShellProfile.SetConnectionType(AzureConnectionType); + _profiles.emplace_back(azureCloudShellProfile); + } + try { _AppendWslProfiles(_profiles); @@ -297,6 +296,11 @@ void CascadiaSettings::_CreateDefaultKeybindings() keyBindings.SetKeyBinding(ShortcutAction::NewTab, KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift, static_cast('T') }); + + keyBindings.SetKeyBinding(ShortcutAction::OpenNewTabDropdown, + KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift, + static_cast(' ') }); + keyBindings.SetKeyBinding(ShortcutAction::DuplicateTab, KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift, static_cast('D') }); @@ -644,3 +648,132 @@ Profile CascadiaSettings::_CreateDefaultProfile(const std::wstring_view name) return newProfile; } + +// Method Description: +// - Gets our list of warnings we found during loading. These are things that we +// knew were bad when we called `_ValidateSettings` last. +// Return Value: +// - a reference to our list of warnings. +std::vector& CascadiaSettings::GetWarnings() +{ + return _warnings; +} + +// Method Description: +// - Attempts to validate this settings structure. If there are critical errors +// found, they'll be thrown as a SettingsLoadError. Non-critical errors, such +// as not finding the default profile, will only result in an error. We'll add +// all these warnings to our list of warnings, and the application can chose +// to display these to the user. +// Arguments: +// - +// Return Value: +// - +void CascadiaSettings::_ValidateSettings() +{ + _warnings.clear(); + + // Make sure to check that profiles exists at all first and foremost: + _ValidateProfilesExist(); + + // Then do some validation on the profiles. The order of these does not + // terribly matter. + _ValidateNoDuplicateProfiles(); + _ValidateDefaultProfileExists(); +} + +// Method Description: +// - Checks if the settings contain profiles at all. As we'll need to have some +// profiles at all, we'll throw an error if there aren't any profiles. +void CascadiaSettings::_ValidateProfilesExist() +{ + const bool hasProfiles = !_profiles.empty(); + if (!hasProfiles) + { + // Throw an exception. This is an invalid state, and we want the app to + // be able to gracefully use the default settings. + + // We can't add the warning to the list of warnings here, because this + // object is not going to be returned at any point. + + throw ::TerminalApp::SettingsException(::TerminalApp::SettingsLoadErrors::NoProfiles); + } +} + +// Method Description: +// - Checks if the "globals.defaultProfile" is set to one of the profiles we +// actually have. If the value is unset, or the value is set to something that +// doesn't exist in the list of profiles, we'll arbitrarily pick the first +// profile to use temporarily as the default. +// - Appends a SettingsLoadWarnings::MissingDefaultProfile to our list of +// warnings if we failed to find the default. +void CascadiaSettings::_ValidateDefaultProfileExists() +{ + const auto defaultProfileGuid = GlobalSettings().GetDefaultProfile(); + const bool nullDefaultProfile = defaultProfileGuid == GUID{}; + bool defaultProfileNotInProfiles = true; + for (const auto& profile : _profiles) + { + if (profile.GetGuid() == defaultProfileGuid) + { + defaultProfileNotInProfiles = false; + break; + } + } + + if (nullDefaultProfile || defaultProfileNotInProfiles) + { + _warnings.push_back(::TerminalApp::SettingsLoadWarnings::MissingDefaultProfile); + // Use the first profile as the new default + + // _temporarily_ set the default profile to the first profile. Because + // we're adding a warning, this settings change won't be re-serialized. + GlobalSettings().SetDefaultProfile(_profiles[0].GetGuid()); + } +} + +// Method Description: +// - Checks to make sure there aren't any duplicate profiles in the list of +// profiles. If so, we'll remove the subsequent entries (temporarily), as they +// won't be accessible anyways. +// - Appends a SettingsLoadWarnings::DuplicateProfile to our list of warnings if +// we find any such duplicate. +void CascadiaSettings::_ValidateNoDuplicateProfiles() +{ + bool foundDupe = false; + + std::vector indiciesToDelete{}; + + // Helper to establish an ordering on guids + struct GuidEquality + { + bool operator()(const GUID& lhs, const GUID& rhs) const + { + return memcmp(&lhs, &rhs, sizeof(rhs)) < 0; + } + }; + std::set uniqueGuids{}; + + // Try collecting all the unique guids. If we ever encounter a guid that's + // already in the set, then we need to delete that profile. + for (size_t i = 0; i < _profiles.size(); i++) + { + if (!uniqueGuids.insert(_profiles.at(i).GetGuid()).second) + { + foundDupe = true; + indiciesToDelete.push_back(i); + } + } + + // Remove all the duplicates we've marked + // Walk backwards, so we don't accidentally shift any of the elements + for (auto iter = indiciesToDelete.rbegin(); iter != indiciesToDelete.rend(); iter++) + { + _profiles.erase(_profiles.begin() + *iter); + } + + if (foundDupe) + { + _warnings.push_back(::TerminalApp::SettingsLoadWarnings::DuplicateProfile); + } +} diff --git a/src/cascadia/TerminalApp/CascadiaSettings.h b/src/cascadia/TerminalApp/CascadiaSettings.h index 8124aa08913..703cdaa0990 100644 --- a/src/cascadia/TerminalApp/CascadiaSettings.h +++ b/src/cascadia/TerminalApp/CascadiaSettings.h @@ -3,7 +3,7 @@ Copyright (c) Microsoft Corporation Licensed under the MIT license. Module Name: -- CascadiaSettings.hpp +- CascadiaSettings.h Abstract: - This class acts as the container for all app settings. It's composed of two @@ -18,10 +18,17 @@ Author(s): #pragma once #include #include "GlobalAppSettings.h" +#include "TerminalWarnings.h" #include "Profile.h" static constexpr GUID AzureConnectionType = { 0xd9fcfdfa, 0xa479, 0x412c, { 0x83, 0xb7, 0xc5, 0x64, 0xe, 0x61, 0xcd, 0x62 } }; +// fwdecl unittest classes +namespace TerminalAppLocalTests +{ + class SettingsTests; +} + namespace TerminalApp { class CascadiaSettings; @@ -33,7 +40,7 @@ class TerminalApp::CascadiaSettings final CascadiaSettings(); ~CascadiaSettings(); - static std::unique_ptr LoadAll(const bool saveOnLoad = true); + static std::unique_ptr LoadAll(); void SaveAll() const; winrt::Microsoft::Terminal::Settings::TerminalSettings MakeSettings(std::optional profileGuid) const; @@ -47,15 +54,18 @@ class TerminalApp::CascadiaSettings final Json::Value ToJson() const; static std::unique_ptr FromJson(const Json::Value& json); - static std::wstring GetSettingsPath(); + static std::wstring GetSettingsPath(const bool useRoamingPath = false); const Profile* FindProfile(GUID profileGuid) const noexcept; void CreateDefaults(); + std::vector& GetWarnings(); + private: GlobalAppSettings _globals; std::vector _profiles; + std::vector _warnings{}; void _CreateDefaultKeybindings(); void _CreateDefaultSchemes(); @@ -65,8 +75,15 @@ class TerminalApp::CascadiaSettings final static void _WriteSettings(const std::string_view content); static std::optional _ReadSettings(); + void _ValidateSettings(); + void _ValidateProfilesExist(); + void _ValidateDefaultProfileExists(); + void _ValidateNoDuplicateProfiles(); + static bool _isPowerShellCoreInstalledInPath(const std::wstring_view programFileEnv, std::filesystem::path& cmdline); static bool _isPowerShellCoreInstalled(std::filesystem::path& cmdline); static void _AppendWslProfiles(std::vector& profileStorage); static Profile _CreateDefaultProfile(const std::wstring_view name); + + friend class TerminalAppLocalTests::SettingsTests; }; diff --git a/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp index ee1d18ab93c..d8b35a0a675 100644 --- a/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp @@ -30,18 +30,18 @@ static constexpr std::string_view Utf8Bom{ u8"\uFEFF" }; // it will load the settings from our packaged localappdata. If we're // running as an unpackaged application, it will read it from the path // we've set under localappdata. -// Arguments: -// - saveOnLoad: If true, we'll write the settings back out after we load them, -// to make sure the schema is updated. // Return Value: // - a unique_ptr containing a new CascadiaSettings object. -std::unique_ptr CascadiaSettings::LoadAll(const bool saveOnLoad) +std::unique_ptr CascadiaSettings::LoadAll() { std::unique_ptr resultPtr; std::optional fileData = _ReadSettings(); const bool foundFile = fileData.has_value(); - if (foundFile) + // Make sure the file isn't totally empty. If it is, we'll treat the file + // like it doesn't exist at all. + const bool fileHasData = foundFile && !fileData.value().empty(); + if (foundFile && fileHasData) { const auto actualData = fileData.value(); @@ -59,28 +59,14 @@ std::unique_ptr CascadiaSettings::LoadAll(const bool saveOnLoa // `parse` will return false if it fails. if (!reader->parse(actualDataStart, actualData.c_str() + actualData.size(), &root, &errs)) { - // TODO:GH#990 display this exception text to the user, in a - // copy-pasteable way. + // This will be caught by App::_TryLoadSettings, who will display + // the text to the user. throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs)); } resultPtr = FromJson(root); - if (resultPtr->GlobalSettings().GetDefaultProfile() == GUID{}) - { - throw winrt::hresult_invalid_argument(); - } - - if (saveOnLoad) - { - // Logically compare the json we've parsed from the file to what - // we'd serialize at runtime. If the values are different, then - // write the updated schema back out. - const Json::Value reserialized = resultPtr->ToJson(); - if (reserialized != root) - { - resultPtr->SaveAll(); - } - } + // If this throws, the app will catch it and use the default settings (temporarily) + resultPtr->_ValidateSettings(); } else { @@ -261,25 +247,77 @@ void CascadiaSettings::_WriteSettings(const std::string_view content) // from reading the file std::optional CascadiaSettings::_ReadSettings() { - auto pathToSettingsFile{ CascadiaSettings::GetSettingsPath() }; - const auto hFile = CreateFileW(pathToSettingsFile.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (hFile == INVALID_HANDLE_VALUE) + const auto pathToSettingsFile{ CascadiaSettings::GetSettingsPath() }; + wil::unique_hfile hFile{ CreateFileW(pathToSettingsFile.c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr) }; + + if (!hFile) { - // If the file doesn't exist, that's fine. Just log the error and return - // nullopt - we'll create the defaults. - LOG_LAST_ERROR(); - return std::nullopt; + // GH#1770 - Now that we're _not_ roaming our settings, do a quick check + // to see if there's a file in the Roaming App data folder. If there is + // a file there, but not in the LocalAppData, it's likely the user is + // upgrading from a version of the terminal from before this change. + // We'll try moving the file from the Roaming app data folder to the + // local appdata folder. + + const auto pathToRoamingSettingsFile{ CascadiaSettings::GetSettingsPath(true) }; + wil::unique_hfile hRoamingFile{ CreateFileW(pathToRoamingSettingsFile.c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr) }; + + if (hRoamingFile) + { + // Close the file handle, move it, and re-open the file in its new location. + hRoamingFile.reset(); + + // Note: We're unsure if this is unsafe. Theoretically it's possible + // that two instances of the app will try and move the settings file + // simultaneously. We don't know what might happen in that scenario, + // but we're also not sure how to safely lock the file to prevent + // that from ocurring. + THROW_LAST_ERROR_IF(!MoveFile(pathToRoamingSettingsFile.c_str(), + pathToSettingsFile.c_str())); + + hFile.reset(CreateFileW(pathToSettingsFile.c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr)); + + // hFile shouldn't be INVALID. That's unexpected - We just moved the + // file, we should be able to open it. Throw the error so we can get + // some information here. + THROW_LAST_ERROR_IF(!hFile); + } + else + { + // If the roaming file didn't exist, and the local file doesn't exist, + // that's fine. Just log the error and return nullopt - we'll + // create the defaults. + LOG_LAST_ERROR(); + return std::nullopt; + } } // fileSize is in bytes - const auto fileSize = GetFileSize(hFile, nullptr); + const auto fileSize = GetFileSize(hFile.get(), nullptr); THROW_LAST_ERROR_IF(fileSize == INVALID_FILE_SIZE); auto utf8buffer = std::make_unique(fileSize); DWORD bytesRead = 0; - THROW_LAST_ERROR_IF(!ReadFile(hFile, utf8buffer.get(), fileSize, &bytesRead, nullptr)); - CloseHandle(hFile); + THROW_LAST_ERROR_IF(!ReadFile(hFile.get(), utf8buffer.get(), fileSize, &bytesRead, nullptr)); // convert buffer to UTF-8 string std::string utf8string(utf8buffer.get(), fileSize); @@ -289,25 +327,27 @@ std::optional CascadiaSettings::_ReadSettings() // function Description: // - Returns the full path to the settings file, either within the application -// package, or in its unpackaged location. +// package, or in its unpackaged location. This path is under the "Local +// AppData" folder, so it _doesn't_ roam to other machines. // - If the application is unpackaged, -// the file will end up under e.g. C:\Users\admin\AppData\Roaming\Microsoft\Windows Terminal\profiles.json +// the file will end up under e.g. C:\Users\admin\AppData\Local\Microsoft\Windows Terminal\profiles.json // Arguments: // - // Return Value: // - the full path to the settings file -std::wstring CascadiaSettings::GetSettingsPath() +std::wstring CascadiaSettings::GetSettingsPath(const bool useRoamingPath) { - wil::unique_cotaskmem_string roamingAppDataFolder; + wil::unique_cotaskmem_string localAppDataFolder; // KF_FLAG_FORCE_APP_DATA_REDIRECTION, when engaged, causes SHGet... to return // the new AppModel paths (Packages/xxx/RoamingState, etc.) for standard path requests. // Using this flag allows us to avoid Windows.Storage.ApplicationData completely. - if (FAILED(SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_FORCE_APP_DATA_REDIRECTION, 0, &roamingAppDataFolder))) + const auto knowFolderId = useRoamingPath ? FOLDERID_RoamingAppData : FOLDERID_LocalAppData; + if (FAILED(SHGetKnownFolderPath(knowFolderId, KF_FLAG_FORCE_APP_DATA_REDIRECTION, 0, &localAppDataFolder))) { THROW_LAST_ERROR(); } - std::filesystem::path parentDirectoryForSettingsFile{ roamingAppDataFolder.get() }; + std::filesystem::path parentDirectoryForSettingsFile{ localAppDataFolder.get() }; if (!_IsPackaged()) { diff --git a/src/cascadia/TerminalApp/ColorScheme.cpp b/src/cascadia/TerminalApp/ColorScheme.cpp index cc7ffd3cc68..4fe57f7b00c 100644 --- a/src/cascadia/TerminalApp/ColorScheme.cpp +++ b/src/cascadia/TerminalApp/ColorScheme.cpp @@ -66,7 +66,8 @@ void ColorScheme::ApplyScheme(TerminalSettings terminalSettings) const terminalSettings.DefaultForeground(_defaultForeground); terminalSettings.DefaultBackground(_defaultBackground); - for (int i = 0; i < _table.size(); i++) + auto const tableCount = gsl::narrow_cast(_table.size()); + for (int i = 0; i < tableCount; i++) { terminalSettings.SetColorTableEntry(i, _table[i]); } diff --git a/src/cascadia/TerminalApp/FixVisualStudioBug.targets b/src/cascadia/TerminalApp/FixVisualStudioBug.targets new file mode 100644 index 00000000000..465874ad1b9 --- /dev/null +++ b/src/cascadia/TerminalApp/FixVisualStudioBug.targets @@ -0,0 +1,30 @@ + + + + + + <_TerminalAppLibProjectReference Include="@(_ResolvedProjectReferencePaths)" Condition="'%(Filename)' == 'TerminalApp'" /> + <_ResolvedProjectReferencePaths Remove="@(_TerminalAppLibProjectReference)" /> + <_ResolvedProjectReferencePaths Include="@(_TerminalAppLibProjectReference)"> + + + + + + + + <_ResolvedProjectReferencePaths Remove="@(_TerminalAppLibProjectReference)" /> + <_ResolvedProjectReferencePaths Include="@(_TerminalAppLibProjectReference)" /> + + + + diff --git a/src/cascadia/TerminalApp/GlobalAppSettings.cpp b/src/cascadia/TerminalApp/GlobalAppSettings.cpp index 4f6a9d8d396..eba5beedca7 100644 --- a/src/cascadia/TerminalApp/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalApp/GlobalAppSettings.cpp @@ -24,6 +24,7 @@ static constexpr std::string_view ShowTitleInTitlebarKey{ "showTerminalTitleInTi static constexpr std::string_view RequestedThemeKey{ "requestedTheme" }; static constexpr std::string_view ShowTabsInTitlebarKey{ "showTabsInTitlebar" }; static constexpr std::string_view WordDelimitersKey{ "wordDelimiters" }; +static constexpr std::string_view CopyOnSelectKey{ "copyOnSelect" }; static constexpr std::wstring_view LightThemeValue{ L"light" }; static constexpr std::wstring_view DarkThemeValue{ L"dark" }; @@ -39,7 +40,8 @@ GlobalAppSettings::GlobalAppSettings() : _showTitleInTitlebar{ true }, _showTabsInTitlebar{ true }, _requestedTheme{ ElementTheme::Default }, - _wordDelimiters{ DEFAULT_WORD_DELIMITERS } + _wordDelimiters{ DEFAULT_WORD_DELIMITERS }, + _copyOnSelect{ false } { } @@ -117,6 +119,16 @@ void GlobalAppSettings::SetWordDelimiters(const std::wstring wordDelimiters) noe _wordDelimiters = wordDelimiters; } +bool GlobalAppSettings::GetCopyOnSelect() const noexcept +{ + return _copyOnSelect; +} + +void GlobalAppSettings::SetCopyOnSelect(const bool copyOnSelect) noexcept +{ + _copyOnSelect = copyOnSelect; +} + #pragma region ExperimentalSettings bool GlobalAppSettings::GetShowTabsInTitlebar() const noexcept { @@ -141,6 +153,7 @@ void GlobalAppSettings::ApplyToSettings(TerminalSettings& settings) const noexce settings.InitialRows(_initialRows); settings.InitialCols(_initialCols); settings.WordDelimiters(_wordDelimiters); + settings.CopyOnSelect(_copyOnSelect); } // Method Description: @@ -160,6 +173,7 @@ Json::Value GlobalAppSettings::ToJson() const jsonObject[JsonKey(ShowTitleInTitlebarKey)] = _showTitleInTitlebar; jsonObject[JsonKey(ShowTabsInTitlebarKey)] = _showTabsInTitlebar; jsonObject[JsonKey(WordDelimitersKey)] = winrt::to_string(_wordDelimiters); + jsonObject[JsonKey(CopyOnSelectKey)] = _copyOnSelect; jsonObject[JsonKey(RequestedThemeKey)] = winrt::to_string(_SerializeTheme(_requestedTheme)); jsonObject[JsonKey(KeybindingsKey)] = AppKeyBindingsSerialization::ToJson(_keybindings); @@ -210,6 +224,11 @@ GlobalAppSettings GlobalAppSettings::FromJson(const Json::Value& json) result._wordDelimiters = GetWstringFromJson(wordDelimiters); } + if (auto copyOnSelect{ json[JsonKey(CopyOnSelectKey)] }) + { + result._copyOnSelect = copyOnSelect.asBool(); + } + if (auto requestedTheme{ json[JsonKey(RequestedThemeKey)] }) { result._requestedTheme = _ParseTheme(GetWstringFromJson(requestedTheme)); diff --git a/src/cascadia/TerminalApp/GlobalAppSettings.h b/src/cascadia/TerminalApp/GlobalAppSettings.h index 293ba743233..11dab24ccaa 100644 --- a/src/cascadia/TerminalApp/GlobalAppSettings.h +++ b/src/cascadia/TerminalApp/GlobalAppSettings.h @@ -50,6 +50,9 @@ class TerminalApp::GlobalAppSettings final std::wstring GetWordDelimiters() const noexcept; void SetWordDelimiters(const std::wstring wordDelimiters) noexcept; + bool GetCopyOnSelect() const noexcept; + void SetCopyOnSelect(const bool copyOnSelect) noexcept; + winrt::Windows::UI::Xaml::ElementTheme GetRequestedTheme() const noexcept; Json::Value ToJson() const; @@ -72,6 +75,7 @@ class TerminalApp::GlobalAppSettings final bool _showTabsInTitlebar; std::wstring _wordDelimiters; + bool _copyOnSelect; winrt::Windows::UI::Xaml::ElementTheme _requestedTheme; static winrt::Windows::UI::Xaml::ElementTheme _ParseTheme(const std::wstring& themeString) noexcept; diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 173d5ef14a1..3096dddb362 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -19,7 +19,7 @@ Pane::Pane(const GUID& profile, const TermControl& control, const bool lastFocus _lastFocused{ lastFocused }, _profile{ profile } { - _root.Children().Append(_control.GetControl()); + _root.Children().Append(_control); _connectionClosedToken = _control.ConnectionClosed({ this, &Pane::_ControlClosedHandler }); // Set the background of the pane to match that of the theme's default grid @@ -164,26 +164,26 @@ bool Pane::ResizePane(const Direction& direction) { return _Resize(direction); } - else + + // If neither of our children were the focused leaf, then recurse into + // our children and see if they can handle the resize. + // For each child, if it has a focused descendant, try having that child + // handle the resize. + // If the child wasn't able to handle the resize, it's possible that + // there were no descendants with a separator the correct direction. If + // our separator _is_ the correct direction, then we should be the pane + // to resize. Otherwise, just return false, as we couldn't handle it + // either. + if ((!_firstChild->_IsLeaf()) && _firstChild->_HasFocusedChild()) { - // If neither of our children were the focused leaf, then recurse into - // our children and see if they can handle the resize. - // For each child, if it has a focused descendant, try having that child - // handle the resize. - // If the child wasn't able to handle the resize, it's possible that - // there were no descendants with a separator the correct direction. If - // our separator _is_ the correct direction, then we should be the pane - // to resize. Otherwise, just return false, as we couldn't handle it - // either. - if ((!_firstChild->_IsLeaf()) && _firstChild->_HasFocusedChild()) - { - return _firstChild->ResizePane(direction) || _Resize(direction); - } - else if ((!_secondChild->_IsLeaf()) && _secondChild->_HasFocusedChild()) - { - return _secondChild->ResizePane(direction) || _Resize(direction); - } + return _firstChild->ResizePane(direction) || _Resize(direction); + } + + if ((!_secondChild->_IsLeaf()) && _secondChild->_HasFocusedChild()) + { + return _secondChild->ResizePane(direction) || _Resize(direction); } + return false; } @@ -253,26 +253,26 @@ bool Pane::NavigateFocus(const Direction& direction) { return _NavigateFocus(direction); } - else + + // If neither of our children were the focused leaf, then recurse into + // our children and see if they can handle the focus move. + // For each child, if it has a focused descendant, try having that child + // handle the focus move. + // If the child wasn't able to handle the focus move, it's possible that + // there were no descendants with a separator the correct direction. If + // our separator _is_ the correct direction, then we should be the pane + // to move focus into our other child. Otherwise, just return false, as + // we couldn't handle it either. + if ((!_firstChild->_IsLeaf()) && _firstChild->_HasFocusedChild()) { - // If neither of our children were the focused leaf, then recurse into - // our children and see if they can handle the focus move. - // For each child, if it has a focused descendant, try having that child - // handle the focus move. - // If the child wasn't able to handle the focus move, it's possible that - // there were no descendants with a separator the correct direction. If - // our separator _is_ the correct direction, then we should be the pane - // to move focus into our other child. Otherwise, just return false, as - // we couldn't handle it either. - if ((!_firstChild->_IsLeaf()) && _firstChild->_HasFocusedChild()) - { - return _firstChild->NavigateFocus(direction) || _NavigateFocus(direction); - } - else if ((!_secondChild->_IsLeaf()) && _secondChild->_HasFocusedChild()) - { - return _secondChild->NavigateFocus(direction) || _NavigateFocus(direction); - } + return _firstChild->NavigateFocus(direction) || _NavigateFocus(direction); + } + + if ((!_secondChild->_IsLeaf()) && _secondChild->_HasFocusedChild()) + { + return _secondChild->NavigateFocus(direction) || _NavigateFocus(direction); } + return false; } @@ -348,15 +348,13 @@ std::shared_ptr Pane::GetFocusedPane() { return _lastFocused ? shared_from_this() : nullptr; } - else + + auto firstFocused = _firstChild->GetFocusedPane(); + if (firstFocused != nullptr) { - auto firstFocused = _firstChild->GetFocusedPane(); - if (firstFocused != nullptr) - { - return firstFocused; - } - return _secondChild->GetFocusedPane(); + return firstFocused; } + return _secondChild->GetFocusedPane(); } // Method Description: @@ -426,7 +424,7 @@ bool Pane::_HasFocusedChild() const noexcept // We're intentionally making this one giant expression, so the compiler // will skip the following lookups if one of the lookups before it returns // true - return (_control && _control.GetControl().FocusState() != FocusState::Unfocused) || + return (_control && _control.FocusState() != FocusState::Unfocused) || (_firstChild && _firstChild->_HasFocusedChild()) || (_secondChild && _secondChild->_HasFocusedChild()); } @@ -445,7 +443,7 @@ void Pane::UpdateFocus() if (_IsLeaf()) { const auto controlFocused = _control && - _control.GetControl().FocusState() != FocusState::Unfocused; + _control.FocusState() != FocusState::Unfocused; _lastFocused = controlFocused; } @@ -468,7 +466,7 @@ void Pane::_FocusFirstChild() { if (_IsLeaf()) { - _control.GetControl().Focus(FocusState::Programmatic); + _control.Focus(FocusState::Programmatic); } else { @@ -564,11 +562,11 @@ void Pane::_CloseChild(const bool closeFirst) _separatorRoot = { nullptr }; // Reattach the TermControl to our grid. - _root.Children().Append(_control.GetControl()); + _root.Children().Append(_control); if (_lastFocused) { - _control.GetControl().Focus(FocusState::Programmatic); + _control.Focus(FocusState::Programmatic); } _splitState = SplitState::None; @@ -778,60 +776,90 @@ void Pane::_ApplySplitDefinitions() } // Method Description: -// - Vertically split the focused pane in our tree of panes, and place the given -// TermControl into the newly created pane. If we're the focused pane, then -// we'll create two new children, and place them side-by-side in our Grid. +// - Determines whether the pane can be split // Arguments: -// - profile: The profile GUID to associate with the newly created pane. -// - control: A TermControl to use in the new pane. +// - splitType: what type of split we want to create. // Return Value: -// - -void Pane::SplitVertical(const GUID& profile, const TermControl& control) +// - True if the pane can be split. False otherwise. +bool Pane::CanSplit(SplitState splitType) { - // If we're not the leaf, recurse into our children to split them. - if (!_IsLeaf()) + if (_IsLeaf()) { - if (_firstChild->_HasFocusedChild()) - { - _firstChild->SplitVertical(profile, control); - } - else if (_secondChild->_HasFocusedChild()) - { - _secondChild->SplitVertical(profile, control); - } + return _CanSplit(splitType); + } - return; + if (_firstChild->_HasFocusedChild()) + { + return _firstChild->CanSplit(splitType); + } + + if (_secondChild->_HasFocusedChild()) + { + return _secondChild->CanSplit(splitType); } - _Split(SplitState::Vertical, profile, control); + return false; } // Method Description: -// - Horizontally split the focused pane in our tree of panes, and place the given +// - Split the focused pane in our tree of panes, and place the given // TermControl into the newly created pane. If we're the focused pane, then // we'll create two new children, and place them side-by-side in our Grid. // Arguments: +// - splitType: what type of split we want to create. // - profile: The profile GUID to associate with the newly created pane. // - control: A TermControl to use in the new pane. // Return Value: // - -void Pane::SplitHorizontal(const GUID& profile, const TermControl& control) +void Pane::Split(SplitState splitType, const GUID& profile, const TermControl& control) { if (!_IsLeaf()) { if (_firstChild->_HasFocusedChild()) { - _firstChild->SplitHorizontal(profile, control); + _firstChild->Split(splitType, profile, control); } else if (_secondChild->_HasFocusedChild()) { - _secondChild->SplitHorizontal(profile, control); + _secondChild->Split(splitType, profile, control); } return; } - _Split(SplitState::Horizontal, profile, control); + _Split(splitType, profile, control); +} + +// Method Description: +// - Determines whether the pane can be split. +// Arguments: +// - splitType: what type of split we want to create. +// Return Value: +// - True if the pane can be split. False otherwise. +bool Pane::_CanSplit(SplitState splitType) +{ + const Size actualSize{ gsl::narrow_cast(_root.ActualWidth()), + gsl::narrow_cast(_root.ActualHeight()) }; + + const Size minSize = _GetMinSize(); + + if (splitType == SplitState::Vertical) + { + const auto widthMinusSeparator = actualSize.Width - PaneSeparatorSize; + const auto newWidth = widthMinusSeparator * Half; + + return newWidth > minSize.Width; + } + + if (splitType == SplitState::Horizontal) + { + const auto heightMinusSeparator = actualSize.Height - PaneSeparatorSize; + const auto newHeight = heightMinusSeparator * Half; + + return newHeight > minSize.Height; + } + + return false; } // Method Description: @@ -922,14 +950,12 @@ Size Pane::_GetMinSize() const { return _control.MinimumSize(); } - else - { - const auto firstSize = _firstChild->_GetMinSize(); - const auto secondSize = _secondChild->_GetMinSize(); - const auto newWidth = firstSize.Width + secondSize.Width + (_splitState == SplitState::Vertical ? PaneSeparatorSize : 0); - const auto newHeight = firstSize.Height + secondSize.Height + (_splitState == SplitState::Horizontal ? PaneSeparatorSize : 0); - return { newWidth, newHeight }; - } + + const auto firstSize = _firstChild->_GetMinSize(); + const auto secondSize = _secondChild->_GetMinSize(); + const auto newWidth = firstSize.Width + secondSize.Width + (_splitState == SplitState::Vertical ? PaneSeparatorSize : 0); + const auto newHeight = firstSize.Height + secondSize.Height + (_splitState == SplitState::Horizontal ? PaneSeparatorSize : 0); + return { newWidth, newHeight }; } DEFINE_EVENT(Pane, Closed, _closedHandlers, ConnectionClosedEventArgs); diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index 2fb4a2e499f..d526153720e 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -49,8 +49,8 @@ class Pane : public std::enable_shared_from_this bool ResizePane(const winrt::TerminalApp::Direction& direction); bool NavigateFocus(const winrt::TerminalApp::Direction& direction); - void SplitHorizontal(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); - void SplitVertical(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); + bool CanSplit(SplitState splitType); + void Split(SplitState splitType, const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); void Close(); @@ -79,6 +79,7 @@ class Pane : public std::enable_shared_from_this bool _HasFocusedChild() const noexcept; void _SetupChildCloseHandlers(); + bool _CanSplit(SplitState splitType); void _Split(SplitState splitType, const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); void _CreateRowColDefinitions(const winrt::Windows::Foundation::Size& rootSize); void _CreateSplitContent(); diff --git a/src/cascadia/TerminalApp/Profile.cpp b/src/cascadia/TerminalApp/Profile.cpp index b73baf20609..5863c3aae38 100644 --- a/src/cascadia/TerminalApp/Profile.cpp +++ b/src/cascadia/TerminalApp/Profile.cpp @@ -150,7 +150,8 @@ TerminalSettings Profile::CreateTerminalSettings(const std::vector& TerminalSettings terminalSettings{}; // Fill in the Terminal Setting's CoreSettings from the profile - for (int i = 0; i < _colorTable.size(); i++) + auto const colorTableCount = gsl::narrow_cast(_colorTable.size()); + for (int i = 0; i < colorTableCount; i++) { terminalSettings.SetColorTableEntry(i, _colorTable[i]); } @@ -177,6 +178,10 @@ TerminalSettings Profile::CreateTerminalSettings(const std::vector& terminalSettings.StartingDirectory(winrt::to_hstring(evaluatedDirectory.c_str())); } + // GH#2373: Use the tabTitle as the starting title if it exists, otherwise + // use the profile name + terminalSettings.StartingTitle(_tabTitle ? _tabTitle.value() : _name); + if (_schemeName) { const ColorScheme* const matchingScheme = _FindScheme(schemes, _schemeName.value()); @@ -352,7 +357,17 @@ Profile Profile::FromJson(const Json::Value& json) } else { - result._guid = Utils::CreateGuid(); + // Always use the name to generate the temporary GUID. That way, across + // reloads, we'll generate the same static GUID. + const std::wstring_view name = result._name; + result._guid = Utils::CreateV5Uuid(RUNTIME_GENERATED_PROFILE_NAMESPACE_GUID, gsl::as_bytes(gsl::make_span(name))); + + TraceLoggingWrite( + g_hTerminalAppProvider, + "SynthesizedGuidForProfile", + TraceLoggingDescription("Event emitted when a profile is deserialized without a GUID"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); } // Core Settings @@ -482,12 +497,12 @@ Profile Profile::FromJson(const Json::Value& json) void Profile::SetFontFace(std::wstring fontFace) noexcept { - _fontFace = fontFace; + _fontFace = std::move(fontFace); } void Profile::SetColorScheme(std::optional schemeName) noexcept { - _schemeName = schemeName; + _schemeName = std::move(schemeName); } void Profile::SetAcrylicOpacity(double opacity) noexcept @@ -497,17 +512,17 @@ void Profile::SetAcrylicOpacity(double opacity) noexcept void Profile::SetCommandline(std::wstring cmdline) noexcept { - _commandline = cmdline; + _commandline = std::move(cmdline); } void Profile::SetStartingDirectory(std::wstring startingDirectory) noexcept { - _startingDirectory = startingDirectory; + _startingDirectory = std::move(startingDirectory); } void Profile::SetName(std::wstring name) noexcept { - _name = name; + _name = std::move(name); } void Profile::SetUseAcrylic(bool useAcrylic) noexcept @@ -546,27 +561,33 @@ bool Profile::HasIcon() const noexcept // - tabTitle: the tab title void Profile::SetTabTitle(std::wstring tabTitle) noexcept { - _tabTitle = tabTitle; + _tabTitle = std::move(tabTitle); } // Method Description: // - Sets this profile's icon path. // Arguments: // - path: the path -void Profile::SetIconPath(std::wstring_view path) noexcept +void Profile::SetIconPath(std::wstring_view path) { + static_assert(!noexcept(_icon.emplace(path))); _icon.emplace(path); } // Method Description: -// - Returns this profile's icon path, if one is set. Otherwise returns the empty string. +// - Returns this profile's icon path, if one is set. Otherwise returns the +// empty string. This method will expand any environment variables in the +// path, if there are any. // Return Value: // - this profile's icon path, if one is set. Otherwise returns the empty string. -std::wstring_view Profile::GetIconPath() const noexcept +winrt::hstring Profile::GetExpandedIconPath() const { - return HasIcon() ? - std::wstring_view{ _icon.value().c_str(), _icon.value().size() } : - std::wstring_view{ L"", 0 }; + if (!HasIcon()) + { + return { L"" }; + } + winrt::hstring envExpandedPath{ wil::ExpandEnvironmentStringsW(_icon.value().data()) }; + return envExpandedPath; } // Method Description: @@ -580,26 +601,6 @@ std::wstring_view Profile::GetName() const noexcept return _name; } -// Method Description: -// - Returns true if profile's custom tab title is set, if one is set. Otherwise returns false. -// Return Value: -// - true if this profile's custom tab title is set. Otherwise returns false. -bool Profile::HasTabTitle() const noexcept -{ - return _tabTitle.has_value(); -} - -// Method Description: -// - Returns the custom tab title, if one is set. Otherwise returns the empty string. -// Return Value: -// - this profile's custom tab title, if one is set. Otherwise returns the empty string. -std::wstring_view Profile::GetTabTitle() const noexcept -{ - return HasTabTitle() ? - std::wstring_view{ _tabTitle.value().c_str(), _tabTitle.value().size() } : - std::wstring_view{ L"", 0 }; -} - bool Profile::HasConnectionType() const noexcept { return _connectionType.has_value(); diff --git a/src/cascadia/TerminalApp/Profile.h b/src/cascadia/TerminalApp/Profile.h index 55054a43636..990bbc728c0 100644 --- a/src/cascadia/TerminalApp/Profile.h +++ b/src/cascadia/TerminalApp/Profile.h @@ -16,6 +16,10 @@ Author(s): #pragma once #include "ColorScheme.h" +// GUID used for generating GUIDs at runtime, for profiles that did not have a +// GUID specified manually. +constexpr GUID RUNTIME_GENERATED_PROFILE_NAMESPACE_GUID = { 0xf65ddb7e, 0x706b, 0x4499, { 0x8a, 0x50, 0x40, 0x31, 0x3c, 0xaf, 0x51, 0x0a } }; + namespace TerminalApp { class Profile; @@ -36,8 +40,6 @@ class TerminalApp::Profile final GUID GetGuid() const noexcept; std::wstring_view GetName() const noexcept; - bool HasTabTitle() const noexcept; - std::wstring_view GetTabTitle() const noexcept; bool HasConnectionType() const noexcept; GUID GetConnectionType() const noexcept; @@ -55,8 +57,8 @@ class TerminalApp::Profile final void SetConnectionType(GUID connectionType) noexcept; bool HasIcon() const noexcept; - std::wstring_view GetIconPath() const noexcept; - void SetIconPath(std::wstring_view path) noexcept; + winrt::hstring GetExpandedIconPath() const; + void SetIconPath(std::wstring_view path); bool GetCloseOnExit() const noexcept; diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index 8173dcac8e8..c461da5b42b 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -1,17 +1,17 @@ ο»Ώ - @@ -118,17 +118,39 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Settings could not be loaded from file - temporarily using the default settings. Check for syntax errors, including trailing commas. + Settings could not be loaded from file. Check for syntax errors, including trailing commas. + + + + Could not find your default profile in your list of profiles - using the first profile. Check to make sure the defaultProfile matches the GUID of one of your profiles. + + + + Found multiple profiles with the same GUID in your settings file - ignoring duplicates. Make sure each profile's GUID is unique. + + + + No profiles were found in your settings. + + + + Settings could not be reloaded from file. Check for syntax errors, including trailing commas. + + + + +Temporarily using the Windows Terminal default settings. + Failed to load settings + + Encountered errors while loading user settings + Ok - - Settings could not be reloaded from file. Check for syntax errors, including trailing commas. - Failed to reload settings @@ -171,4 +193,4 @@ Settings - \ No newline at end of file + diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 94acf9feee1..c4e40032695 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "Tab.h" +#include "Utils.h" using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::UI::Core; @@ -124,7 +125,7 @@ void Tab::_Focus() auto lastFocusedControl = _rootPane->GetFocusedTerminalControl(); if (lastFocusedControl) { - lastFocusedControl.GetControl().Focus(FocusState::Programmatic); + lastFocusedControl.Focus(FocusState::Programmatic); } } @@ -142,6 +143,21 @@ void Tab::UpdateFocus() _rootPane->UpdateFocus(); } +void Tab::UpdateIcon(const winrt::hstring iconPath) +{ + // Don't reload our icon if it hasn't changed. + if (iconPath == _lastIconPath) + { + return; + } + + _lastIconPath = iconPath; + + _tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() { + _tabViewItem.Icon(GetColoredIcon(_lastIconPath)); + }); +} + // Method Description: // - Gets the title string of the last focused terminal control in our tree. // Returns the empty string if there is no such control. @@ -181,36 +197,35 @@ void Tab::SetTabText(const winrt::hstring& text) void Tab::Scroll(const int delta) { auto control = GetFocusedTerminalControl(); - control.GetControl().Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [control, delta]() { + control.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [control, delta]() { const auto currentOffset = control.GetScrollOffset(); control.KeyboardScrollViewport(currentOffset + delta); }); } // Method Description: -// - Vertically split the focused pane in our tree of panes, and place the -// given TermControl into the newly created pane. +// - Determines whether the focused pane has sufficient space to be split. // Arguments: -// - profile: The profile GUID to associate with the newly created pane. -// - control: A TermControl to use in the new pane. +// - splitType: The type of split we want to create. // Return Value: -// - -void Tab::AddVerticalSplit(const GUID& profile, TermControl& control) +// - True if the focused pane can be split. False otherwise. +bool Tab::CanSplitPane(Pane::SplitState splitType) { - _rootPane->SplitVertical(profile, control); + return _rootPane->CanSplit(splitType); } // Method Description: -// - Horizontally split the focused pane in our tree of panes, and place the +// - Split the focused pane in our tree of panes, and place the // given TermControl into the newly created pane. // Arguments: +// - splitType: The type of split we want to create. // - profile: The profile GUID to associate with the newly created pane. // - control: A TermControl to use in the new pane. // Return Value: // - -void Tab::AddHorizontalSplit(const GUID& profile, TermControl& control) +void Tab::SplitPane(Pane::SplitState splitType, const GUID& profile, TermControl& control) { - _rootPane->SplitHorizontal(profile, control); + _rootPane->Split(splitType, profile, control); } // Method Description: diff --git a/src/cascadia/TerminalApp/Tab.h b/src/cascadia/TerminalApp/Tab.h index 7e033ff323f..8af4423e6b8 100644 --- a/src/cascadia/TerminalApp/Tab.h +++ b/src/cascadia/TerminalApp/Tab.h @@ -19,10 +19,13 @@ class Tab void SetFocused(const bool focused); void Scroll(const int delta); - void AddVerticalSplit(const GUID& profile, winrt::Microsoft::Terminal::TerminalControl::TermControl& control); - void AddHorizontalSplit(const GUID& profile, winrt::Microsoft::Terminal::TerminalControl::TermControl& control); + + bool CanSplitPane(Pane::SplitState splitType); + void SplitPane(Pane::SplitState splitType, const GUID& profile, winrt::Microsoft::Terminal::TerminalControl::TermControl& control); void UpdateFocus(); + void UpdateIcon(const winrt::hstring iconPath); + void ResizeContent(const winrt::Windows::Foundation::Size& newSize); void ResizePane(const winrt::TerminalApp::Direction& direction); void NavigateFocus(const winrt::TerminalApp::Direction& direction); @@ -37,6 +40,7 @@ class Tab private: std::shared_ptr _rootPane{ nullptr }; + winrt::hstring _lastIconPath{}; bool _focused{ false }; winrt::Microsoft::UI::Xaml::Controls::TabViewItem _tabViewItem{ nullptr }; diff --git a/src/cascadia/TerminalApp/TerminalApp.vcxproj b/src/cascadia/TerminalApp/TerminalApp.vcxproj index 05957cdb670..207c8923647 100644 --- a/src/cascadia/TerminalApp/TerminalApp.vcxproj +++ b/src/cascadia/TerminalApp/TerminalApp.vcxproj @@ -99,38 +99,18 @@ User32.lib;WindowsApp.lib;shell32.lib;%(AdditionalDependencies) + + + /INCLUDE:_DllMain@12 + /INCLUDE:DllMain - - - - <_TerminalAppLibProjectReference Include="@(_ResolvedProjectReferencePaths)" Condition="'%(Filename)' == 'TerminalApp'" /> - <_ResolvedProjectReferencePaths Remove="@(_TerminalAppLibProjectReference)" /> - <_ResolvedProjectReferencePaths Include="@(_TerminalAppLibProjectReference)"> - - - - - - - - <_ResolvedProjectReferencePaths Remove="@(_TerminalAppLibProjectReference)" /> - <_ResolvedProjectReferencePaths Include="@(_TerminalAppLibProjectReference)" /> - - - + + diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 5aa3a100a80..c26e3282582 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -3,16 +3,1263 @@ #include "pch.h" #include "TerminalPage.h" +#include "Utils.h" #include "TerminalPage.g.cpp" +#include using namespace winrt; -using namespace Windows::UI::Xaml; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Core; +using namespace winrt::Windows::System; +using namespace winrt::Windows::ApplicationModel::DataTransfer; +using namespace winrt::Windows::UI::Text; +using namespace winrt::Microsoft::Terminal; +using namespace winrt::Microsoft::Terminal::TerminalControl; +using namespace winrt::Microsoft::Terminal::TerminalConnection; +using namespace winrt::Microsoft::Terminal::Settings; +using namespace ::TerminalApp; + +namespace winrt +{ + namespace MUX = Microsoft::UI::Xaml; + using IInspectable = Windows::Foundation::IInspectable; +} namespace winrt::TerminalApp::implementation { - TerminalPage::TerminalPage() + TerminalPage::TerminalPage() {} + + TerminalPage::TerminalPage(std::shared_ptr resourceLoader) : + _tabs{} { InitializeComponent(); + + _resourceLoader = resourceLoader; + } + + void TerminalPage::SetSettings(std::shared_ptr<::TerminalApp::CascadiaSettings> settings, bool needRefreshUI) + { + _settings = settings; + if (needRefreshUI) + { + _RefreshUIForSettingsReload(); + } + } + + void TerminalPage::Create() + { + // Hookup the key bindings + _HookupKeyBindings(_settings->GetKeybindings()); + + _tabContent = this->TabContent(); + _tabRow = this->TabRow(); + _tabView = _tabRow.TabView(); + _newTabButton = _tabRow.NewTabButton(); + + if (_settings->GlobalSettings().GetShowTabsInTitlebar()) + { + // Remove the TabView from the page. We'll hang on to it, we need to + // put it in the titlebar. + uint32_t index = 0; + if (this->Root().Children().IndexOf(_tabRow, index)) + { + this->Root().Children().RemoveAt(index); + } + + // Inform the host that our titlebar content has changed. + _setTitleBarContentHandlers(*this, _tabRow); + } + + //Event Bindings (Early) + _newTabButton.Click([this](auto&&, auto&&) { + this->_OpenNewTab(std::nullopt); + }); + _tabView.SelectionChanged({ this, &TerminalPage::_OnTabSelectionChanged }); + _tabView.Items().VectorChanged({ this, &TerminalPage::_OnTabItemsChanged }); + _tabView.TabClosing({ this, &TerminalPage::_OnTabClosing }); + + _CreateNewTabFlyout(); + _OpenNewTab(std::nullopt); + + _tabContent.SizeChanged({ this, &TerminalPage::_OnContentSizeChanged }); + } + + // Method Description: + // - Show a ContentDialog with a single "Ok" button to dismiss. Looks up the + // the title and text from our Resources using the provided keys. + // - Only one dialog can be visible at a time. If another dialog is visible + // when this is called, nothing happens. See _ShowDialog for details + // Arguments: + // - titleKey: The key to use to lookup the title text from our resources. + // - contentKey: The key to use to lookup the content text from our resources. + void TerminalPage::ShowOkDialog(const winrt::hstring& titleKey, + const winrt::hstring& contentKey) + { + auto title = _resourceLoader->GetLocalizedString(titleKey); + auto message = _resourceLoader->GetLocalizedString(contentKey); + auto buttonText = _resourceLoader->GetLocalizedString(L"Ok"); + + Controls::ContentDialog dialog; + dialog.Title(winrt::box_value(title)); + dialog.Content(winrt::box_value(message)); + dialog.CloseButtonText(buttonText); + + _showDialogHandlers(*this, dialog); + } + + // Method Description: + // - Show a dialog with "About" information. Displays the app's Display + // Name, version, getting started link, documentation link, and release + // Notes link. + void TerminalPage::_ShowAboutDialog() + { + const auto title = _resourceLoader->GetLocalizedString(L"AboutTitleText"); + const auto versionLabel = _resourceLoader->GetLocalizedString(L"VersionLabelText"); + const auto gettingStartedLabel = _resourceLoader->GetLocalizedString(L"GettingStartedLabelText"); + const auto documentationLabel = _resourceLoader->GetLocalizedString(L"DocumentationLabelText"); + const auto releaseNotesLabel = _resourceLoader->GetLocalizedString(L"ReleaseNotesLabelText"); + const auto gettingStartedUriValue = _resourceLoader->GetLocalizedString(L"GettingStartedUriValue"); + const auto documentationUriValue = _resourceLoader->GetLocalizedString(L"DocumentationUriValue"); + const auto releaseNotesUriValue = _resourceLoader->GetLocalizedString(L"ReleaseNotesUriValue"); + const auto package = winrt::Windows::ApplicationModel::Package::Current(); + const auto packageName = package.DisplayName(); + const auto version = package.Id().Version(); + winrt::Windows::UI::Xaml::Documents::Run about; + winrt::Windows::UI::Xaml::Documents::Run gettingStarted; + winrt::Windows::UI::Xaml::Documents::Run documentation; + winrt::Windows::UI::Xaml::Documents::Run releaseNotes; + winrt::Windows::UI::Xaml::Documents::Hyperlink gettingStartedLink; + winrt::Windows::UI::Xaml::Documents::Hyperlink documentationLink; + winrt::Windows::UI::Xaml::Documents::Hyperlink releaseNotesLink; + std::wstringstream aboutTextStream; + + gettingStarted.Text(gettingStartedLabel); + documentation.Text(documentationLabel); + releaseNotes.Text(releaseNotesLabel); + + winrt::Windows::Foundation::Uri gettingStartedUri{ gettingStartedUriValue }; + winrt::Windows::Foundation::Uri documentationUri{ documentationUriValue }; + winrt::Windows::Foundation::Uri releaseNotesUri{ releaseNotesUriValue }; + + gettingStartedLink.NavigateUri(gettingStartedUri); + documentationLink.NavigateUri(documentationUri); + releaseNotesLink.NavigateUri(releaseNotesUri); + + gettingStartedLink.Inlines().Append(gettingStarted); + documentationLink.Inlines().Append(documentation); + releaseNotesLink.Inlines().Append(releaseNotes); + + // Format our about text. It will look like the following: + // + // Version: ... + // Getting Started + // Documentation + // Release Notes + + aboutTextStream << packageName.c_str() << L"\n"; + + aboutTextStream << versionLabel.c_str() << L" "; + aboutTextStream << version.Major << L"." << version.Minor << L"." << version.Build << L"." << version.Revision << L"\n"; + + winrt::hstring aboutText{ aboutTextStream.str() }; + about.Text(aboutText); + + const auto buttonText = _resourceLoader->GetLocalizedString(L"Ok"); + + Controls::TextBlock aboutTextBlock; + aboutTextBlock.Inlines().Append(about); + aboutTextBlock.Inlines().Append(gettingStartedLink); + aboutTextBlock.Inlines().Append(documentationLink); + aboutTextBlock.Inlines().Append(releaseNotesLink); + aboutTextBlock.IsTextSelectionEnabled(true); + + Controls::ContentDialog dialog; + dialog.Title(winrt::box_value(title)); + dialog.Content(aboutTextBlock); + dialog.CloseButtonText(buttonText); + + _showDialogHandlers(*this, dialog); + } + + // Method Description: + // - Builds the flyout (dropdown) attached to the new tab button, and + // attaches it to the button. Populates the flyout with one entry per + // Profile, displaying the profile's name. Clicking each flyout item will + // open a new tab with that profile. + // Below the profiles are the static menu items: settings, feedback + void TerminalPage::_CreateNewTabFlyout() + { + auto newTabFlyout = Controls::MenuFlyout{}; + auto keyBindings = _settings->GetKeybindings(); + + const GUID defaultProfileGuid = _settings->GlobalSettings().GetDefaultProfile(); + // the number of profiles should not change in the loop for this to work + auto const profileCount = gsl::narrow_cast(_settings->GetProfiles().size()); + for (int profileIndex = 0; profileIndex < profileCount; profileIndex++) + { + const auto& profile = _settings->GetProfiles()[profileIndex]; + auto profileMenuItem = Controls::MenuFlyoutItem{}; + + // add the keyboard shortcuts for the first 9 profiles + if (profileIndex < 9) + { + // enum value for ShortcutAction::NewTabProfileX; 0==NewTabProfile0 + const auto action = static_cast(profileIndex + static_cast(ShortcutAction::NewTabProfile0)); + auto profileKeyChord = keyBindings.GetKeyBinding(action); + + // make sure we find one to display + if (profileKeyChord) + { + _SetAcceleratorForMenuItem(profileMenuItem, profileKeyChord); + } + } + + auto profileName = profile.GetName(); + winrt::hstring hName{ profileName }; + profileMenuItem.Text(hName); + + // If there's an icon set for this profile, set it as the icon for + // this flyout item. + if (profile.HasIcon()) + { + profileMenuItem.Icon(_GetIconFromProfile(profile)); + } + + if (profile.GetGuid() == defaultProfileGuid) + { + // Contrast the default profile with others in font weight. + profileMenuItem.FontWeight(FontWeights::Bold()); + } + + profileMenuItem.Click([this, profileIndex](auto&&, auto&&) { + this->_OpenNewTab({ profileIndex }); + }); + newTabFlyout.Items().Append(profileMenuItem); + } + + // add menu separator + auto separatorItem = Controls::MenuFlyoutSeparator{}; + newTabFlyout.Items().Append(separatorItem); + + // add static items + { + // Create the settings button. + auto settingsItem = Controls::MenuFlyoutItem{}; + settingsItem.Text(_resourceLoader->GetLocalizedString(L"SettingsMenuItem")); + + Controls::SymbolIcon ico{}; + ico.Symbol(Controls::Symbol::Setting); + settingsItem.Icon(ico); + + settingsItem.Click({ this, &TerminalPage::_SettingsButtonOnClick }); + newTabFlyout.Items().Append(settingsItem); + + auto settingsKeyChord = keyBindings.GetKeyBinding(ShortcutAction::OpenSettings); + if (settingsKeyChord) + { + _SetAcceleratorForMenuItem(settingsItem, settingsKeyChord); + } + + // Create the feedback button. + auto feedbackFlyout = Controls::MenuFlyoutItem{}; + feedbackFlyout.Text(_resourceLoader->GetLocalizedString(L"FeedbackMenuItem")); + + Controls::FontIcon feedbackIco{}; + feedbackIco.Glyph(L"\xE939"); + feedbackIco.FontFamily(Media::FontFamily{ L"Segoe MDL2 Assets" }); + feedbackFlyout.Icon(feedbackIco); + + feedbackFlyout.Click({ this, &TerminalPage::_FeedbackButtonOnClick }); + newTabFlyout.Items().Append(feedbackFlyout); + + // Create the about button. + auto aboutFlyout = Controls::MenuFlyoutItem{}; + aboutFlyout.Text(_resourceLoader->GetLocalizedString(L"AboutMenuItem")); + + Controls::SymbolIcon aboutIco{}; + aboutIco.Symbol(Controls::Symbol::Help); + aboutFlyout.Icon(aboutIco); + + aboutFlyout.Click({ this, &TerminalPage::_AboutButtonOnClick }); + newTabFlyout.Items().Append(aboutFlyout); + } + + _newTabButton.Flyout(newTabFlyout); + } + + // Function Description: + // Called when the openNewTabDropdown keybinding is used. + // Adds the flyout show option to left-align the dropdown with the split button. + // Shows the dropdown flyout. + void TerminalPage::_OpenNewTabDropdown() + { + Controls::Primitives::FlyoutShowOptions options{}; + options.Placement(Controls::Primitives::FlyoutPlacementMode::BottomEdgeAlignedLeft); + _newTabButton.Flyout().ShowAt(_newTabButton, options); + } + + // Method Description: + // - Open a new tab. This will create the TerminalControl hosting the + // terminal, and add a new Tab to our list of tabs. The method can + // optionally be provided a profile index, which will be used to create + // a tab using the profile in that index. + // If no index is provided, the default profile will be used. + // Arguments: + // - profileIndex: an optional index into the list of profiles to use to + // initialize this tab up with. + void TerminalPage::_OpenNewTab(std::optional profileIndex) + { + GUID profileGuid; + + if (profileIndex) + { + const auto realIndex = profileIndex.value(); + const auto profiles = _settings->GetProfiles(); + + // If we don't have that many profiles, then do nothing. + if (realIndex >= gsl::narrow(profiles.size())) + { + return; + } + + const auto& selectedProfile = profiles[realIndex]; + profileGuid = selectedProfile.GetGuid(); + } + else + { + // Getting Guid for default profile + const auto globalSettings = _settings->GlobalSettings(); + profileGuid = globalSettings.GetDefaultProfile(); + } + + TerminalSettings settings = _settings->MakeSettings(profileGuid); + _CreateNewTabFromSettings(profileGuid, settings); + + const int tabCount = static_cast(_tabs.size()); + TraceLoggingWrite( + g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider + "TabInformation", + TraceLoggingDescription("Event emitted upon new tab creation in TerminalApp"), + TraceLoggingInt32(tabCount, "TabCount", "Count of tabs curently opened in TerminalApp"), + TraceLoggingBool(profileIndex.has_value(), "ProfileSpecified", "Whether the new tab specified a profile explicitly"), + TraceLoggingGuid(profileGuid, "ProfileGuid", "The GUID of the profile spawned in the new tab"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); + } + + // Method Description: + // - Creates a new tab with the given settings. If the tab bar is not being + // currently displayed, it will be shown. + // Arguments: + // - settings: the TerminalSettings object to use to create the TerminalControl with. + void TerminalPage::_CreateNewTabFromSettings(GUID profileGuid, TerminalSettings settings) + { + // Initialize the new tab + + // Create a connection based on the values in our settings object. + const auto connection = _CreateConnectionFromSettings(profileGuid, settings); + + TermControl term{ settings, connection }; + + // Add the new tab to the list of our tabs. + auto newTab = _tabs.emplace_back(std::make_shared(profileGuid, term)); + + const auto* const profile = _settings->FindProfile(profileGuid); + + // Hookup our event handlers to the new terminal + _RegisterTerminalEvents(term, newTab); + + auto tabViewItem = newTab->GetTabViewItem(); + _tabView.Items().Append(tabViewItem); + + // Set this profile's tab to the icon the user specified + if (profile != nullptr && profile->HasIcon()) + { + newTab->UpdateIcon(profile->GetExpandedIconPath()); + } + + tabViewItem.PointerPressed({ this, &TerminalPage::_OnTabClick }); + + // When the tab is closed, remove it from our list of tabs. + newTab->Closed([tabViewItem, this]() { + _tabView.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [tabViewItem, this]() { + _RemoveTabViewItem(tabViewItem); + }); + }); + + // This is one way to set the tab's selected background color. + // tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), a Brush?); + + // This kicks off TabView::SelectionChanged, in response to which we'll attach the terminal's + // Xaml control to the Xaml root. + _tabView.SelectedItem(tabViewItem); + } + + // Method Description: + // - Creates a new connection based on the profile settings + // Arguments: + // - the profile GUID we want the settings from + // - the terminal settings + // Return value: + // - the desired connection + TerminalConnection::ITerminalConnection TerminalPage::_CreateConnectionFromSettings(GUID profileGuid, + winrt::Microsoft::Terminal::Settings::TerminalSettings settings) + { + const auto* const profile = _settings->FindProfile(profileGuid); + + TerminalConnection::ITerminalConnection connection{ nullptr }; + + GUID connectionType{ 0 }; + GUID sessionGuid{ 0 }; + + if (profile->HasConnectionType()) + { + connectionType = profile->GetConnectionType(); + } + + if (profile->HasConnectionType() && + profile->GetConnectionType() == AzureConnectionType && + TerminalConnection::AzureConnection::IsAzureConnectionAvailable()) + { + connection = TerminalConnection::AzureConnection(settings.InitialRows(), + settings.InitialCols()); + } + + else + { + auto conhostConn = TerminalConnection::ConhostConnection(settings.Commandline(), + settings.StartingDirectory(), + settings.StartingTitle(), + settings.InitialRows(), + settings.InitialCols(), + winrt::guid()); + sessionGuid = conhostConn.Guid(); + connection = conhostConn; + } + + TraceLoggingWrite( + g_hTerminalAppProvider, + "ConnectionCreated", + TraceLoggingDescription("Event emitted upon the creation of a connection"), + TraceLoggingGuid(connectionType, "ConnectionTypeGuid", "The type of the connection"), + TraceLoggingGuid(profileGuid, "ProfileGuid", "The profile's GUID"), + TraceLoggingGuid(sessionGuid, "SessionGuid", "The WT_SESSION's GUID"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); + + return connection; + } + + // Method Description: + // - Called when the settings button is clicked. Launches a background + // thread to open the settings file in the default JSON editor. + // Arguments: + // - + // Return Value: + // - + void TerminalPage::_SettingsButtonOnClick(const IInspectable&, + const RoutedEventArgs&) + { + LaunchSettings(); + } + + // Method Description: + // - Called when the feedback button is clicked. Launches github in your + // default browser, navigated to the "issues" page of the Terminal repo. + void TerminalPage::_FeedbackButtonOnClick(const IInspectable&, + const RoutedEventArgs&) + { + const auto feedbackUriValue = _resourceLoader->GetLocalizedString(L"FeedbackUriValue"); + + winrt::Windows::System::Launcher::LaunchUriAsync({ feedbackUriValue }); + } + + // Method Description: + // - Called when the about button is clicked. See _ShowAboutDialog for more info. + // Arguments: + // - + // Return Value: + // - + void TerminalPage::_AboutButtonOnClick(const IInspectable&, + const RoutedEventArgs&) + { + _ShowAboutDialog(); + } + + // Method Description: + // - Register our event handlers with the given keybindings object. This + // should be done regardless of what the events are actually bound to - + // this simply ensures the AppKeyBindings object will call us correctly + // for each event. + // Arguments: + // - bindings: A AppKeyBindings object to wire up with our event handlers + void TerminalPage::_HookupKeyBindings(TerminalApp::AppKeyBindings bindings) noexcept + { + // Hook up the KeyBinding object's events to our handlers. + // They should all be hooked up here, regardless of whether or not + // there's an actual keychord for them. + + bindings.NewTab({ this, &TerminalPage::_HandleNewTab }); + bindings.OpenNewTabDropdown({ this, &TerminalPage::_HandleOpenNewTabDropdown }); + bindings.DuplicateTab({ this, &TerminalPage::_HandleDuplicateTab }); + bindings.CloseTab({ this, &TerminalPage::_HandleCloseTab }); + bindings.ClosePane({ this, &TerminalPage::_HandleClosePane }); + bindings.ScrollUp({ this, &TerminalPage::_HandleScrollUp }); + bindings.ScrollDown({ this, &TerminalPage::_HandleScrollDown }); + bindings.NextTab({ this, &TerminalPage::_HandleNextTab }); + bindings.PrevTab({ this, &TerminalPage::_HandlePrevTab }); + bindings.SplitVertical({ this, &TerminalPage::_HandleSplitVertical }); + bindings.SplitHorizontal({ this, &TerminalPage::_HandleSplitHorizontal }); + bindings.ScrollUpPage({ this, &TerminalPage::_HandleScrollUpPage }); + bindings.ScrollDownPage({ this, &TerminalPage::_HandleScrollDownPage }); + bindings.OpenSettings({ this, &TerminalPage::_HandleOpenSettings }); + bindings.PasteText({ this, &TerminalPage::_HandlePasteText }); + bindings.NewTabWithProfile({ this, &TerminalPage::_HandleNewTabWithProfile }); + bindings.SwitchToTab({ this, &TerminalPage::_HandleSwitchToTab }); + bindings.ResizePane({ this, &TerminalPage::_HandleResizePane }); + bindings.MoveFocus({ this, &TerminalPage::_HandleMoveFocus }); + bindings.CopyText({ this, &TerminalPage::_HandleCopyText }); + } + + // Method Description: + // - Get the title of the currently focused terminal control, and set it's + // tab's text to that text. If this tab is the focused tab, then also + // bubble this title to any listeners of our TitleChanged event. + // Arguments: + // - tab: the Tab to update the title for. + void TerminalPage::_UpdateTitle(std::shared_ptr tab) + { + auto newTabTitle = tab->GetFocusedTitle(); + tab->SetTabText(newTabTitle); + + if (_settings->GlobalSettings().GetShowTitleInTitlebar() && + tab->IsFocused()) + { + _titleChangeHandlers(*this, newTabTitle); + } + } + + // Method Description: + // - Get the icon of the currently focused terminal control, and set its + // tab's icon to that icon. + // Arguments: + // - tab: the Tab to update the title for. + void TerminalPage::_UpdateTabIcon(std::shared_ptr tab) + { + const auto lastFocusedProfileOpt = tab->GetFocusedProfile(); + if (lastFocusedProfileOpt.has_value()) + { + const auto lastFocusedProfile = lastFocusedProfileOpt.value(); + const auto* const matchingProfile = _settings->FindProfile(lastFocusedProfile); + if (matchingProfile) + { + tab->UpdateIcon(matchingProfile->GetExpandedIconPath()); + } + else + { + tab->UpdateIcon({}); + } + } + } + + // Method Description: + // - Handle changes in tab layout. + void TerminalPage::_UpdateTabView() + { + // Show tabs when there's more than 1, or the user has chosen to always + // show the tab bar. + const bool isVisible = _settings->GlobalSettings().GetShowTabsInTitlebar() || + (_tabs.size() > 1) || + _settings->GlobalSettings().GetAlwaysShowTabs(); + + // collapse/show the tabs themselves + _tabView.Visibility(isVisible ? Visibility::Visible : Visibility::Collapsed); + + // collapse/show the row that the tabs are in. + // NaN is the special value XAML uses for "Auto" sizing. + _tabRow.Height(isVisible ? NAN : 0); + } + + // Method Description: + // - Duplicates the current focused tab + void TerminalPage::_DuplicateTabViewItem() + { + const int& focusedTabIndex = _GetFocusedTabIndex(); + const auto& _tab = _tabs.at(focusedTabIndex); + + const auto& profileGuid = _tab->GetFocusedProfile(); + const auto& settings = _settings->MakeSettings(profileGuid); + + _CreateNewTabFromSettings(profileGuid.value(), settings); + } + + // Method Description: + // - Removes the tab (both TerminalControl and XAML) + // Arguments: + // - tabViewItem: the TabViewItem in the TabView that is being removed. + void TerminalPage::_RemoveTabViewItem(const IInspectable& tabViewItem) + { + // To close the window here, we need to close the hosting window. + if (_tabs.size() == 1) + { + _lastTabClosedHandlers(*this, nullptr); + } + uint32_t tabIndexFromControl = 0; + _tabView.Items().IndexOf(tabViewItem, tabIndexFromControl); + auto focusedTabIndex = _GetFocusedTabIndex(); + + // Removing the tab from the collection will destroy its control and disconnect its connection. + _tabs.erase(_tabs.begin() + tabIndexFromControl); + _tabView.Items().RemoveAt(tabIndexFromControl); + + if (tabIndexFromControl == focusedTabIndex) + { + auto const tabCount = gsl::narrow_cast(_tabs.size()); + if (focusedTabIndex >= tabCount) + { + focusedTabIndex = tabCount - 1; + } + else if (focusedTabIndex < 0) + { + focusedTabIndex = 0; + } + + _SelectTab(focusedTabIndex); + } + } + + // Method Description: + // - Connects event handlers to the TermControl for events that we want to + // handle. This includes: + // * the Copy and Paste events, for setting and retrieving clipboard data + // on the right thread + // * the TitleChanged event, for changing the text of the tab + // * the GotFocus event, for changing the title/icon in the tab when a new + // control is focused + // Arguments: + // - term: The newly created TermControl to connect the events for + // - hostingTab: The Tab that's hosting this TermControl instance + void TerminalPage::_RegisterTerminalEvents(TermControl term, std::shared_ptr hostingTab) + { + // Add an event handler when the terminal's selection wants to be copied. + // When the text buffer data is retrieved, we'll copy the data into the Clipboard + term.CopyToClipboard({ this, &TerminalPage::_CopyToClipboardHandler }); + + // Add an event handler when the terminal wants to paste data from the Clipboard. + term.PasteFromClipboard({ this, &TerminalPage::_PasteFromClipboardHandler }); + + // Don't capture a strong ref to the tab. If the tab is removed as this + // is called, we don't really care anymore about handling the event. + std::weak_ptr weakTabPtr = hostingTab; + term.TitleChanged([this, weakTabPtr](auto newTitle) { + auto tab = weakTabPtr.lock(); + if (!tab) + { + return; + } + // The title of the control changed, but not necessarily the title + // of the tab. Get the title of the focused pane of the tab, and set + // the tab's text to the focused panes' text. + _UpdateTitle(tab); + }); + + term.GotFocus([this, weakTabPtr](auto&&, auto&&) { + auto tab = weakTabPtr.lock(); + if (!tab) + { + return; + } + // Update the focus of the tab's panes + tab->UpdateFocus(); + + // Possibly update the title of the tab, window to match the newly + // focused pane. + _UpdateTitle(tab); + + // Possibly update the icon of the tab. + _UpdateTabIcon(tab); + }); + } + + // Method Description: + // - Sets focus to the tab to the right or left the currently selected tab. + void TerminalPage::_SelectNextTab(const bool bMoveRight) + { + int focusedTabIndex = _GetFocusedTabIndex(); + auto tabCount = _tabs.size(); + // Wraparound math. By adding tabCount and then calculating modulo tabCount, + // we clamp the values to the range [0, tabCount) while still supporting moving + // leftward from 0 to tabCount - 1. + _SetFocusedTabIndex( + static_cast((tabCount + focusedTabIndex + (bMoveRight ? 1 : -1)) % tabCount)); + } + + // Method Description: + // - Sets focus to the desired tab. Returns false if the provided tabIndex + // is greater than the number of tabs we have. + // Return Value: + // true iff we were able to select that tab index, false otherwise + bool TerminalPage::_SelectTab(const int tabIndex) + { + if (tabIndex >= 0 && tabIndex < gsl::narrow_cast(_tabs.size())) + { + _SetFocusedTabIndex(tabIndex); + return true; + } + return false; + } + + // Method Description: + // - Attempt to move focus between panes, as to focus the child on + // the other side of the separator. See Pane::NavigateFocus for details. + // - Moves the focus of the currently focused tab. + // Arguments: + // - direction: The direction to move the focus in. + // Return Value: + // - + void TerminalPage::_MoveFocus(const Direction& direction) + { + const auto focusedTabIndex = _GetFocusedTabIndex(); + _tabs[focusedTabIndex]->NavigateFocus(direction); + } + + winrt::Microsoft::Terminal::TerminalControl::TermControl TerminalPage::_GetFocusedControl() + { + int focusedTabIndex = _GetFocusedTabIndex(); + auto focusedTab = _tabs[focusedTabIndex]; + return focusedTab->GetFocusedTerminalControl(); + } + + // Method Description: + // - Returns the index in our list of tabs of the currently focused tab. If + // no tab is currently selected, returns -1. + // Return Value: + // - the index of the currently focused tab if there is one, else -1 + int TerminalPage::_GetFocusedTabIndex() const + { + // GH#1117: This is a workaround because _tabView.SelectedIndex() + // sometimes return incorrect result after removing some tabs + uint32_t focusedIndex; + if (_tabView.Items().IndexOf(_tabView.SelectedItem(), focusedIndex)) + { + return focusedIndex; + } + return -1; + } + + void TerminalPage::_SetFocusedTabIndex(int tabIndex) + { + // GH#1117: This is a workaround because _tabView.SelectedIndex(tabIndex) + // sometimes set focus to an incorrect tab after removing some tabs + auto tab = _tabs.at(tabIndex); + _tabView.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [tab, this]() { + auto tabViewItem = tab->GetTabViewItem(); + _tabView.SelectedItem(tabViewItem); + }); + } + + // Method Description: + // - Close the currently focused tab. Focus will move to the left, if possible. + void TerminalPage::_CloseFocusedTab() + { + int focusedTabIndex = _GetFocusedTabIndex(); + std::shared_ptr focusedTab{ _tabs[focusedTabIndex] }; + _RemoveTabViewItem(focusedTab->GetTabViewItem()); + } + + // Method Description: + // - Close the currently focused pane. If the pane is the last pane in the + // tab, the tab will also be closed. This will happen when we handle the + // tab's Closed event. + void TerminalPage::_CloseFocusedPane() + { + int focusedTabIndex = _GetFocusedTabIndex(); + std::shared_ptr focusedTab{ _tabs[focusedTabIndex] }; + focusedTab->ClosePane(); + } + + // Method Description: + // - Move the viewport of the terminal of the currently focused tab up or + // down a number of lines. Negative values of `delta` will move the + // view up, and positive values will move the viewport down. + // Arguments: + // - delta: a number of lines to move the viewport relative to the current viewport. + void TerminalPage::_Scroll(int delta) + { + int focusedTabIndex = _GetFocusedTabIndex(); + _tabs[focusedTabIndex]->Scroll(delta); + } + + // Method Description: + // - Vertically split the focused pane, and place the given TermControl into + // the newly created pane. + // Arguments: + // - profile: The profile GUID to associate with the newly created pane. If + // this is nullopt, use the default profile. + void TerminalPage::_SplitVertical(const std::optional& profileGuid) + { + _SplitPane(Pane::SplitState::Vertical, profileGuid); + } + + // Method Description: + // - Horizontally split the focused pane and place the given TermControl + // into the newly created pane. + // Arguments: + // - profile: The profile GUID to associate with the newly created pane. If + // this is nullopt, use the default profile. + void TerminalPage::_SplitHorizontal(const std::optional& profileGuid) + { + _SplitPane(Pane::SplitState::Horizontal, profileGuid); + } + + // Method Description: + // - Split the focused pane either horizontally or vertically, and place the + // given TermControl into the newly created pane. + // - If splitType == SplitState::None, this method does nothing. + // Arguments: + // - splitType: one value from the Pane::SplitState enum, indicating how the + // new pane should be split from its parent. + // - profile: The profile GUID to associate with the newly created pane. If + // this is nullopt, use the default profile. + void TerminalPage::_SplitPane(const Pane::SplitState splitType, const std::optional& profileGuid) + { + // Do nothing if we're requesting no split. + if (splitType == Pane::SplitState::None) + { + return; + } + + const auto realGuid = profileGuid ? profileGuid.value() : + _settings->GlobalSettings().GetDefaultProfile(); + const auto controlSettings = _settings->MakeSettings(realGuid); + + const auto controlConnection = _CreateConnectionFromSettings(realGuid, controlSettings); + + const int focusedTabIndex = _GetFocusedTabIndex(); + auto focusedTab = _tabs[focusedTabIndex]; + + const auto canSplit = focusedTab->CanSplitPane(splitType); + + if (!canSplit) + { + return; + } + + TermControl newControl{ controlSettings, controlConnection }; + + // Hookup our event handlers to the new terminal + _RegisterTerminalEvents(newControl, focusedTab); + + focusedTab->SplitPane(splitType, realGuid, newControl); + } + + // Method Description: + // - Attempt to move a separator between panes, as to resize each child on + // either size of the separator. See Pane::ResizePane for details. + // - Moves a separator on the currently focused tab. + // Arguments: + // - direction: The direction to move the separator in. + // Return Value: + // - + void TerminalPage::_ResizePane(const Direction& direction) + { + const auto focusedTabIndex = _GetFocusedTabIndex(); + _tabs[focusedTabIndex]->ResizePane(direction); + } + + // Method Description: + // - Move the viewport of the terminal of the currently focused tab up or + // down a page. The page length will be dependent on the terminal view height. + // Negative values of `delta` will move the view up by one page, and positive values + // will move the viewport down by one page. + // Arguments: + // - delta: The direction to move the view relative to the current viewport(it + // is clamped between -1 and 1) + void TerminalPage::_ScrollPage(int delta) + { + delta = std::clamp(delta, -1, 1); + const auto focusedTabIndex = _GetFocusedTabIndex(); + const auto control = _GetFocusedControl(); + const auto termHeight = control.GetViewHeight(); + _tabs[focusedTabIndex]->Scroll(termHeight * delta); + } + + // Method Description: + // - Gets a colored IconElement for the profile in question. If the profile + // has an `icon` set in the settings, this will return an icon with that + // image in it. Otherwise it will return a nullptr-initialized + // IconElement. + // Arguments: + // - profile: the profile to get the icon from + // Return Value: + // - an IconElement for the profile's icon, if it has one. + Controls::IconElement TerminalPage::_GetIconFromProfile(const Profile& profile) + { + return profile.HasIcon() ? GetColoredIcon(profile.GetExpandedIconPath()) : Controls::IconElement{ nullptr }; + } + + // Method Description: + // - Gets the title of the currently focused terminal control. If there + // isn't a control selected for any reason, returns "Windows Terminal" + // Arguments: + // - + // Return Value: + // - the title of the focused control if there is one, else "Windows Terminal" + hstring TerminalPage::Title() + { + if (_settings->GlobalSettings().GetShowTitleInTitlebar()) + { + auto selectedIndex = _tabView.SelectedIndex(); + if (selectedIndex >= 0) + { + try + { + if (auto focusedControl{ _GetFocusedControl() }) + { + return focusedControl.Title(); + } + } + CATCH_LOG(); + } + } + return { L"Windows Terminal" }; + } + + // Method Description: + // - Handles the special case of providing a text override for the UI shortcut due to VK_OEM issue. + // Looks at the flags from the KeyChord modifiers and provides a concatenated string value of all + // in the same order that XAML would put them as well. + // Return Value: + // - a string representation of the key modifiers for the shortcut + //NOTE: This needs to be localized with https://github.com/microsoft/terminal/issues/794 if XAML framework issue not resolved before then + static std::wstring _FormatOverrideShortcutText(Settings::KeyModifiers modifiers) + { + std::wstring buffer{ L"" }; + + if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Ctrl)) + { + buffer += L"Ctrl+"; + } + + if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Shift)) + { + buffer += L"Shift+"; + } + + if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Alt)) + { + buffer += L"Alt+"; + } + + return buffer; + } + + // Method Description: + // - Takes a MenuFlyoutItem and a corresponding KeyChord value and creates the accelerator for UI display. + // Takes into account a special case for an error condition for a comma + // Arguments: + // - MenuFlyoutItem that will be displayed, and a KeyChord to map an accelerator + void TerminalPage::_SetAcceleratorForMenuItem(Controls::MenuFlyoutItem& menuItem, + const winrt::Microsoft::Terminal::Settings::KeyChord& keyChord) + { +#ifdef DEP_MICROSOFT_UI_XAML_708_FIXED + // work around https://github.com/microsoft/microsoft-ui-xaml/issues/708 in case of VK_OEM_COMMA + if (keyChord.Vkey() != VK_OEM_COMMA) + { + // use the XAML shortcut to give us the automatic capabilities + auto menuShortcut = Windows::UI::Xaml::Input::KeyboardAccelerator{}; + + // TODO: Modify this when https://github.com/microsoft/terminal/issues/877 is resolved + menuShortcut.Key(static_cast(keyChord.Vkey())); + + // inspect the modifiers from the KeyChord and set the flags int he XAML value + auto modifiers = AppKeyBindings::ConvertVKModifiers(keyChord.Modifiers()); + + // add the modifiers to the shortcut + menuShortcut.Modifiers(modifiers); + + // add to the menu + menuItem.KeyboardAccelerators().Append(menuShortcut); + } + else // we've got a comma, so need to just use the alternate method +#endif + { + // extract the modifier and key to a nice format + auto overrideString = _FormatOverrideShortcutText(keyChord.Modifiers()); + auto mappedCh = MapVirtualKeyW(keyChord.Vkey(), MAPVK_VK_TO_CHAR); + if (mappedCh != 0) + { + menuItem.KeyboardAcceleratorTextOverride(overrideString + gsl::narrow_cast(mappedCh)); + } + } + } + + // Method Description: + // - Place `copiedData` into the clipboard as text. Triggered when a + // terminal control raises it's CopyToClipboard event. + // Arguments: + // - copiedData: the new string content to place on the clipboard. + void TerminalPage::_CopyToClipboardHandler(const IInspectable& /*sender*/, + const winrt::Microsoft::Terminal::TerminalControl::CopyToClipboardEventArgs& copiedData) + { + this->Dispatcher().RunAsync(CoreDispatcherPriority::High, [copiedData]() { + DataPackage dataPack = DataPackage(); + dataPack.RequestedOperation(DataPackageOperation::Copy); + + // copy text to dataPack + dataPack.SetText(copiedData.Text()); + + // copy html to dataPack + const auto htmlData = copiedData.Html(); + if (!htmlData.empty()) + { + dataPack.SetHtmlFormat(htmlData); + } + + try + { + Clipboard::SetContent(dataPack); + Clipboard::Flush(); + } + CATCH_LOG(); + }); + } + + // Method Description: + // - Fires an async event to get data from the clipboard, and paste it to + // the terminal. Triggered when the Terminal Control requests clipboard + // data with it's PasteFromClipboard event. + // Arguments: + // - eventArgs: the PasteFromClipboard event sent from the TermControl + void TerminalPage::_PasteFromClipboardHandler(const IInspectable& /*sender*/, + const PasteFromClipboardEventArgs& eventArgs) + { + this->Dispatcher().RunAsync(CoreDispatcherPriority::High, [eventArgs]() { + TerminalPage::PasteFromClipboard(eventArgs); + }); } + + // Function Description: + // - Copies and processes the text data from the Windows Clipboard. + // Does some of this in a background thread, as to not hang/crash the UI thread. + // Arguments: + // - eventArgs: the PasteFromClipboard event sent from the TermControl + fire_and_forget TerminalPage::PasteFromClipboard(PasteFromClipboardEventArgs eventArgs) + { + const DataPackageView data = Clipboard::GetContent(); + + // This will switch the execution of the function to a background (not + // UI) thread. This is IMPORTANT, because the getting the clipboard data + // will crash on the UI thread, because the main thread is a STA. + co_await winrt::resume_background(); + + hstring text = L""; + if (data.Contains(StandardDataFormats::Text())) + { + text = co_await data.GetTextAsync(); + } + eventArgs.HandleClipboardData(text); + } + + // Method Description: + // - Copy text from the focused terminal to the Windows Clipboard + // Arguments: + // - trimTrailingWhitespace: enable removing any whitespace from copied selection + // and get text to appear on separate lines. + // Return Value: + // - true iff we we able to copy text (if a selection was active) + bool TerminalPage::_CopyText(const bool trimTrailingWhitespace) + { + const auto control = _GetFocusedControl(); + return control.CopySelectionToClipboard(trimTrailingWhitespace); + } + + // Method Description: + // - Paste text from the Windows Clipboard to the focused terminal + void TerminalPage::_PasteText() + { + const auto control = _GetFocusedControl(); + control.PasteTextFromClipboard(); + } + + void TerminalPage::_OpenSettings() + { + LaunchSettings(); + } + + // Function Description: + // - Called when the settings button is clicked. ShellExecutes the settings + // file, as to open it in the default editor for .json files. Does this in + // a background thread, as to not hang/crash the UI thread. + fire_and_forget TerminalPage::LaunchSettings() + { + // This will switch the execution of the function to a background (not + // UI) thread. This is IMPORTANT, because the Windows.Storage API's + // (used for retrieving the path to the file) will crash on the UI + // thread, because the main thread is a STA. + co_await winrt::resume_background(); + + const auto settingsPath = CascadiaSettings::GetSettingsPath(); + + HINSTANCE res = ShellExecute(nullptr, nullptr, settingsPath.c_str(), nullptr, nullptr, SW_SHOW); + if (static_cast(reinterpret_cast(res)) <= 32) + { + ShellExecute(nullptr, nullptr, L"notepad", settingsPath.c_str(), nullptr, SW_SHOW); + } + } + + // Method Description: + // - Responds to changes in the TabView's item list by changing the tabview's + // visibility. + // Arguments: + // - sender: the control that originated this event + // - eventArgs: the event's constituent arguments + void TerminalPage::_OnTabItemsChanged(const IInspectable& sender, const Windows::Foundation::Collections::IVectorChangedEventArgs& eventArgs) + { + _UpdateTabView(); + } + + // Method Description: + // - Additional responses to clicking on a TabView's item. Currently, just remove tab with middle click + // Arguments: + // - sender: the control that originated this event (TabViewItem) + // - eventArgs: the event's constituent arguments + void TerminalPage::_OnTabClick(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs) + { + if (eventArgs.GetCurrentPoint(*this).Properties().IsMiddleButtonPressed()) + { + _RemoveTabViewItem(sender); + eventArgs.Handled(true); + } + } + + // Method Description: + // - Responds to the TabView control's Selection Changed event (to move a + // new terminal control into focus.) + // Arguments: + // - sender: the control that originated this event + // - eventArgs: the event's constituent arguments + void TerminalPage::_OnTabSelectionChanged(const IInspectable& sender, const Controls::SelectionChangedEventArgs& eventArgs) + { + auto tabView = sender.as(); + auto selectedIndex = tabView.SelectedIndex(); + + // Unfocus all the tabs. + for (auto tab : _tabs) + { + tab->SetFocused(false); + } + + if (selectedIndex >= 0) + { + try + { + auto tab = _tabs.at(selectedIndex); + + _tabContent.Children().Clear(); + _tabContent.Children().Append(tab->GetRootElement()); + + tab->SetFocused(true); + _titleChangeHandlers(*this, Title()); + } + CATCH_LOG(); + } + } + + // Method Description: + // - Called when our tab content size changes. This updates each tab with + // the new size, so they have a chance to update each of their panes with + // the new size. + // Arguments: + // - e: the SizeChangedEventArgs with the new size of the tab content area. + // Return Value: + // - + void TerminalPage::_OnContentSizeChanged(const IInspectable& /*sender*/, Windows::UI::Xaml::SizeChangedEventArgs const& e) + { + const auto newSize = e.NewSize(); + for (auto& tab : _tabs) + { + tab->ResizeContent(newSize); + } + } + + // Method Description: + // - Responds to the TabView control's Tab Closing event by removing + // the indicated tab from the set and focusing another one. + // The event is cancelled so App maintains control over the + // items in the tabview. + // Arguments: + // - sender: the control that originated this event + // - eventArgs: the event's constituent arguments + void TerminalPage::_OnTabClosing(const IInspectable& sender, const MUX::Controls::TabViewTabClosingEventArgs& eventArgs) + { + const auto tabViewItem = eventArgs.Item(); + _RemoveTabViewItem(tabViewItem); + + // If we don't cancel the event, the TabView will remove the item itself. + eventArgs.Cancel(true); + } + + // Method Description: + // - Hook up keybindings, and refresh the UI of the terminal. + // This includes update the settings of all the tabs according + // to their profiles, update the title and icon of each tab, and + // finally create the tab flyout + void TerminalPage::_RefreshUIForSettingsReload() + { + // Re-wire the keybindings to their handlers, as we'll have created a + // new AppKeyBindings object. + _HookupKeyBindings(_settings->GetKeybindings()); + + // Refresh UI elements + auto profiles = _settings->GetProfiles(); + for (auto& profile : profiles) + { + const GUID profileGuid = profile.GetGuid(); + TerminalSettings settings = _settings->MakeSettings(profileGuid); + + for (auto& tab : _tabs) + { + // Attempt to reload the settings of any panes with this profile + tab->UpdateSettings(settings, profileGuid); + } + } + + // Update the icon of the tab for the currently focused profile in that tab. + for (auto& tab : _tabs) + { + _UpdateTabIcon(tab); + _UpdateTitle(tab); + } + + this->Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() { + // repopulate the new tab button's flyout with entries for each + // profile, which might have changed + _CreateNewTabFlyout(); + }); + } + + // Method Description: + // - This is the method that App will call when the titlebar + // has been clicked. It dismisses any open flyouts. + // Arguments: + // - + // Return Value: + // - + void TerminalPage::TitlebarClicked() + { + if (_newTabButton && _newTabButton.Flyout()) + { + _newTabButton.Flyout().Hide(); + } + } + + // -------------------------------- 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_WITH_TYPED_EVENT_HANDLER(TerminalPage, TitleChanged, _titleChangeHandlers, winrt::Windows::Foundation::IInspectable, winrt::hstring); + DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TerminalPage, LastTabClosed, _lastTabClosedHandlers, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::LastTabClosedEventArgs); + DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TerminalPage, SetTitleBarContent, _setTitleBarContentHandlers, winrt::Windows::Foundation::IInspectable, UIElement); + DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TerminalPage, ShowDialog, _showDialogHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::Controls::ContentDialog); } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index c272b41a1a9..244e8e25ddb 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -6,12 +6,144 @@ #include "winrt/Microsoft.UI.Xaml.Controls.h" #include "TerminalPage.g.h" +#include "Tab.h" +#include "CascadiaSettings.h" +#include "Profile.h" +#include "ScopedResourceLoader.h" + +#include +#include +#include +#include +#include namespace winrt::TerminalApp::implementation { struct TerminalPage : TerminalPageT { + public: TerminalPage(); + + TerminalPage(std::shared_ptr resourceLoader); + + void SetSettings(std::shared_ptr<::TerminalApp::CascadiaSettings> settings, bool needRefreshUI); + + void Create(); + + hstring Title(); + + void ShowOkDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey); + + void TitlebarClicked(); + + // -------------------------------- WinRT Events --------------------------------- + DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(TitleChanged, _titleChangeHandlers, winrt::Windows::Foundation::IInspectable, winrt::hstring); + DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(LastTabClosed, _lastTabClosedHandlers, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::LastTabClosedEventArgs); + DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(SetTitleBarContent, _setTitleBarContentHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::UIElement); + DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(ShowDialog, _showDialogHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::Controls::ContentDialog); + + private: + // If you add controls here, but forget to null them either here or in + // the ctor, you're going to have a bad time. It'll mysteriously fail to + // activate the app. + // ALSO: If you add any UIElements as roots here, make sure they're + // updated in App::_ApplyTheme. The roots currently is _tabRow + // (which is a root when the tabs are in the titlebar.) + Microsoft::UI::Xaml::Controls::TabView _tabView{ nullptr }; + TerminalApp::TabRowControl _tabRow{ nullptr }; + Windows::UI::Xaml::Controls::Grid _tabContent{ nullptr }; + Windows::UI::Xaml::Controls::SplitButton _newTabButton{ nullptr }; + + std::shared_ptr<::TerminalApp::CascadiaSettings> _settings{ nullptr }; + + std::vector> _tabs; + + std::shared_ptr _resourceLoader{ nullptr }; + + void _ShowAboutDialog(); + + void _CreateNewTabFlyout(); + void _OpenNewTabDropdown(); + void _OpenNewTab(std::optional profileIndex); + void _CreateNewTabFromSettings(GUID profileGuid, winrt::Microsoft::Terminal::Settings::TerminalSettings settings); + winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(GUID profileGuid, winrt::Microsoft::Terminal::Settings::TerminalSettings settings); + + void _SettingsButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); + void _FeedbackButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); + void _AboutButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); + + void _HookupKeyBindings(TerminalApp::AppKeyBindings bindings) noexcept; + + void _UpdateTitle(std::shared_ptr tab); + void _UpdateTabIcon(std::shared_ptr tab); + void _UpdateTabView(); + void _DuplicateTabViewItem(); + void _RemoveTabViewItem(const IInspectable& tabViewItem); + + void _RegisterTerminalEvents(Microsoft::Terminal::TerminalControl::TermControl term, std::shared_ptr hostingTab); + + void _SelectNextTab(const bool bMoveRight); + bool _SelectTab(const int tabIndex); + void _MoveFocus(const Direction& direction); + + winrt::Microsoft::Terminal::TerminalControl::TermControl _GetFocusedControl(); + int _GetFocusedTabIndex() const; + void _SetFocusedTabIndex(int tabIndex); + void _CloseFocusedTab(); + void _CloseFocusedPane(); + + // Todo: add more event implementations here + // MSFT:20641986: Add keybindings for New Window + void _Scroll(int delta); + void _SplitVertical(const std::optional& profileGuid); + void _SplitHorizontal(const std::optional& profileGuid); + void _SplitPane(const Pane::SplitState splitType, const std::optional& profileGuid); + void _ResizePane(const Direction& direction); + void _ScrollPage(int delta); + static Windows::UI::Xaml::Controls::IconElement _GetIconFromProfile(const ::TerminalApp::Profile& profile); + void _SetAcceleratorForMenuItem(Windows::UI::Xaml::Controls::MenuFlyoutItem& menuItem, const winrt::Microsoft::Terminal::Settings::KeyChord& keyChord); + + void _CopyToClipboardHandler(const IInspectable& sender, const winrt::Microsoft::Terminal::TerminalControl::CopyToClipboardEventArgs& copiedData); + void _PasteFromClipboardHandler(const IInspectable& sender, + const Microsoft::Terminal::TerminalControl::PasteFromClipboardEventArgs& eventArgs); + bool _CopyText(const bool trimTrailingWhitespace); + void _PasteText(); + static fire_and_forget PasteFromClipboard(winrt::Microsoft::Terminal::TerminalControl::PasteFromClipboardEventArgs eventArgs); + + void _OpenSettings(); + fire_and_forget LaunchSettings(); + + void _OnTabClick(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs); + void _OnTabSelectionChanged(const IInspectable& sender, const Windows::UI::Xaml::Controls::SelectionChangedEventArgs& eventArgs); + void _OnTabItemsChanged(const IInspectable& sender, const Windows::Foundation::Collections::IVectorChangedEventArgs& eventArgs); + void _OnContentSizeChanged(const IInspectable& /*sender*/, Windows::UI::Xaml::SizeChangedEventArgs const& e); + void _OnTabClosing(const IInspectable& sender, const Microsoft::UI::Xaml::Controls::TabViewTabClosingEventArgs& eventArgs); + + void _RefreshUIForSettingsReload(); + +#pragma region ActionHandlers + // These are all defined in AppActionHandlers.cpp + void _HandleNewTab(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleOpenNewTabDropdown(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleDuplicateTab(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleCloseTab(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleClosePane(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleScrollUp(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleScrollDown(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleNextTab(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandlePrevTab(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleSplitVertical(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleSplitHorizontal(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleScrollUpPage(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleScrollDownPage(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleOpenSettings(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandlePasteText(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleNewTabWithProfile(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleSwitchToTab(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleResizePane(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleMoveFocus(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleCopyText(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); +#pragma endregion }; } diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index ea012620078..62f05fec87b 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -1,10 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import "..\App.idl"; namespace TerminalApp { [default_interface] runtimeclass TerminalPage : Windows.UI.Xaml.Controls.Page { TerminalPage(); + + event Windows.Foundation.TypedEventHandler TitleChanged; + event Windows.Foundation.TypedEventHandler LastTabClosed; + event Windows.Foundation.TypedEventHandler SetTitleBarContent; + event Windows.Foundation.TypedEventHandler ShowDialog; } } diff --git a/src/cascadia/TerminalApp/TerminalWarnings.h b/src/cascadia/TerminalApp/TerminalWarnings.h new file mode 100644 index 00000000000..b926f1c1ab7 --- /dev/null +++ b/src/cascadia/TerminalApp/TerminalWarnings.h @@ -0,0 +1,57 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- TerminalWarnings.h + +Abstract: +- This file contains definitions for warnings, errors and exceptions used by the + Windows Terminal + +Author(s): +- Mike Griese - August 2019 + +--*/ +#pragma once + +namespace TerminalApp +{ + // SettingsLoadWarnings are scenarios where the settings contained + // information we knew was invalid, but we could recover from. + enum class SettingsLoadWarnings : uint32_t + { + MissingDefaultProfile = 0, + DuplicateProfile = 1 + }; + + // SettingsLoadWarnings are scenarios where the settings had invalid state + // that we could not recover from. + enum class SettingsLoadErrors : uint32_t + { + NoProfiles = 0 + }; + + // This is a helper class to wrap up a SettingsLoadErrors into a proper + // exception type. + class SettingsException : public std::runtime_error + { + public: + SettingsException(const SettingsLoadErrors& error) : + std::runtime_error{ nullptr }, + _error{ error } {}; + + // We don't use the what() method - we want to be able to display + // localizable error messages. Catchers of this exception should use + // _GetErrorText (in App.cpp) to get the localized exception string. + const char* what() const override + { + return "Exception while loading or validating Terminal settings"; + }; + + SettingsLoadErrors Error() const noexcept { return _error; }; + + private: + const SettingsLoadErrors _error; + }; +}; diff --git a/src/cascadia/TerminalApp/Utils.cpp b/src/cascadia/TerminalApp/Utils.cpp index 6bf6bdc87d4..90b2a587701 100644 --- a/src/cascadia/TerminalApp/Utils.cpp +++ b/src/cascadia/TerminalApp/Utils.cpp @@ -16,3 +16,33 @@ std::wstring GetWstringFromJson(const Json::Value& json) { return winrt::to_hstring(json.asString()).c_str(); } + +// Method Description: +// - Creates an IconElement for the given path. The icon returned is a colored +// icon. If we couldn't create the icon for any reason, we return an empty +// IconElement. +// Arguments: +// - path: the full, expanded path to the icon. +// Return Value: +// - An IconElement with its IconSource set, if possible. +winrt::Windows::UI::Xaml::Controls::IconElement GetColoredIcon(const winrt::hstring& path) +{ + winrt::Windows::UI::Xaml::Controls::IconSourceElement elem{}; + if (!path.empty()) + { + try + { + winrt::Windows::Foundation::Uri iconUri{ path }; + winrt::Windows::UI::Xaml::Controls::BitmapIconSource iconSource; + // Make sure to set this to false, so we keep the RGB data of the + // image. Otherwise, the icon will be white for all the + // non-transparent pixels in the image. + iconSource.ShowAsMonochrome(false); + iconSource.UriSource(iconUri); + elem.IconSource(iconSource); + } + CATCH_LOG(); + } + + return elem; +} diff --git a/src/cascadia/TerminalApp/Utils.h b/src/cascadia/TerminalApp/Utils.h index 4cdfc2f4b85..56e9cb87dd1 100644 --- a/src/cascadia/TerminalApp/Utils.h +++ b/src/cascadia/TerminalApp/Utils.h @@ -28,3 +28,5 @@ inline std::string JsonKey(const std::string_view key) { return static_cast(key); } + +winrt::Windows::UI::Xaml::Controls::IconElement GetColoredIcon(const winrt::hstring& path); diff --git a/src/cascadia/TerminalApp/init.cpp b/src/cascadia/TerminalApp/init.cpp new file mode 100644 index 00000000000..d6b7eb06ac5 --- /dev/null +++ b/src/cascadia/TerminalApp/init.cpp @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation +// Licensed under the MIT license. + +#include "pch.h" + +// Note: Generate GUID using TlgGuid.exe tool +TRACELOGGING_DEFINE_PROVIDER( + g_hTerminalAppProvider, + "Microsoft.Windows.Terminal.App", + // {24a1622f-7da7-5c77-3303-d850bd1ab2ed} + (0x24a1622f, 0x7da7, 0x5c77, 0x33, 0x03, 0xd8, 0x50, 0xbd, 0x1a, 0xb2, 0xed), + TraceLoggingOptionMicrosoftTelemetry()); + +BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/) +{ + switch (reason) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hInstDll); + TraceLoggingRegister(g_hTerminalAppProvider); + break; + case DLL_PROCESS_DETACH: + if (g_hTerminalAppProvider) + { + TraceLoggingUnregister(g_hTerminalAppProvider); + } + break; + } + + return TRUE; +} diff --git a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj index ea47ae07b21..b53faad8eb4 100644 --- a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj @@ -68,8 +68,14 @@ + - + + ../ActionArgs.idl + + + ../AppKeyBindings.idl + ../App.xaml @@ -77,6 +83,7 @@ + ../MinMaxCloseControl.xaml @@ -107,9 +114,15 @@ ../AppKeyBindings.idl + + ../ActionArgs.idl + ../App.xaml + + ../App.xaml + @@ -127,6 +140,7 @@ ../App.xaml + ../MinMaxCloseControl.xaml Code @@ -252,7 +266,7 @@ x86 $(Platform) <_MUXRoot>$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.2.190611001-prerelease\ - <_MUXAppRoot>$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0-preview6.2\ + <_MUXAppRoot>$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0-preview7\ @@ -282,7 +296,7 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + diff --git a/src/cascadia/TerminalApp/lib/pch.h b/src/cascadia/TerminalApp/lib/pch.h index ee26a863b0e..f42dce36c69 100644 --- a/src/cascadia/TerminalApp/lib/pch.h +++ b/src/cascadia/TerminalApp/lib/pch.h @@ -45,7 +45,7 @@ // Including TraceLogging essentials for the binary #include #include -TRACELOGGING_DECLARE_PROVIDER(g_hTerminalWin32Provider); +TRACELOGGING_DECLARE_PROVIDER(g_hTerminalAppProvider); #include #include diff --git a/src/cascadia/TerminalApp/packages.config b/src/cascadia/TerminalApp/packages.config index fe0c6e184a5..efa27692017 100644 --- a/src/cascadia/TerminalApp/packages.config +++ b/src/cascadia/TerminalApp/packages.config @@ -1,6 +1,6 @@ ο»Ώ - + - + diff --git a/src/cascadia/TerminalConnection/AzureConnection-ARM64.cpp b/src/cascadia/TerminalConnection/AzureConnection-ARM64.cpp new file mode 100644 index 00000000000..75e7bc21503 --- /dev/null +++ b/src/cascadia/TerminalConnection/AzureConnection-ARM64.cpp @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "AzureConnection-ARM64.h" +#include "AzureConnection.g.cpp" + +namespace winrt::Microsoft::Terminal::TerminalConnection::implementation +{ + bool AzureConnection::IsAzureConnectionAvailable() + { + return false; + } + AzureConnection::AzureConnection(uint32_t rows, uint32_t columns) + { + throw hresult_not_implemented(); + } + winrt::event_token AzureConnection::TerminalOutput(Microsoft::Terminal::TerminalConnection::TerminalOutputEventArgs const& handler) + { + throw hresult_not_implemented(); + } + void AzureConnection::TerminalOutput(winrt::event_token const& token) + { + throw hresult_not_implemented(); + } + winrt::event_token AzureConnection::TerminalDisconnected(Microsoft::Terminal::TerminalConnection::TerminalDisconnectedEventArgs const& handler) + { + throw hresult_not_implemented(); + } + void AzureConnection::TerminalDisconnected(winrt::event_token const& token) + { + throw hresult_not_implemented(); + } + void AzureConnection::Start() + { + throw hresult_not_implemented(); + } + void AzureConnection::WriteInput(hstring const& data) + { + throw hresult_not_implemented(); + } + void AzureConnection::Resize(uint32_t rows, uint32_t columns) + { + throw hresult_not_implemented(); + } + void AzureConnection::Close() + { + throw hresult_not_implemented(); + } +} diff --git a/src/cascadia/TerminalConnection/AzureConnection-ARM64.h b/src/cascadia/TerminalConnection/AzureConnection-ARM64.h new file mode 100644 index 00000000000..086ac775364 --- /dev/null +++ b/src/cascadia/TerminalConnection/AzureConnection-ARM64.h @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once +#include "AzureConnection.g.h" +#include "pch.h" + +namespace winrt::Microsoft::Terminal::TerminalConnection::implementation +{ + struct AzureConnection : AzureConnectionT + { + AzureConnection() = default; + + AzureConnection(uint32_t rows, uint32_t columns); + static bool IsAzureConnectionAvailable(); + winrt::event_token TerminalOutput(Microsoft::Terminal::TerminalConnection::TerminalOutputEventArgs const& handler); + void TerminalOutput(winrt::event_token const& token); + winrt::event_token TerminalDisconnected(Microsoft::Terminal::TerminalConnection::TerminalDisconnectedEventArgs const& handler); + void TerminalDisconnected(winrt::event_token const& token); + void Start(); + void WriteInput(hstring const& data); + void Resize(uint32_t rows, uint32_t columns); + void Close(); + }; +} +namespace winrt::Microsoft::Terminal::TerminalConnection::factory_implementation +{ + struct AzureConnection : AzureConnectionT + { + }; +} diff --git a/src/cascadia/TerminalConnection/AzureConnection.cpp b/src/cascadia/TerminalConnection/AzureConnection.cpp index e639371dc01..b93529d6706 100644 --- a/src/cascadia/TerminalConnection/AzureConnection.cpp +++ b/src/cascadia/TerminalConnection/AzureConnection.cpp @@ -23,8 +23,19 @@ using namespace web::websockets::client; using namespace concurrency::streams; using namespace winrt::Windows::Security::Credentials; +static constexpr int CurrentCredentialVersion = 1; + namespace winrt::Microsoft::Terminal::TerminalConnection::implementation { + // This file only builds for non-ARM64 so we don't need to check that here + // This function exists because the clientID only gets added by the release pipelines + // and is not available on local builds, so we want to be able to make sure we don't + // try to make an Azure connection if its a local build + bool AzureConnection::IsAzureConnectionAvailable() + { + return (AzureClientID != L"0"); + } + AzureConnection::AzureConnection(const uint32_t initialRows, const uint32_t initialCols) : _initialRows{ initialRows }, _initialCols{ initialCols } @@ -263,6 +274,20 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } } + // Method description: + // - This method returns a tenant's ID and display name (if one is available). + // If there is no display name, a placeholder is returned in its stead. + // Arguments: + // - tenant - the unparsed tenant + // Return value: + // - a tuple containing the ID and display name of the tenant. + static std::tuple _crackTenant(const json::value& tenant) + { + auto tenantId{ tenant.at(L"tenantId").as_string() }; + auto displayName{ tenant.has_string_field(L"displayName") ? tenant.at(L"displayName").as_string() : unknownTenantName }; + return { tenantId, displayName }; + } + // Method description: // - this method bridges the thread to the Azure connection instance // Arguments: @@ -378,6 +403,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation // - E_FAIL if the user closes the tab HRESULT AzureConnection::_AccessHelper() { + bool oldVersionEncountered = false; auto vault = PasswordVault(); winrt::Windows::Foundation::Collections::IVectorView credList; // FindAllByResource throws an exception if there are no credentials stored under the given resource so we wrap it in a try-catch block @@ -391,13 +417,37 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation _state = State::DeviceFlow; return S_FALSE; } - _maxStored = credList.Size(); - // Display the user's saved connection settings - for (int i = 0; i < _maxStored; i++) + _maxStored = 0; + for (const auto& entry : credList) { - auto entry = credList.GetAt(i); auto nameJson = json::value::parse(entry.UserName().c_str()); - _outputHandlers(_StrFormatHelper(ithTenant, i, nameJson.at(L"displayName").as_string().c_str(), nameJson.at(L"tenantID").as_string().c_str())); + std::optional credentialVersion; + if (nameJson.has_integer_field(U("ver"))) + { + credentialVersion = nameJson.at(U("ver")).as_integer(); + } + + if (!credentialVersion.has_value() || credentialVersion.value() != CurrentCredentialVersion) + { + // ignore credentials that aren't from the latest credential revision + vault.Remove(entry); + oldVersionEncountered = true; + continue; + } + + _outputHandlers(_StrFormatHelper(ithTenant, _maxStored, nameJson.at(L"displayName").as_string().c_str(), nameJson.at(L"tenantID").as_string().c_str())); + _maxStored++; + } + + if (!_maxStored) + { + if (oldVersionEncountered) + { + _outputHandlers(winrt::to_hstring(oldCredentialsFlushedMessage)); + } + // No valid up-to-date credentials were found, so start the device flow + _state = State::DeviceFlow; + return S_FALSE; } _outputHandlers(winrt::to_hstring(enterTenant)); @@ -499,8 +549,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } else if (_tenantList.size() == 1) { - _tenantID = tenantListAsArray.at(0).at(L"tenantId").as_string(); - _displayName = tenantListAsArray.at(0).at(L"displayName").as_string(); + const auto& chosenTenant = tenantListAsArray.at(0); + std::tie(_tenantID, _displayName) = _crackTenant(chosenTenant); // We have to refresh now that we have the tenantID const auto refreshResponse = _RefreshTokens(); @@ -524,34 +574,41 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation // - S_OK otherwise HRESULT AzureConnection::_TenantChoiceHelper() { - const auto tenantListAsArray = _tenantList.as_array(); - _maxSize = tenantListAsArray.size(); - for (int i = 0; i < _maxSize; i++) - { - _outputHandlers(_StrFormatHelper(ithTenant, i, tenantListAsArray.at(i).at(L"displayName").as_string().c_str(), tenantListAsArray.at(i).at(L"tenantId").as_string().c_str())); - } - _outputHandlers(winrt::to_hstring(enterTenant)); - // Use a lock to wait for the user to input a valid number - std::unique_lock tenantNumberLock{ _commonMutex }; - _canProceed.wait(tenantNumberLock, [=]() { - return (_tenantNumber >= 0 && _tenantNumber < _maxSize) || _closing.load(); - }); - // User might have closed the tab while we waited for input - if (_closing.load()) + try { - return E_FAIL; - } - _tenantID = tenantListAsArray.at(_tenantNumber).at(L"tenantId").as_string(); - _displayName = tenantListAsArray.at(_tenantNumber).at(L"displayName").as_string(); + const auto tenantListAsArray = _tenantList.as_array(); + _maxSize = gsl::narrow(tenantListAsArray.size()); + for (int i = 0; i < _maxSize; i++) + { + const auto& tenant = tenantListAsArray.at(i); + const auto [tenantId, tenantDisplayName] = _crackTenant(tenant); + _outputHandlers(_StrFormatHelper(ithTenant, i, tenantDisplayName.c_str(), tenantId.c_str())); + } + _outputHandlers(winrt::to_hstring(enterTenant)); + // Use a lock to wait for the user to input a valid number + std::unique_lock tenantNumberLock{ _commonMutex }; + _canProceed.wait(tenantNumberLock, [=]() { + return (_tenantNumber >= 0 && _tenantNumber < _maxSize) || _closing.load(); + }); + // User might have closed the tab while we waited for input + if (_closing.load()) + { + return E_FAIL; + } - // We have to refresh now that we have the tenantID - const auto refreshResponse = _RefreshTokens(); - _accessToken = refreshResponse.at(L"access_token").as_string(); - _refreshToken = refreshResponse.at(L"refresh_token").as_string(); - _expiry = std::stoi(refreshResponse.at(L"expires_on").as_string()); + const auto& chosenTenant = tenantListAsArray.at(_tenantNumber); + std::tie(_tenantID, _displayName) = _crackTenant(chosenTenant); - _state = State::StoreTokens; - return S_OK; + // We have to refresh now that we have the tenantID + const auto refreshResponse = _RefreshTokens(); + _accessToken = refreshResponse.at(L"access_token").as_string(); + _refreshToken = refreshResponse.at(L"refresh_token").as_string(); + _expiry = std::stoi(refreshResponse.at(L"expires_on").as_string()); + + _state = State::StoreTokens; + return S_OK; + } + CATCH_RETURN(); } // Method description: @@ -837,6 +894,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation { auto vault = PasswordVault(); json::value userName; + userName[U("ver")] = CurrentCredentialVersion; userName[U("displayName")] = json::value::string(_displayName); userName[U("tenantID")] = json::value::string(_tenantID); json::value passWord; diff --git a/src/cascadia/TerminalConnection/AzureConnection.h b/src/cascadia/TerminalConnection/AzureConnection.h index e747112a7b1..b6b889df714 100644 --- a/src/cascadia/TerminalConnection/AzureConnection.h +++ b/src/cascadia/TerminalConnection/AzureConnection.h @@ -15,6 +15,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation { struct AzureConnection : AzureConnectionT { + static bool IsAzureConnectionAvailable(); AzureConnection(const uint32_t rows, const uint32_t cols); winrt::event_token TerminalOutput(TerminalConnection::TerminalOutputEventArgs const& handler); diff --git a/src/cascadia/TerminalConnection/AzureConnection.idl b/src/cascadia/TerminalConnection/AzureConnection.idl index f0361c9037a..f925706c5d9 100644 --- a/src/cascadia/TerminalConnection/AzureConnection.idl +++ b/src/cascadia/TerminalConnection/AzureConnection.idl @@ -7,6 +7,8 @@ namespace Microsoft.Terminal.TerminalConnection { [default_interface] runtimeclass AzureConnection : ITerminalConnection { + static Boolean IsAzureConnectionAvailable(); + AzureConnection(UInt32 rows, UInt32 columns); }; diff --git a/src/cascadia/TerminalConnection/AzureConnectionStrings.h b/src/cascadia/TerminalConnection/AzureConnectionStrings.h index 7686df840ed..ec75d954bb7 100644 --- a/src/cascadia/TerminalConnection/AzureConnectionStrings.h +++ b/src/cascadia/TerminalConnection/AzureConnectionStrings.h @@ -26,5 +26,7 @@ const auto tokensRemoved = L"Tokens removed!\r\n"; const auto exitStr = L"Exit.\r\n"; const auto authString = L"Authenticated.\r\n"; const auto internetOrServerIssue = L"Could not connect to Azure. You may not have internet or the server might be down.\r\n"; +const auto oldCredentialsFlushedMessage = L"Authentication parameters changed. You'll need to log in again.\r\n"; +const auto unknownTenantName = L""; const auto ithTenant = L"Tenant %d: %s (%s)\r\n"; diff --git a/src/cascadia/TerminalConnection/ConhostConnection.cpp b/src/cascadia/TerminalConnection/ConhostConnection.cpp index 027004cda53..5c2280251eb 100644 --- a/src/cascadia/TerminalConnection/ConhostConnection.cpp +++ b/src/cascadia/TerminalConnection/ConhostConnection.cpp @@ -18,6 +18,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation { ConhostConnection::ConhostConnection(const hstring& commandline, const hstring& startingDirectory, + const hstring& startingTitle, const uint32_t initialRows, const uint32_t initialCols, const guid& initialGuid) : @@ -25,6 +26,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation _initialCols{ initialCols }, _commandline{ commandline }, _startingDirectory{ startingDirectory }, + _startingTitle{ startingTitle }, _guid{ initialGuid } { if (_guid == guid{}) @@ -79,6 +81,20 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation extraEnvVars.emplace(L"WT_SESSION", pwszGuid); } + STARTUPINFO si = { 0 }; + si.cb = sizeof(STARTUPINFOW); + + // If we have a startingTitle, create a mutable character buffer to add + // it to the STARTUPINFO. + std::unique_ptr mutableTitle{ nullptr }; + if (!_startingTitle.empty()) + { + mutableTitle = std::make_unique(_startingTitle.size() + 1); + THROW_IF_NULL_ALLOC(mutableTitle); + THROW_IF_FAILED(StringCchCopy(mutableTitle.get(), _startingTitle.size() + 1, _startingTitle.c_str())); + si.lpTitle = mutableTitle.get(); + } + THROW_IF_FAILED( CreateConPty(cmdline, startingDirectory, @@ -88,27 +104,14 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation &_outPipe, &_signalPipe, &_piConhost, - CREATE_SUSPENDED, + 0, + si, extraEnvVars)); - _hJob.reset(CreateJobObjectW(nullptr, nullptr)); - THROW_LAST_ERROR_IF_NULL(_hJob); - - // We want the conhost and all associated descendant processes - // to be terminated when the tab is closed. GUI applications - // spawned from the shell tend to end up in their own jobs. - JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobExtendedInformation{}; - jobExtendedInformation.BasicLimitInformation.LimitFlags = - JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; - - THROW_IF_WIN32_BOOL_FALSE(SetInformationJobObject(_hJob.get(), - JobObjectExtendedLimitInformation, - &jobExtendedInformation, - sizeof(jobExtendedInformation))); - - THROW_IF_WIN32_BOOL_FALSE(AssignProcessToJobObject(_hJob.get(), _piConhost.hProcess)); + _startTime = std::chrono::high_resolution_clock::now(); // Create our own output handling thread + // This must be done after the pipes are populated. // Each connection needs to make sure to drain the output from its backing host. _hOutputThread.reset(CreateThread(nullptr, 0, @@ -119,9 +122,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation THROW_LAST_ERROR_IF_NULL(_hOutputThread); - // Wind up the conhost! We only do this after we've got everything in place. - THROW_LAST_ERROR_IF(-1 == ResumeThread(_piConhost.hThread)); - _connected = true; } @@ -211,6 +211,21 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation return 0; } + if (!_recievedFirstByte) + { + auto now = std::chrono::high_resolution_clock::now(); + std::chrono::duration delta = now - _startTime; + + TraceLoggingWrite(g_hTerminalConnectionProvider, + "RecievedFirstByte", + TraceLoggingDescription("An event emitted when the connection recieves the first byte"), + TraceLoggingGuid(_guid, "SessionGuid", "The WT_SESSION's GUID"), + TraceLoggingFloat64(delta.count(), "Duration"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); + _recievedFirstByte = true; + } + // Convert buffer to hstring auto hstr{ winrt::to_hstring(strView) }; diff --git a/src/cascadia/TerminalConnection/ConhostConnection.h b/src/cascadia/TerminalConnection/ConhostConnection.h index e0f9bf82198..93aeada8d5a 100644 --- a/src/cascadia/TerminalConnection/ConhostConnection.h +++ b/src/cascadia/TerminalConnection/ConhostConnection.h @@ -9,7 +9,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation { struct ConhostConnection : ConhostConnectionT { - ConhostConnection(const hstring& cmdline, const hstring& startingDirectory, const uint32_t rows, const uint32_t cols, const guid& guid); + ConhostConnection(const hstring& cmdline, const hstring& startingDirectory, const hstring& startingTitle, const uint32_t rows, const uint32_t cols, const guid& guid); winrt::event_token TerminalOutput(TerminalConnection::TerminalOutputEventArgs const& handler); void TerminalOutput(winrt::event_token const& token) noexcept; @@ -30,10 +30,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation uint32_t _initialCols{}; hstring _commandline; hstring _startingDirectory; + hstring _startingTitle; guid _guid{}; // A unique session identifier for connected client bool _connected{}; std::atomic _closing{ false }; + bool _recievedFirstByte{ false }; + std::chrono::high_resolution_clock::time_point _startTime{}; wil::unique_hfile _inPipe; // The pipe for writing input to wil::unique_hfile _outPipe; // The pipe for reading output from diff --git a/src/cascadia/TerminalConnection/ConhostConnection.idl b/src/cascadia/TerminalConnection/ConhostConnection.idl index 00841cb94a5..50550c19030 100644 --- a/src/cascadia/TerminalConnection/ConhostConnection.idl +++ b/src/cascadia/TerminalConnection/ConhostConnection.idl @@ -5,10 +5,9 @@ import "ITerminalConnection.idl"; namespace Microsoft.Terminal.TerminalConnection { - [default_interface] - runtimeclass ConhostConnection : ITerminalConnection + [default_interface] runtimeclass ConhostConnection : ITerminalConnection { - ConhostConnection(String cmdline, String startingDirectory, UInt32 rows, UInt32 columns, Guid guid); + ConhostConnection(String cmdline, String startingDirectory, String startingTitle, UInt32 rows, UInt32 columns, Guid guid); Guid Guid { get; }; }; diff --git a/src/cascadia/TerminalConnection/TerminalConnection.vcxproj b/src/cascadia/TerminalConnection/TerminalConnection.vcxproj index 35c75d5a15b..cf277ae6486 100644 --- a/src/cascadia/TerminalConnection/TerminalConnection.vcxproj +++ b/src/cascadia/TerminalConnection/TerminalConnection.vcxproj @@ -24,6 +24,7 @@ + @@ -34,7 +35,9 @@ + + Create @@ -50,7 +53,7 @@ - + diff --git a/src/cascadia/TerminalConnection/init.cpp b/src/cascadia/TerminalConnection/init.cpp new file mode 100644 index 00000000000..6d3521c11e9 --- /dev/null +++ b/src/cascadia/TerminalConnection/init.cpp @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation +// Licensed under the MIT license. + +#include "pch.h" + +// Note: Generate GUID using TlgGuid.exe tool +TRACELOGGING_DEFINE_PROVIDER( + g_hTerminalConnectionProvider, + "Microsoft.Windows.Terminal.Connection", + // {e912fe7b-eeb6-52a5-c628-abe388e5f792} + (0xe912fe7b, 0xeeb6, 0x52a5, 0xc6, 0x28, 0xab, 0xe3, 0x88, 0xe5, 0xf7, 0x92), + TraceLoggingOptionMicrosoftTelemetry()); + +BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/) +{ + switch (reason) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hInstDll); + TraceLoggingRegister(g_hTerminalConnectionProvider); + break; + case DLL_PROCESS_DETACH: + if (g_hTerminalConnectionProvider) + { + TraceLoggingUnregister(g_hTerminalConnectionProvider); + } + break; + } + + return TRUE; +} diff --git a/src/cascadia/TerminalConnection/packages.config b/src/cascadia/TerminalConnection/packages.config index 5e77f0e0f5d..9bc27a32ead 100644 --- a/src/cascadia/TerminalConnection/packages.config +++ b/src/cascadia/TerminalConnection/packages.config @@ -1,5 +1,5 @@ ο»Ώ - + diff --git a/src/cascadia/TerminalConnection/pch.h b/src/cascadia/TerminalConnection/pch.h index 5878290603a..eff5c531e89 100644 --- a/src/cascadia/TerminalConnection/pch.h +++ b/src/cascadia/TerminalConnection/pch.h @@ -19,3 +19,7 @@ #include "winrt/Windows.Security.Credentials.h" #include "winrt/Windows.Foundation.Collections.h" #include + +#include +TRACELOGGING_DECLARE_PROVIDER(g_hTerminalConnectionProvider); +#include diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index c7e90c826a1..3aade82053b 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -11,6 +11,7 @@ #include "..\..\types\inc\GlyphWidth.hpp" #include "TermControl.g.cpp" +#include "TermControlAutomationPeer.h" using namespace ::Microsoft::Console::Types; using namespace ::Microsoft::Terminal::Core; @@ -30,7 +31,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation _connection{ connection }, _initializedTerminal{ false }, _root{ nullptr }, - _controlRoot{ nullptr }, _swapChainPanel{ nullptr }, _settings{ settings }, _closing{ false }, @@ -52,11 +52,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void TermControl::_Create() { - // Create a dummy UserControl to use as the "root" of our control we'll - // build manually. - Controls::UserControl myControl; - _controlRoot = myControl; - Controls::Grid container; Controls::ColumnDefinition contentColumn{}; @@ -108,20 +103,20 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation _bgImageLayer = bgImageLayer; _swapChainPanel = swapChainPanel; - _controlRoot.Content(_root); + this->Content(_root); _ApplyUISettings(); // These are important: // 1. When we get tapped, focus us - _controlRoot.Tapped([this](auto&, auto& e) { - _controlRoot.Focus(FocusState::Pointer); + this->Tapped([this](auto&, auto& e) { + this->Focus(FocusState::Pointer); e.Handled(true); }); // 2. Make sure we can be focused (why this isn't `Focusable` I'll never know) - _controlRoot.IsTabStop(true); + this->IsTabStop(true); // 3. Actually not sure about this one. Maybe it isn't necessary either. - _controlRoot.AllowFocusOnInteraction(true); + this->AllowFocusOnInteraction(true); // DON'T CALL _InitializeTerminal here - wait until the swap chain is loaded to do that. @@ -185,8 +180,23 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation _BackgroundColorChanged(bg); // Apply padding as swapChainPanel's margin - auto thickness = _ParseThicknessFromPadding(_settings.Padding()); - _swapChainPanel.Margin(thickness); + 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().c_str(); @@ -330,14 +340,26 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation Close(); } - UIElement TermControl::GetRoot() + Windows::UI::Xaml::Automation::Peers::AutomationPeer TermControl::OnCreateAutomationPeer() { - return _root; + // create a custom automation peer with this code pattern: + // (https://docs.microsoft.com/en-us/windows/uwp/design/accessibility/custom-automation-peers) + return winrt::make(*this); } - Controls::UserControl TermControl::GetControl() + ::Microsoft::Console::Types::IUiaData* TermControl::GetUiaData() const { - return _controlRoot; + return _terminal.get(); + } + + const FontInfo TermControl::GetActualFont() const + { + return _actualFont; + } + + const Windows::UI::Xaml::Thickness TermControl::GetPadding() const + { + return _swapChainPanel.Margin(); } void TermControl::SwapChainChanged() @@ -491,9 +513,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // through CharacterRecieved. // I don't believe there's a difference between KeyDown and // PreviewKeyDown for our purposes - // These two handlers _must_ be on _controlRoot, not _root. - _controlRoot.PreviewKeyDown({ this, &TermControl::_KeyDownHandler }); - _controlRoot.CharacterReceived({ this, &TermControl::_CharacterHandler }); + // 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); @@ -527,14 +549,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // import value from WinUser (convert from milli-seconds to micro-seconds) _multiClickTimer = GetDoubleClickTime() * 1000; - _gotFocusRevoker = _controlRoot.GotFocus(winrt::auto_revoke, { this, &TermControl::_GotFocusHandler }); - _lostFocusRevoker = _controlRoot.LostFocus(winrt::auto_revoke, { this, &TermControl::_LostFocusHandler }); + _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. - _controlRoot.Focus(FocusState::Programmatic); + this->Focus(FocusState::Programmatic); _connection.Start(); _initializedTerminal = true; @@ -600,42 +622,51 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation if (_closing || e.OriginalKey() == VirtualKey::Control || e.OriginalKey() == VirtualKey::Shift || - e.OriginalKey() == VirtualKey::Menu) + e.OriginalKey() == VirtualKey::Menu || + e.OriginalKey() == VirtualKey::LeftWindows || + e.OriginalKey() == VirtualKey::RightWindows) + { e.Handled(true); return; } const auto modifiers = _GetPressedModifierKeys(); - const auto vkey = static_cast(e.OriginalKey()); + // AltGr key combinations don't always contain any meaningful, + // pretranslated unicode character during WM_KEYDOWN. + // E.g. on a German keyboard AltGr+Q should result in a "@" character, + // but actually results in "Q" with Alt and Ctrl modifier states. + // By returning false though, we can abort handling this WM_KEYDOWN + // event and let the WM_CHAR handler kick in, which will be + // provided with an appropriate unicode character. + // + // GH#2235: Make sure to handle AltGr before trying keybindings, + // so Ctrl+Alt keybindings won't eat an AltGr keypress. + if (modifiers.IsAltGrPressed()) + { + _HandleVoidKeyEvent(); + e.Handled(false); + return; + } + + const auto vkey = static_cast(e.OriginalKey()); bool handled = false; + auto bindings = _settings.KeyBindings(); if (bindings) { - KeyChord chord( + handled = bindings.TryKeyChord({ modifiers.IsCtrlPressed(), modifiers.IsAltPressed(), modifiers.IsShiftPressed(), - vkey); - handled = bindings.TryKeyChord(chord); + vkey, + }); } if (!handled) { - _terminal->ClearSelection(); - // 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 CharacterRecieved event. - handled = _terminal->SendKeyEvent(vkey, modifiers); - - if (_cursorTimer.has_value()) - { - // Manually show the cursor when a key is pressed. Restarting - // the timer prevents flickering. - _terminal->SetCursorVisible(true); - _cursorTimer.value().Start(); - } + handled = _TrySendKeyEvent(vkey, modifiers); } // Manually prevent keyboard navigation with tab. We want to send tab to @@ -649,6 +680,45 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation e.Handled(handled); } + // Method Description: + // - Some key events cannot be handled (e.g. AltGr combinations) and are + // delegated to the character handler. Just like with _TrySendKeyEvent(), + // the character handler counts on us though to: + // - Clears the current selection. + // - Makes the cursor briefly visible during typing. + void TermControl::_HandleVoidKeyEvent() + { + _TrySendKeyEvent(0, {}); + } + + // 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(WORD vkey, const ControlKeyStates modifiers) + { + _terminal->ClearSelection(); + + // 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 CharacterRecieved event. + const auto handled = vkey ? _terminal->SendKeyEvent(vkey, 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: @@ -662,7 +732,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation const auto ptr = args.Pointer(); const auto point = args.GetCurrentPoint(_root); - if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse) + 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 @@ -716,15 +786,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } else if (point.Properties().IsRightButtonPressed()) { - // copy selection, if one exists - if (_terminal->IsSelectionActive()) + // copyOnSelect causes right-click to always paste + if (_terminal->IsCopyOnSelectActive() || !_terminal->IsSelectionActive()) { - CopySelectionToClipboard(!shiftEnabled); + PasteTextFromClipboard(); } - // paste selection, otherwise else { - PasteTextFromClipboard(); + CopySelectionToClipboard(!shiftEnabled); } } } @@ -749,9 +818,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation const auto ptr = args.Pointer(); const auto point = args.GetCurrentPoint(_root); - if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse) + if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse || ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Pen) { - if (_terminal->IsSelectionActive() && point.Properties().IsLeftButtonPressed()) + if (point.Properties().IsLeftButtonPressed()) { const auto cursorPosition = point.Position(); _SetEndSelectionPointAtCursor(cursorPosition); @@ -828,7 +897,19 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation const auto ptr = args.Pointer(); - if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Touch) + 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; } @@ -1330,19 +1411,41 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } // Method Description: - // - get text from buffer and send it to the Windows Clipboard (CascadiaWin32:main.cpp). Also removes rendering of selection. + // - 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. - void TermControl::CopySelectionToClipboard(bool trimTrailingWhitespace) + bool TermControl::CopySelectionToClipboard(bool trimTrailingWhitespace) { + // no selection --> nothing to copy + if (_terminal == nullptr || !_terminal->IsSelectionActive()) + { + return false; + } // extract text from buffer - const auto copiedData = _terminal->RetrieveSelectedTextFromBuffer(trimTrailingWhitespace); + const auto bufferData = _terminal->RetrieveSelectedTextFromBuffer(trimTrailingWhitespace); - _terminal->ClearSelection(); + // 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(), "Windows Terminal"); + + if (!_terminal->IsCopyOnSelectActive()) + { + _terminal->ClearSelection(); + } // send data up for clipboard - _clipboardCopyHandlers(copiedData); + auto copyArgs = winrt::make_self(winrt::hstring(textData.data(), gsl::narrow(textData.size())), winrt::to_hstring(htmlData)); + _clipboardCopyHandlers(*this, *copyArgs); + return true; } // Method Description: @@ -1740,9 +1843,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // These macros will define them both for you. DEFINE_EVENT(TermControl, TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs); DEFINE_EVENT(TermControl, ConnectionClosed, _connectionClosedHandlers, TerminalControl::ConnectionClosedEventArgs); - DEFINE_EVENT(TermControl, CopyToClipboard, _clipboardCopyHandlers, TerminalControl::CopyToClipboardEventArgs); DEFINE_EVENT(TermControl, ScrollPositionChanged, _scrollPositionChangedHandlers, TerminalControl::ScrollPositionChangedEventArgs); - // clang-format on 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 57892b4924a..14a9e4045bf 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -4,6 +4,7 @@ #pragma once #include "TermControl.g.h" +#include "CopyToClipboardEventArgs.g.h" #include "PasteFromClipboardEventArgs.g.h" #include #include @@ -14,6 +15,22 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation { + struct CopyToClipboardEventArgs : + public CopyToClipboardEventArgsT + { + public: + CopyToClipboardEventArgs(hstring text, hstring html) : + _text(text), + _html(html) {} + + hstring Text() { return _text; }; + hstring Html() { return _html; }; + + private: + hstring _text; + hstring _html; + }; + struct PasteFromClipboardEventArgs : public PasteFromClipboardEventArgsT { @@ -35,12 +52,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation TermControl(); TermControl(Settings::IControlSettings settings, TerminalConnection::ITerminalConnection connection); - Windows::UI::Xaml::UIElement GetRoot(); - Windows::UI::Xaml::Controls::UserControl GetControl(); void UpdateSettings(Settings::IControlSettings newSettings); hstring Title(); - void CopySelectionToClipboard(bool trimTrailingWhitespace); + + bool CopySelectionToClipboard(bool trimTrailingWhitespace); void PasteTextFromClipboard(); void Close(); bool ShouldCloseOnExit() const noexcept; @@ -55,6 +71,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void SwapChainChanged(); ~TermControl(); + Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer(); + ::Microsoft::Console::Types::IUiaData* GetUiaData() const; + const FontInfo GetActualFont() const; + const Windows::UI::Xaml::Thickness GetPadding() const; + static Windows::Foundation::Point GetProposedDimensions(Microsoft::Terminal::Settings::IControlSettings const& settings, const uint32_t dpi); // clang-format off @@ -62,16 +83,15 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation DECLARE_EVENT(TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs); DECLARE_EVENT(ConnectionClosed, _connectionClosedHandlers, TerminalControl::ConnectionClosedEventArgs); DECLARE_EVENT(ScrollPositionChanged, _scrollPositionChangedHandlers, TerminalControl::ScrollPositionChangedEventArgs); - DECLARE_EVENT(CopyToClipboard, _clipboardCopyHandlers, TerminalControl::CopyToClipboardEventArgs); - DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(PasteFromClipboard, _clipboardPasteHandlers, TerminalControl::TermControl, TerminalControl::PasteFromClipboardEventArgs); + DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(PasteFromClipboard, _clipboardPasteHandlers, TerminalControl::TermControl, TerminalControl::PasteFromClipboardEventArgs); + DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(CopyToClipboard, _clipboardCopyHandlers, TerminalControl::TermControl, TerminalControl::CopyToClipboardEventArgs); // clang-format on private: TerminalConnection::ITerminalConnection _connection; bool _initializedTerminal; - Windows::UI::Xaml::Controls::UserControl _controlRoot; Windows::UI::Xaml::Controls::Grid _root; Windows::UI::Xaml::Controls::Image _bgImageLayer; Windows::UI::Xaml::Controls::SwapChainPanel _swapChainPanel; @@ -164,6 +184,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation static Windows::UI::Xaml::Thickness _ParseThicknessFromPadding(const hstring padding); ::Microsoft::Terminal::Core::ControlKeyStates _GetPressedModifierKeys() const; + void _HandleVoidKeyEvent(); + bool _TrySendKeyEvent(WORD vkey, ::Microsoft::Terminal::Core::ControlKeyStates modifiers); const COORD _GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition); const unsigned int _NumberOfClicks(winrt::Windows::Foundation::Point clickPos, Timestamp clickTime); diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index 69ef35670e7..9f9ac42484d 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -6,32 +6,35 @@ namespace Microsoft.Terminal.TerminalControl delegate void TitleChangedEventArgs(String newTitle); delegate void ConnectionClosedEventArgs(); delegate void ScrollPositionChangedEventArgs(Int32 viewTop, Int32 viewHeight, Int32 bufferLength); - delegate void CopyToClipboardEventArgs(String copiedData); + + runtimeclass CopyToClipboardEventArgs + { + String Text { get; }; + String Html { get; }; + } runtimeclass PasteFromClipboardEventArgs { void HandleClipboardData(String data); } - [default_interface] - runtimeclass TermControl + [default_interface] runtimeclass TermControl : Windows.UI.Xaml.Controls.UserControl { TermControl(); TermControl(Microsoft.Terminal.Settings.IControlSettings settings, Microsoft.Terminal.TerminalConnection.ITerminalConnection connection); static Windows.Foundation.Point GetProposedDimensions(Microsoft.Terminal.Settings.IControlSettings settings, UInt32 dpi); - Windows.UI.Xaml.UIElement GetRoot(); - Windows.UI.Xaml.Controls.UserControl GetControl(); void UpdateSettings(Microsoft.Terminal.Settings.IControlSettings newSettings); event TitleChangedEventArgs TitleChanged; event ConnectionClosedEventArgs ConnectionClosed; - event CopyToClipboardEventArgs CopyToClipboard; + event Windows.Foundation.TypedEventHandler CopyToClipboard; event Windows.Foundation.TypedEventHandler PasteFromClipboard; String Title { get; }; - void CopySelectionToClipboard(Boolean trimTrailingWhitespace); + + Boolean CopySelectionToClipboard(Boolean trimTrailingWhitespace); void PasteTextFromClipboard(); void Close(); Boolean ShouldCloseOnExit { get; }; diff --git a/src/cascadia/TerminalControl/TermControlAutomationPeer.cpp b/src/cascadia/TerminalControl/TermControlAutomationPeer.cpp new file mode 100644 index 00000000000..18d9f6abb44 --- /dev/null +++ b/src/cascadia/TerminalControl/TermControlAutomationPeer.cpp @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include +#include "TermControlAutomationPeer.h" +#include "TermControl.h" +#include "TermControlAutomationPeer.g.cpp" + +#include "XamlUiaTextRange.h" + +using namespace Microsoft::Console::Types; +using namespace winrt::Windows::UI::Xaml::Automation::Peers; + +namespace UIA +{ + using ::ITextRangeProvider; + using ::SupportedTextSelection; +} + +namespace XamlAutomation +{ + using winrt::Windows::UI::Xaml::Automation::SupportedTextSelection; + using winrt::Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple; + using winrt::Windows::UI::Xaml::Automation::Provider::ITextRangeProvider; +} + +namespace winrt::Microsoft::Terminal::TerminalControl::implementation +{ + TermControlAutomationPeer::TermControlAutomationPeer(winrt::Microsoft::Terminal::TerminalControl::implementation::TermControl const& owner) : + TermControlAutomationPeerT(owner), // pass owner to FrameworkElementAutomationPeer + _uiaProvider{ owner, std::bind(&TermControlAutomationPeer::GetBoundingRectWrapped, this) } {}; + + winrt::hstring TermControlAutomationPeer::GetClassNameCore() const + { + return L"TermControl"; + } + + AutomationControlType TermControlAutomationPeer::GetAutomationControlTypeCore() const + { + return AutomationControlType::Text; + } + + winrt::hstring TermControlAutomationPeer::GetLocalizedControlTypeCore() const + { + // TODO GitHub #2142: Localize string + return L"TerminalControl"; + } + + winrt::Windows::Foundation::IInspectable TermControlAutomationPeer::GetPatternCore(PatternInterface patternInterface) const + { + switch (patternInterface) + { + case PatternInterface::Text: + return *this; + break; + default: + return nullptr; + } + } + +#pragma region ITextProvider + winrt::com_array TermControlAutomationPeer::GetSelection() + { + SAFEARRAY* pReturnVal; + THROW_IF_FAILED(_uiaProvider.GetSelection(&pReturnVal)); + return WrapArrayOfTextRangeProviders(pReturnVal); + } + + winrt::com_array TermControlAutomationPeer::GetVisibleRanges() + { + SAFEARRAY* pReturnVal; + THROW_IF_FAILED(_uiaProvider.GetVisibleRanges(&pReturnVal)); + return WrapArrayOfTextRangeProviders(pReturnVal); + } + + XamlAutomation::ITextRangeProvider TermControlAutomationPeer::RangeFromChild(XamlAutomation::IRawElementProviderSimple childElement) + { + UIA::ITextRangeProvider* returnVal; + // ScreenInfoUiaProvider doesn't actually use parameter, so just pass in nullptr + THROW_IF_FAILED(_uiaProvider.RangeFromChild(/* IRawElementProviderSimple */ nullptr, + &returnVal)); + + auto parentProvider = this->ProviderFromPeer(*this); + auto xutr = winrt::make_self(returnVal, parentProvider); + return xutr.as(); + } + + XamlAutomation::ITextRangeProvider TermControlAutomationPeer::RangeFromPoint(Windows::Foundation::Point screenLocation) + { + UIA::ITextRangeProvider* returnVal; + THROW_IF_FAILED(_uiaProvider.RangeFromPoint({ screenLocation.X, screenLocation.Y }, &returnVal)); + + auto parentProvider = this->ProviderFromPeer(*this); + auto xutr = winrt::make_self(returnVal, parentProvider); + return xutr.as(); + } + + XamlAutomation::ITextRangeProvider TermControlAutomationPeer::DocumentRange() + { + UIA::ITextRangeProvider* returnVal; + THROW_IF_FAILED(_uiaProvider.get_DocumentRange(&returnVal)); + + auto parentProvider = this->ProviderFromPeer(*this); + auto xutr = winrt::make_self(returnVal, parentProvider); + return xutr.as(); + } + + Windows::UI::Xaml::Automation::SupportedTextSelection TermControlAutomationPeer::SupportedTextSelection() + { + UIA::SupportedTextSelection returnVal; + THROW_IF_FAILED(_uiaProvider.get_SupportedTextSelection(&returnVal)); + return static_cast(returnVal); + } + +#pragma endregion + + RECT TermControlAutomationPeer::GetBoundingRectWrapped() + { + auto rect = GetBoundingRectangle(); + return { + gsl::narrow(rect.X), + gsl::narrow(rect.Y), + gsl::narrow(rect.X + rect.Width), + gsl::narrow(rect.Y + rect.Height) + }; + } + + // Method Description: + // - extracts the UiaTextRanges from the SAFEARRAY and converts them to Xaml ITextRangeProviders + // Arguments: + // - SAFEARRAY of UIA::UiaTextRange (ITextRangeProviders) + // Return Value: + // - com_array of Xaml Wrapped UiaTextRange (ITextRangeProviders) + winrt::com_array TermControlAutomationPeer::WrapArrayOfTextRangeProviders(SAFEARRAY* textRanges) + { + // transfer ownership of UiaTextRanges to this new vector + auto providers = SafeArrayToOwningVector<::Microsoft::Terminal::UiaTextRange>(textRanges); + int count = gsl::narrow(providers.size()); + + std::vector vec; + vec.reserve(count); + auto parentProvider = this->ProviderFromPeer(*this); + for (int i = 0; i < count; i++) + { + auto xutr = winrt::make_self(providers[i].detach(), parentProvider); + vec.emplace_back(xutr.as()); + } + + winrt::com_array result{ vec }; + + return result; + } +} diff --git a/src/cascadia/TerminalControl/TermControlAutomationPeer.h b/src/cascadia/TerminalControl/TermControlAutomationPeer.h new file mode 100644 index 00000000000..e25db477a4d --- /dev/null +++ b/src/cascadia/TerminalControl/TermControlAutomationPeer.h @@ -0,0 +1,63 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- TermControlAutomationPeer.h + +Abstract: +- This module provides UI Automation access to the TermControl + to support both automation tests and accessibility (screen + reading) applications. This mainly interacts with ScreenInfoUiaProvider + to allow for shared code between ConHost and Windows Terminal + accessibility providers. +- Based on the Custom Automation Peers guide on msdn + (https://docs.microsoft.com/en-us/windows/uwp/design/accessibility/custom-automation-peers) +- Wraps the UIAutomationCore ITextProvider + (https://docs.microsoft.com/en-us/windows/win32/api/uiautomationcore/nn-uiautomationcore-itextprovider) + with a XAML ITextProvider + (https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.automation.provider.itextprovider) + +Author(s): +- Carlos Zamora (CaZamor) 2019 +--*/ + +#pragma once + +#include "TermControl.h" +#include "TermControlAutomationPeer.g.h" +#include +#include "../../renderer/inc/IRenderData.hpp" +#include "../types/WindowUiaProviderBase.hpp" +#include "TermControlUiaProvider.hpp" + +namespace winrt::Microsoft::Terminal::TerminalControl::implementation +{ + struct TermControlAutomationPeer : + public TermControlAutomationPeerT + { + public: + TermControlAutomationPeer(winrt::Microsoft::Terminal::TerminalControl::implementation::TermControl const& owner); + + winrt::hstring GetClassNameCore() const; + winrt::Windows::UI::Xaml::Automation::Peers::AutomationControlType GetAutomationControlTypeCore() const; + winrt::hstring GetLocalizedControlTypeCore() const; + winrt::Windows::Foundation::IInspectable GetPatternCore(winrt::Windows::UI::Xaml::Automation::Peers::PatternInterface patternInterface) const; + +#pragma region ITextProvider Pattern + Windows::UI::Xaml::Automation::Provider::ITextRangeProvider RangeFromPoint(Windows::Foundation::Point screenLocation); + Windows::UI::Xaml::Automation::Provider::ITextRangeProvider RangeFromChild(Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple childElement); + winrt::com_array GetVisibleRanges(); + winrt::com_array GetSelection(); + Windows::UI::Xaml::Automation::SupportedTextSelection SupportedTextSelection(); + Windows::UI::Xaml::Automation::Provider::ITextRangeProvider DocumentRange(); +#pragma endregion + + RECT GetBoundingRectWrapped(); + + private: + ::Microsoft::Terminal::TermControlUiaProvider _uiaProvider; + + winrt::com_array WrapArrayOfTextRangeProviders(SAFEARRAY* textRanges); + }; +} diff --git a/src/cascadia/TerminalControl/TermControlAutomationPeer.idl b/src/cascadia/TerminalControl/TermControlAutomationPeer.idl new file mode 100644 index 00000000000..d979c82471f --- /dev/null +++ b/src/cascadia/TerminalControl/TermControlAutomationPeer.idl @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "TermControl.idl"; + +namespace Microsoft.Terminal.TerminalControl +{ + [default_interface] + runtimeclass TermControlAutomationPeer : + Windows.UI.Xaml.Automation.Peers.FrameworkElementAutomationPeer, + Windows.UI.Xaml.Automation.Provider.ITextProvider + { + } +} diff --git a/src/cascadia/TerminalControl/TermControlUiaProvider.cpp b/src/cascadia/TerminalControl/TermControlUiaProvider.cpp new file mode 100644 index 00000000000..19298e86214 --- /dev/null +++ b/src/cascadia/TerminalControl/TermControlUiaProvider.cpp @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "TermControlUiaProvider.hpp" +#include "TermControl.h" + +using namespace Microsoft::Terminal; +using namespace Microsoft::Console::Types; + +TermControlUiaProvider::TermControlUiaProvider(_In_ winrt::Microsoft::Terminal::TerminalControl::implementation::TermControl const& termControl, + _In_ std::function GetBoundingRect) : + _getBoundingRect(GetBoundingRect), + _termControl(termControl), + ScreenInfoUiaProviderBase(THROW_HR_IF_NULL(E_INVALIDARG, termControl.GetUiaData())) +{ + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(nullptr, ApiCall::Constructor, nullptr); +} + +IFACEMETHODIMP TermControlUiaProvider::Navigate(_In_ NavigateDirection direction, + _COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider) +{ + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + /*ApiMsgNavigate apiMsg; + apiMsg.Direction = direction; + Tracing::s_TraceUia(this, ApiCall::Navigate, &apiMsg);*/ + *ppProvider = nullptr; + + if (direction == NavigateDirection_Parent) + { + try + { + // TODO GitHub #2102: UIA Tree Navigation + //_pUiaParent->QueryInterface(IID_PPV_ARGS(ppProvider)); + } + catch (...) + { + *ppProvider = nullptr; + return wil::ResultFromCaughtException(); + } + RETURN_IF_NULL_ALLOC(*ppProvider); + } + + // For the other directions the default of nullptr is correct + return S_OK; +} + +IFACEMETHODIMP TermControlUiaProvider::get_BoundingRectangle(_Out_ UiaRect* pRect) +{ + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::GetBoundingRectangle, nullptr); + + RECT rc = _getBoundingRect(); + + pRect->left = rc.left; + pRect->top = rc.top; + pRect->width = rc.right - rc.left; + pRect->height = rc.bottom - rc.top; + + return S_OK; +} + +IFACEMETHODIMP TermControlUiaProvider::get_FragmentRoot(_COM_Outptr_result_maybenull_ IRawElementProviderFragmentRoot** ppProvider) +{ + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::GetFragmentRoot, nullptr); + try + { + // TODO GitHub #2102: UIA Tree Navigation - the special fragments that knows about all of its descendants is called a fragment root + //_pUiaParent->QueryInterface(IID_PPV_ARGS(ppProvider)); + *ppProvider = nullptr; + } + catch (...) + { + *ppProvider = nullptr; + return wil::ResultFromCaughtException(); + } + RETURN_IF_NULL_ALLOC(*ppProvider); + return S_OK; +} + +const COORD TermControlUiaProvider::GetFontSize() const +{ + return _termControl.GetActualFont().GetSize(); +} + +const winrt::Windows::UI::Xaml::Thickness TermControlUiaProvider::GetPadding() const +{ + return _termControl.GetPadding(); +} + +std::deque TermControlUiaProvider::GetSelectionRanges(_In_ IRawElementProviderSimple* const pProvider) +{ + std::deque result; + + auto ranges = UiaTextRange::GetSelectionRanges(_pData, pProvider); + while (!ranges.empty()) + { + result.emplace_back(ranges.back()); + ranges.pop_back(); + } + + return result; +} + +UiaTextRangeBase* TermControlUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider) +{ + return UiaTextRange::Create(_pData, pProvider); +} + +UiaTextRangeBase* TermControlUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, + const Cursor& cursor) +{ + return UiaTextRange::Create(_pData, pProvider, cursor); +} + +UiaTextRangeBase* TermControlUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, + const Endpoint start, + const Endpoint end, + const bool degenerate) +{ + return UiaTextRange::Create(_pData, pProvider, start, end, degenerate); +} + +UiaTextRangeBase* TermControlUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, + const UiaPoint point) +{ + return UiaTextRange::Create(_pData, pProvider, point); +} diff --git a/src/cascadia/TerminalControl/TermControlUiaProvider.hpp b/src/cascadia/TerminalControl/TermControlUiaProvider.hpp new file mode 100644 index 00000000000..b71211502ba --- /dev/null +++ b/src/cascadia/TerminalControl/TermControlUiaProvider.hpp @@ -0,0 +1,72 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- TermControlUiaProvider.hpp + +Abstract: +- This module provides UI Automation access to the screen buffer to + support both automation tests and accessibility (screen reading) + applications. +- ConHost and Windows Terminal must use IRenderData to have access to the proper information +- Based on examples, sample code, and guidance from + https://msdn.microsoft.com/en-us/library/windows/desktop/ee671596(v=vs.85).aspx + +Author(s): +- Carlos Zamora (CaZamor) 2019 +--*/ + +#pragma once + +#include "..\types\ScreenInfoUiaProviderBase.h" +#include "..\types\UiaTextRangeBase.hpp" +#include "UiaTextRange.hpp" + +namespace winrt::Microsoft::Terminal::TerminalControl::implementation +{ + struct TermControl; +} + +namespace Microsoft::Terminal +{ + class TermControlUiaProvider : public Microsoft::Console::Types::ScreenInfoUiaProviderBase + { + public: + TermControlUiaProvider(_In_ winrt::Microsoft::Terminal::TerminalControl::implementation::TermControl const& termControl, + _In_ std::function GetBoundingRect); + + // IRawElementProviderFragment methods + IFACEMETHODIMP Navigate(_In_ NavigateDirection direction, + _COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider) override; + IFACEMETHODIMP get_BoundingRectangle(_Out_ UiaRect* pRect) override; + IFACEMETHODIMP get_FragmentRoot(_COM_Outptr_result_maybenull_ IRawElementProviderFragmentRoot** ppProvider) override; + + const COORD GetFontSize() const; + const winrt::Windows::UI::Xaml::Thickness GetPadding() const; + + protected: + std::deque GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider) override; + + // degenerate range + Microsoft::Console::Types::UiaTextRangeBase* CreateTextRange(_In_ IRawElementProviderSimple* const pProvider) override; + + // degenerate range at cursor position + Microsoft::Console::Types::UiaTextRangeBase* CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, + const Cursor& cursor) override; + + // specific endpoint range + Microsoft::Console::Types::UiaTextRangeBase* CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, + const Endpoint start, + const Endpoint end, + const bool degenerate) override; + + // range from a UiaPoint + Microsoft::Console::Types::UiaTextRangeBase* CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, + const UiaPoint point) override; + + private: + std::function _getBoundingRect; + winrt::Microsoft::Terminal::TerminalControl::implementation::TermControl const& _termControl; + }; +} diff --git a/src/cascadia/TerminalControl/TerminalControl.vcxproj b/src/cascadia/TerminalControl/TerminalControl.vcxproj index 1fd40b42984..a67e2ef476c 100644 --- a/src/cascadia/TerminalControl/TerminalControl.vcxproj +++ b/src/cascadia/TerminalControl/TerminalControl.vcxproj @@ -17,21 +17,35 @@ + TermControl.idl + + TermControlAutomationPeer.idl + + + Create + + TermControl.idl + + TermControlAutomationPeer.idl + + + + @@ -49,7 +63,7 @@ false - + false false diff --git a/src/cascadia/TerminalControl/TerminalControl.vcxproj.filters b/src/cascadia/TerminalControl/TerminalControl.vcxproj.filters index f2869cf5bea..888488b3f7a 100644 --- a/src/cascadia/TerminalControl/TerminalControl.vcxproj.filters +++ b/src/cascadia/TerminalControl/TerminalControl.vcxproj.filters @@ -11,20 +11,24 @@ - + + + + - + + + + - - - + @@ -33,4 +37,4 @@ - \ No newline at end of file + diff --git a/src/cascadia/TerminalControl/UiaTextRange.cpp b/src/cascadia/TerminalControl/UiaTextRange.cpp new file mode 100644 index 00000000000..93133347a7e --- /dev/null +++ b/src/cascadia/TerminalControl/UiaTextRange.cpp @@ -0,0 +1,224 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "UiaTextRange.hpp" +#include "TermControlUiaProvider.hpp" + +using namespace Microsoft::Terminal; +using namespace Microsoft::Console::Types; + +std::deque UiaTextRange::GetSelectionRanges(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* pProvider) +{ + std::deque ranges; + + // get the selection rects + const auto rectangles = pData->GetSelectionRects(); + + // create a range for each row + for (const auto& rect : rectangles) + { + ScreenInfoRow currentRow = rect.Top(); + Endpoint start = _screenInfoRowToEndpoint(pData, currentRow) + rect.Left(); + Endpoint end = _screenInfoRowToEndpoint(pData, currentRow) + rect.RightInclusive(); + UiaTextRange* range = UiaTextRange::Create(pData, + pProvider, + start, + end, + false); + if (range == nullptr) + { + // something went wrong, clean up and throw + while (!ranges.empty()) + { + UiaTextRangeBase* temp = ranges[0]; + ranges.pop_front(); + } + THROW_HR(E_INVALIDARG); + } + else + { + ranges.push_back(range); + } + } + return ranges; +} + +UiaTextRange* UiaTextRange::Create(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider) +{ + try + { + return new UiaTextRange(pData, pProvider); + } + catch (...) + { + return nullptr; + } +} + +UiaTextRange* UiaTextRange::Create(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const Cursor& cursor) +{ + try + { + return new UiaTextRange(pData, pProvider, cursor); + } + catch (...) + { + return nullptr; + } +} + +UiaTextRange* UiaTextRange::Create(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const Endpoint start, + const Endpoint end, + const bool degenerate) +{ + try + { + return new UiaTextRange(pData, + pProvider, + start, + end, + degenerate); + } + catch (...) + { + return nullptr; + } +} + +UiaTextRange* UiaTextRange::Create(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const UiaPoint point) +{ + try + { + return new UiaTextRange(pData, pProvider, point); + } + catch (...) + { + return nullptr; + } +} + +// degenerate range constructor. +UiaTextRange::UiaTextRange(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider) : + UiaTextRangeBase(pData, pProvider) +{ +} + +UiaTextRange::UiaTextRange(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const Cursor& cursor) : + UiaTextRangeBase(pData, pProvider, cursor) +{ +} + +UiaTextRange::UiaTextRange(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const Endpoint start, + const Endpoint end, + const bool degenerate) : + UiaTextRangeBase(pData, pProvider, start, end, degenerate) +{ +} + +// returns a degenerate text range of the start of the row closest to the y value of point +UiaTextRange::UiaTextRange(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const UiaPoint point) : + UiaTextRangeBase(pData, pProvider) +{ + Initialize(point); +} + +IFACEMETHODIMP UiaTextRange::Clone(_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) +{ + RETURN_HR_IF(E_INVALIDARG, ppRetVal == nullptr); + *ppRetVal = nullptr; + try + { + *ppRetVal = new UiaTextRange(*this); + } + catch (...) + { + *ppRetVal = nullptr; + return wil::ResultFromCaughtException(); + } + if (*ppRetVal == nullptr) + { + return E_OUTOFMEMORY; + } + +#if defined(_DEBUG) && defined(UiaTextRangeBase_DEBUG_MSGS) + OutputDebugString(L"Clone\n"); + std::wstringstream ss; + ss << _id << L" cloned to " << (static_cast(*ppRetVal))->_id; + std::wstring str = ss.str(); + OutputDebugString(str.c_str()); + OutputDebugString(L"\n"); +#endif + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + // tracing + /*ApiMsgClone apiMsg; + apiMsg.CloneId = static_cast(*ppRetVal)->GetId(); + Tracing::s_TraceUia(this, ApiCall::Clone, &apiMsg);*/ + + return S_OK; +} + +IFACEMETHODIMP UiaTextRange::FindText(_In_ BSTR text, + _In_ BOOL searchBackward, + _In_ BOOL ignoreCase, + _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) +{ + // TODO GitHub #605: Search functionality + return E_NOTIMPL; +} + +void UiaTextRange::_ChangeViewport(const SMALL_RECT /*NewWindow*/) +{ + // TODO GitHub #2361: Update viewport when calling UiaTextRangeBase::ScrollIntoView() +} + +// Method Description: +// - Transform coordinates relative to the client to relative to the screen +// Arguments: +// - clientPoint: coordinates relative to the client where +// (0,0) is the top-left of the app window +// Return Value: +// - +void UiaTextRange::_TranslatePointToScreen(LPPOINT clientPoint) const +{ + auto provider = static_cast(_pProvider.get()); + + // 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); +} + +void UiaTextRange::_TranslatePointFromScreen(LPPOINT /*screenPoint*/) const +{ + // TODO GitHub #2103: NON-HWND IMPLEMENTATION OF SCREENTOCLIENT() +} + +const COORD UiaTextRange::_getScreenFontSize() const +{ + // Do NOT get the font info from IRenderData. It is a dummy font info. + // Instead, the font info is saved in the TermControl. So we have to + // ask our parent to get it for us. + auto provider = static_cast(_pProvider.get()); + return provider->GetFontSize(); +} diff --git a/src/cascadia/TerminalControl/UiaTextRange.hpp b/src/cascadia/TerminalControl/UiaTextRange.hpp new file mode 100644 index 00000000000..020373b4302 --- /dev/null +++ b/src/cascadia/TerminalControl/UiaTextRange.hpp @@ -0,0 +1,84 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- UiaTextRange.hpp + +Abstract: +- This module provides UI Automation access to the text of the console + window to support both automation tests and accessibility (screen + reading) applications. + +Author(s): +- Carlos Zamora (CaZamor) 2019 +--*/ + +#pragma once + +#include "..\types\UiaTextRangeBase.hpp" + +namespace Microsoft::Terminal +{ + class UiaTextRange final : public Microsoft::Console::Types::UiaTextRangeBase + { + public: + static std::deque GetSelectionRanges(_In_ Microsoft::Console::Types::IUiaData* pData, + _In_ IRawElementProviderSimple* pProvider); + + // degenerate range + static UiaTextRange* Create(_In_ Microsoft::Console::Types::IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider); + + // degenerate range at cursor position + static UiaTextRange* Create(_In_ Microsoft::Console::Types::IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const Cursor& cursor); + + // specific endpoint range + static UiaTextRange* Create(_In_ Microsoft::Console::Types::IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const Endpoint start, + const Endpoint end, + const bool degenerate); + + // range from a UiaPoint + static UiaTextRange* Create(_In_ Microsoft::Console::Types::IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const UiaPoint point); + + IFACEMETHODIMP Clone(_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) override; + IFACEMETHODIMP FindText(_In_ BSTR text, + _In_ BOOL searchBackward, + _In_ BOOL ignoreCase, + _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) override; + + protected: + void _ChangeViewport(const SMALL_RECT NewWindow) override; + void _TranslatePointToScreen(LPPOINT clientPoint) const override; + void _TranslatePointFromScreen(LPPOINT screenPoint) const override; + const COORD _getScreenFontSize() const override; + + private: + // degenerate range + UiaTextRange(_In_ Microsoft::Console::Types::IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider); + + // degenerate range at cursor position + UiaTextRange(_In_ Microsoft::Console::Types::IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const Cursor& cursor); + + // specific endpoint range + UiaTextRange(_In_ Microsoft::Console::Types::IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const Endpoint start, + const Endpoint end, + const bool degenerate); + + // range from a UiaPoint + UiaTextRange(_In_ Microsoft::Console::Types::IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const UiaPoint point); + }; +} diff --git a/src/cascadia/TerminalControl/XamlUiaTextRange.cpp b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp new file mode 100644 index 00000000000..4ca5c524590 --- /dev/null +++ b/src/cascadia/TerminalControl/XamlUiaTextRange.cpp @@ -0,0 +1,199 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "XamlUiaTextRange.h" +#include "UiaTextRange.hpp" + +namespace UIA +{ + using ::ITextRangeProvider; + using ::SupportedTextSelection; + using ::TextPatternRangeEndpoint; + using ::TextUnit; +} + +namespace XamlAutomation +{ + using winrt::Windows::UI::Xaml::Automation::SupportedTextSelection; + using winrt::Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple; + using winrt::Windows::UI::Xaml::Automation::Provider::ITextRangeProvider; + using winrt::Windows::UI::Xaml::Automation::Text::TextPatternRangeEndpoint; + using winrt::Windows::UI::Xaml::Automation::Text::TextUnit; +} + +namespace winrt::Microsoft::Terminal::TerminalControl::implementation +{ + XamlAutomation::ITextRangeProvider XamlUiaTextRange::Clone() const + { + UIA::ITextRangeProvider* pReturn; + THROW_IF_FAILED(_uiaProvider->Clone(&pReturn)); + auto xutr = winrt::make_self(pReturn, _parentProvider); + return xutr.as(); + } + + bool XamlUiaTextRange::Compare(XamlAutomation::ITextRangeProvider pRange) const + { + auto self = winrt::get_self(pRange); + + BOOL returnVal; + THROW_IF_FAILED(_uiaProvider->Compare(self->_uiaProvider.get(), &returnVal)); + return returnVal; + } + + int32_t XamlUiaTextRange::CompareEndpoints(XamlAutomation::TextPatternRangeEndpoint endpoint, + XamlAutomation::ITextRangeProvider pTargetRange, + XamlAutomation::TextPatternRangeEndpoint targetEndpoint) + { + auto self = winrt::get_self(pTargetRange); + + int32_t returnVal; + THROW_IF_FAILED(_uiaProvider->CompareEndpoints(static_cast(endpoint), + self->_uiaProvider.get(), + static_cast(targetEndpoint), + &returnVal)); + return returnVal; + } + + void XamlUiaTextRange::ExpandToEnclosingUnit(XamlAutomation::TextUnit unit) const + { + THROW_IF_FAILED(_uiaProvider->ExpandToEnclosingUnit(static_cast(unit))); + } + + XamlAutomation::ITextRangeProvider XamlUiaTextRange::FindAttribute(int32_t textAttributeId, + winrt::Windows::Foundation::IInspectable val, + bool searchBackward) + { + // TODO GitHub #2161: potential accessibility improvement + // we don't support this currently + throw winrt::hresult_not_implemented(); + } + + XamlAutomation::ITextRangeProvider XamlUiaTextRange::FindText(winrt::hstring text, + bool searchBackward, + bool ignoreCase) + { + // TODO GitHub #605: Search functionality + // we need to wrap this around the UiaTextRange FindText() function + // but right now it returns E_NOTIMPL, so let's just return nullptr for now. + throw winrt::hresult_not_implemented(); + } + + winrt::Windows::Foundation::IInspectable XamlUiaTextRange::GetAttributeValue(int32_t textAttributeId) const + { + // Copied functionality from Types::UiaTextRange.cpp + if (textAttributeId == UIA_IsReadOnlyAttributeId) + { + return winrt::box_value(false); + } + else + { + return nullptr; + } + } + + void XamlUiaTextRange::GetBoundingRectangles(com_array& returnValue) const + { + returnValue = {}; + try + { + SAFEARRAY* pReturnVal; + THROW_IF_FAILED(_uiaProvider->GetBoundingRectangles(&pReturnVal)); + + double* pVals; + THROW_IF_FAILED(SafeArrayAccessData(pReturnVal, (void**)&pVals)); + + long lBound, uBound; + THROW_IF_FAILED(SafeArrayGetLBound(pReturnVal, 1, &lBound)); + THROW_IF_FAILED(SafeArrayGetUBound(pReturnVal, 1, &uBound)); + + long count = uBound - lBound + 1; + + std::vector vec; + vec.reserve(count); + for (int i = 0; i < count; i++) + { + double element = pVals[i]; + vec.push_back(element); + } + + winrt::com_array result{ vec }; + returnValue = std::move(result); + } + catch (...) + { + } + } + + XamlAutomation::IRawElementProviderSimple XamlUiaTextRange::GetEnclosingElement() + { + return _parentProvider; + } + + winrt::hstring XamlUiaTextRange::GetText(int32_t maxLength) const + { + BSTR returnVal; + THROW_IF_FAILED(_uiaProvider->GetText(maxLength, &returnVal)); + return winrt::to_hstring(returnVal); + } + + int32_t XamlUiaTextRange::Move(XamlAutomation::TextUnit unit, + int32_t count) + { + int returnVal; + THROW_IF_FAILED(_uiaProvider->Move(static_cast(unit), + count, + &returnVal)); + return returnVal; + } + + int32_t XamlUiaTextRange::MoveEndpointByUnit(XamlAutomation::TextPatternRangeEndpoint endpoint, + XamlAutomation::TextUnit unit, + int32_t count) const + { + int returnVal; + THROW_IF_FAILED(_uiaProvider->MoveEndpointByUnit(static_cast(endpoint), + static_cast(unit), + count, + &returnVal)); + return returnVal; + } + + void XamlUiaTextRange::MoveEndpointByRange(XamlAutomation::TextPatternRangeEndpoint endpoint, + XamlAutomation::ITextRangeProvider pTargetRange, + XamlAutomation::TextPatternRangeEndpoint targetEndpoint) const + { + auto self = winrt::get_self(pTargetRange); + THROW_IF_FAILED(_uiaProvider->MoveEndpointByRange(static_cast(endpoint), + /*pTargetRange*/ self->_uiaProvider.get(), + static_cast(targetEndpoint))); + } + + void XamlUiaTextRange::Select() const + { + THROW_IF_FAILED(_uiaProvider->Select()); + } + + void XamlUiaTextRange::AddToSelection() const + { + // we don't support this + throw winrt::hresult_not_implemented(); + } + + void XamlUiaTextRange::RemoveFromSelection() const + { + // we don't support this + throw winrt::hresult_not_implemented(); + } + + void XamlUiaTextRange::ScrollIntoView(bool alignToTop) const + { + THROW_IF_FAILED(_uiaProvider->ScrollIntoView(alignToTop)); + } + + winrt::com_array XamlUiaTextRange::GetChildren() const + { + // we don't have any children + return {}; + } +} diff --git a/src/cascadia/TerminalControl/XamlUiaTextRange.h b/src/cascadia/TerminalControl/XamlUiaTextRange.h new file mode 100644 index 00000000000..57b4ac605c5 --- /dev/null +++ b/src/cascadia/TerminalControl/XamlUiaTextRange.h @@ -0,0 +1,75 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- XamlUiaTextRange.h + +Abstract: +- This module is a wrapper for the UiaTextRange + (a text range accessibility provider). It allows + for UiaTextRange to be used in Windows Terminal. +- Wraps the UIAutomationCore ITextRangeProvider + (https://docs.microsoft.com/en-us/windows/win32/api/uiautomationcore/nn-uiautomationcore-itextrangeprovider) + with a XAML ITextRangeProvider + (https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.automation.provider.itextrangeprovider) + +Author(s): +- Carlos Zamora (CaZamor) 2019 +--*/ + +#pragma once + +#include "TermControlAutomationPeer.h" +#include +#include "UiaTextRange.hpp" + +namespace winrt::Microsoft::Terminal::TerminalControl::implementation +{ + class XamlUiaTextRange : + public winrt::implements + { + public: + XamlUiaTextRange(::ITextRangeProvider* uiaProvider, Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple parentProvider) : + _parentProvider{ parentProvider } + { + _uiaProvider.attach(uiaProvider); + } + +#pragma region ITextRangeProvider + Windows::UI::Xaml::Automation::Provider::ITextRangeProvider Clone() const; + bool Compare(Windows::UI::Xaml::Automation::Provider::ITextRangeProvider pRange) const; + int32_t CompareEndpoints(Windows::UI::Xaml::Automation::Text::TextPatternRangeEndpoint endpoint, + Windows::UI::Xaml::Automation::Provider::ITextRangeProvider pTargetRange, + Windows::UI::Xaml::Automation::Text::TextPatternRangeEndpoint targetEndpoint); + void ExpandToEnclosingUnit(Windows::UI::Xaml::Automation::Text::TextUnit unit) const; + Windows::UI::Xaml::Automation::Provider::ITextRangeProvider FindAttribute(int32_t textAttributeId, + winrt::Windows::Foundation::IInspectable val, + bool searchBackward); + Windows::UI::Xaml::Automation::Provider::ITextRangeProvider FindText(winrt::hstring text, + bool searchBackward, + bool ignoreCase); + winrt::Windows::Foundation::IInspectable GetAttributeValue(int32_t textAttributeId) const; + void GetBoundingRectangles(winrt::com_array& returnValue) const; + Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple GetEnclosingElement(); + winrt::hstring GetText(int32_t maxLength) const; + int32_t Move(Windows::UI::Xaml::Automation::Text::TextUnit unit, + int32_t count); + int32_t MoveEndpointByUnit(Windows::UI::Xaml::Automation::Text::TextPatternRangeEndpoint endpoint, + Windows::UI::Xaml::Automation::Text::TextUnit unit, + int32_t count) const; + void MoveEndpointByRange(Windows::UI::Xaml::Automation::Text::TextPatternRangeEndpoint endpoint, + Windows::UI::Xaml::Automation::Provider::ITextRangeProvider pTargetRange, + Windows::UI::Xaml::Automation::Text::TextPatternRangeEndpoint targetEndpoint) const; + void Select() const; + void AddToSelection() const; + void RemoveFromSelection() const; + void ScrollIntoView(bool alignToTop) const; + winrt::com_array GetChildren() const; +#pragma endregion ITextRangeProvider + + private: + wil::com_ptr<::ITextRangeProvider> _uiaProvider; + Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple _parentProvider; + }; +} diff --git a/src/cascadia/TerminalControl/init.cpp b/src/cascadia/TerminalControl/init.cpp new file mode 100644 index 00000000000..92b5b3c83b1 --- /dev/null +++ b/src/cascadia/TerminalControl/init.cpp @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation +// Licensed under the MIT license. + +#include "pch.h" + +// Note: Generate GUID using TlgGuid.exe tool +TRACELOGGING_DEFINE_PROVIDER( + g_hTerminalControlProvider, + "Microsoft.Windows.Terminal.Control", + // {28c82e50-57af-5a86-c25b-e39cd990032b} + (0x28c82e50, 0x57af, 0x5a86, 0xc2, 0x5b, 0xe3, 0x9c, 0xd9, 0x90, 0x03, 0x2b), + TraceLoggingOptionMicrosoftTelemetry()); + +BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/) +{ + switch (reason) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hInstDll); + TraceLoggingRegister(g_hTerminalControlProvider); + break; + case DLL_PROCESS_DETACH: + if (g_hTerminalControlProvider) + { + TraceLoggingUnregister(g_hTerminalControlProvider); + } + break; + } + + return TRUE; +} diff --git a/src/cascadia/TerminalControl/packages.config b/src/cascadia/TerminalControl/packages.config index 58e49c05c1a..e345a6ccd78 100644 --- a/src/cascadia/TerminalControl/packages.config +++ b/src/cascadia/TerminalControl/packages.config @@ -1,4 +1,4 @@ ο»Ώ - + diff --git a/src/cascadia/TerminalControl/pch.h b/src/cascadia/TerminalControl/pch.h index c807bc1f2a4..c8e879242d4 100644 --- a/src/cascadia/TerminalControl/pch.h +++ b/src/cascadia/TerminalControl/pch.h @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include #include @@ -30,3 +32,7 @@ #include #include + +#include +TRACELOGGING_DECLARE_PROVIDER(g_hTerminalControlProvider); +#include diff --git a/src/cascadia/TerminalCore/ITerminalApi.hpp b/src/cascadia/TerminalCore/ITerminalApi.hpp index 9e4e2a3767f..e85fb29c950 100644 --- a/src/cascadia/TerminalCore/ITerminalApi.hpp +++ b/src/cascadia/TerminalCore/ITerminalApi.hpp @@ -24,7 +24,11 @@ namespace Microsoft::Terminal::Core virtual bool SetCursorPosition(short x, short y) = 0; virtual COORD GetCursorPosition() = 0; + virtual bool DeleteCharacter(const unsigned int uiCount) = 0; + virtual bool InsertCharacter(const unsigned int uiCount) = 0; virtual bool EraseCharacters(const unsigned int numChars) = 0; + virtual bool EraseInLine(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType eraseType) = 0; + virtual bool EraseInDisplay(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType eraseType) = 0; virtual bool SetWindowTitle(std::wstring_view title) = 0; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index c7304b1af3a..f89544ca638 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -45,6 +45,8 @@ Terminal::Terminal() : _snapOnInput{ true }, _boxSelection{ false }, _selectionActive{ false }, + _allowSingleCharSelection{ false }, + _copyOnSelect{ false }, _selectionAnchor{ 0, 0 }, _endSelectionPosition{ 0, 0 } { @@ -135,6 +137,8 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting _wordDelimiters = settings.WordDelimiters(); + _copyOnSelect = settings.CopyOnSelect(); + // TODO:MSFT:21327402 - if HistorySize has changed, resize the buffer so we // have a smaller scrollback. We should do this carefully - if the new buffer // size is smaller than where the mutable viewport currently is, we'll want @@ -192,7 +196,7 @@ void Terminal::Write(std::wstring_view stringView) // - Send this particular key event to the terminal. The terminal will translate // the key and the modifiers pressed into the appropriate VT sequence for that // key chord. If we do translate the key, we'll return true. In that case, the -// event should NOT br processed any further. If we return false, the event +// event should NOT be processed any further. If we return false, the event // was NOT translated, and we should instead use the event to try and get the // real character out of the event. // Arguments: @@ -210,18 +214,6 @@ bool Terminal::SendKeyEvent(const WORD vkey, const ControlKeyStates states) _NotifyScrollEvent(); } - // AltGr key combinations don't always contain any meaningful, - // pretranslated unicode character during WM_KEYDOWN. - // E.g. on a German keyboard AltGr+Q should result in a "@" character, - // but actually results in "Q" with Alt and Ctrl modifier states. - // By returning false though, we can abort handling this WM_KEYDOWN - // event and let the WM_CHAR handler kick in, which will be - // provided with an appropriate unicode character. - if (states.IsAltGrPressed()) - { - return false; - } - // Alt key sequences _require_ the char to be in the keyevent. If alt is // pressed, manually get the character that's being typed, and put it in the // KeyEvent. diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 6e3064bd339..ba2f2bdc4cf 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -11,6 +11,7 @@ #include "../../terminal/input/terminalInput.hpp" #include "../../types/inc/Viewport.hpp" +#include "../../types/IUiaData.h" #include "../../cascadia/terminalcore/ITerminalApi.hpp" #include "../../cascadia/terminalcore/ITerminalInput.hpp" @@ -30,7 +31,8 @@ namespace Microsoft::Terminal::Core class Microsoft::Terminal::Core::Terminal final : public Microsoft::Terminal::Core::ITerminalApi, public Microsoft::Terminal::Core::ITerminalInput, - public Microsoft::Console::Render::IRenderData + public Microsoft::Console::Render::IRenderData, + public Microsoft::Console::Types::IUiaData { public: Terminal(); @@ -66,7 +68,11 @@ class Microsoft::Terminal::Core::Terminal final : bool ReverseText(bool reversed) override; bool SetCursorPosition(short x, short y) override; COORD GetCursorPosition() override; + bool DeleteCharacter(const unsigned int uiCount) override; + bool InsertCharacter(const unsigned int uiCount) override; bool EraseCharacters(const unsigned int numChars) override; + bool EraseInLine(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType eraseType) override; + bool EraseInDisplay(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType eraseType) override; bool SetWindowTitle(std::wstring_view title) override; bool SetColorTableEntry(const size_t tableIndex, const COLORREF dwColor) override; bool SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle) override; @@ -82,11 +88,17 @@ class Microsoft::Terminal::Core::Terminal final : int GetScrollOffset() override; #pragma endregion -#pragma region IRenderData - // These methods are defined in TerminalRenderData.cpp +#pragma region IBaseData(base to IRenderData and IUiaData) Microsoft::Console::Types::Viewport GetViewport() noexcept override; const TextBuffer& GetTextBuffer() noexcept override; const FontInfo& GetFontInfo() noexcept override; + + void LockConsole() noexcept override; + void UnlockConsole() noexcept override; +#pragma endregion + +#pragma region IRenderData + // These methods are defined in TerminalRenderData.cpp const TextAttribute GetDefaultBrushColors() noexcept override; const COLORREF GetForegroundColor(const TextAttribute& attr) const noexcept override; const COLORREF GetBackgroundColor(const TextAttribute& attr) const noexcept override; @@ -100,10 +112,14 @@ class Microsoft::Terminal::Core::Terminal final : bool IsCursorDoubleWidth() const noexcept override; const std::vector GetOverlays() const noexcept override; const bool IsGridLineDrawingAllowed() noexcept override; +#pragma endregion + +#pragma region IUiaData std::vector GetSelectionRects() noexcept override; + const bool IsSelectionActive() const noexcept; + void ClearSelection() override; + void SelectNewRegion(const COORD coordStart, const COORD coordEnd) override; const std::wstring GetConsoleTitle() const noexcept override; - void LockConsole() noexcept override; - void UnlockConsole() noexcept override; #pragma endregion void SetWriteInputCallback(std::function pfn) noexcept; @@ -116,15 +132,14 @@ class Microsoft::Terminal::Core::Terminal final : #pragma region TextSelection // These methods are defined in TerminalSelection.cpp - const bool IsSelectionActive() const noexcept; + const bool IsCopyOnSelectActive() const noexcept; void DoubleClickSelection(const COORD position); void TripleClickSelection(const COORD position); void SetSelectionAnchor(const COORD position); void SetEndSelectionPosition(const COORD position); void SetBoxSelection(const bool isEnabled) noexcept; - void ClearSelection() noexcept; - const std::wstring RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace) const; + const TextBuffer::TextAndColor RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace) const; #pragma endregion private: @@ -144,14 +159,24 @@ class Microsoft::Terminal::Core::Terminal final : bool _snapOnInput; - // Text Selection +#pragma region Text Selection + enum SelectionExpansionMode + { + Cell, + Word, + Line + }; COORD _selectionAnchor; COORD _endSelectionPosition; bool _boxSelection; bool _selectionActive; + bool _allowSingleCharSelection; + bool _copyOnSelect; SHORT _selectionAnchor_YOffset; SHORT _endSelectionPosition_YOffset; std::wstring _wordDelimiters; + SelectionExpansionMode _multiClickSelectionMode; +#pragma endregion std::shared_mutex _readWriteLock; @@ -194,9 +219,10 @@ class Microsoft::Terminal::Core::Terminal final : std::vector _GetSelectionRects() const; const SHORT _ExpandWideGlyphSelectionLeft(const SHORT xPos, const SHORT yPos) const; const SHORT _ExpandWideGlyphSelectionRight(const SHORT xPos, const SHORT yPos) const; - void _ExpandDoubleClickSelectionLeft(const COORD position); - void _ExpandDoubleClickSelectionRight(const COORD position); + COORD _ExpandDoubleClickSelectionLeft(const COORD position) const; + COORD _ExpandDoubleClickSelectionRight(const COORD position) const; const bool _isWordDelimiter(std::wstring_view cellChar) const; const COORD _ConvertToBufferCell(const COORD viewportPos) const; + const bool _isSingleCellSelection() const noexcept; #pragma endregion }; diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 27ca646b320..8aad5abc010 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "Terminal.hpp" +#include "../src/inc/unicode.hpp" using namespace Microsoft::Terminal::Core; using namespace Microsoft::Console::Types; @@ -126,17 +127,240 @@ COORD Terminal::GetCursorPosition() return newPos; } +// Method Description: +// - deletes uiCount characters starting from the cursor's current position +// - it moves over the remaining text to 'replace' the deleted text +// - for example, if the buffer looks like this ('|' is the cursor): [abc|def] +// - calling DeleteCharacter(1) will change it to: [abc|ef], +// - i.e. the 'd' gets deleted and the 'ef' gets shifted over 1 space and **retain their previous text attributes** +// Arguments: +// - uiCount, the number of characters to delete +// Return value: +// - true if succeeded, false otherwise +bool Terminal::DeleteCharacter(const unsigned int uiCount) +{ + SHORT dist; + if (!SUCCEEDED(UIntToShort(uiCount, &dist))) + { + return false; + } + const auto cursorPos = _buffer->GetCursor().GetPosition(); + const auto copyToPos = cursorPos; + const COORD copyFromPos{ cursorPos.X + dist, cursorPos.Y }; + auto sourceWidth = _mutableViewport.RightExclusive() - copyFromPos.X; + SHORT width; + if (!SUCCEEDED(UIntToShort(sourceWidth, &width))) + { + return false; + } + + // Get a rectangle of the source + auto source = Viewport::FromDimensions(copyFromPos, width, 1); + + // Get a rectangle of the target + const auto target = Viewport::FromDimensions(copyToPos, source.Dimensions()); + const auto walkDirection = Viewport::DetermineWalkDirection(source, target); + + auto sourcePos = source.GetWalkOrigin(walkDirection); + auto targetPos = target.GetWalkOrigin(walkDirection); + + // Iterate over the source cell data and copy it over to the target + do + { + const auto data = OutputCell(*(_buffer->GetCellDataAt(sourcePos))); + _buffer->Write(OutputCellIterator({ &data, 1 }), targetPos); + } while (source.WalkInBounds(sourcePos, walkDirection) && target.WalkInBounds(targetPos, walkDirection)); + + return true; +} + +// Method Description: +// - Inserts uiCount spaces starting from the cursor's current position, moving over the existing text +// - for example, if the buffer looks like this ('|' is the cursor): [abc|def] +// - calling InsertCharacter(1) will change it to: [abc| def], +// - i.e. the 'def' gets shifted over 1 space and **retain their previous text attributes** +// Arguments: +// - uiCount, the number of spaces to insert +// Return value: +// - true if succeeded, false otherwise +bool Terminal::InsertCharacter(const unsigned int uiCount) +{ + // NOTE: the code below is _extremely_ similar to DeleteCharacter + // We will want to use this same logic and implement a helper function instead + // that does the 'move a region from here to there' operation + // TODO: Github issue #2163 + SHORT dist; + if (!SUCCEEDED(UIntToShort(uiCount, &dist))) + { + return false; + } + const auto cursorPos = _buffer->GetCursor().GetPosition(); + const auto copyFromPos = cursorPos; + const COORD copyToPos{ cursorPos.X + dist, cursorPos.Y }; + auto sourceWidth = _mutableViewport.RightExclusive() - copyFromPos.X; + SHORT width; + if (!SUCCEEDED(UIntToShort(sourceWidth, &width))) + { + return false; + } + + // Get a rectangle of the source + auto source = Viewport::FromDimensions(copyFromPos, width, 1); + const auto sourceOrigin = source.Origin(); + + // Get a rectangle of the target + const auto target = Viewport::FromDimensions(copyToPos, source.Dimensions()); + const auto walkDirection = Viewport::DetermineWalkDirection(source, target); + + auto sourcePos = source.GetWalkOrigin(walkDirection); + auto targetPos = target.GetWalkOrigin(walkDirection); + + // Iterate over the source cell data and copy it over to the target + do + { + const auto data = OutputCell(*(_buffer->GetCellDataAt(sourcePos))); + _buffer->Write(OutputCellIterator({ &data, 1 }), targetPos); + } while (source.WalkInBounds(sourcePos, walkDirection) && target.WalkInBounds(targetPos, walkDirection)); + auto eraseIter = OutputCellIterator(UNICODE_SPACE, _buffer->GetCurrentAttributes(), dist); + _buffer->Write(eraseIter, cursorPos); + + return true; +} + bool Terminal::EraseCharacters(const unsigned int numChars) { const auto absoluteCursorPos = _buffer->GetCursor().GetPosition(); const auto viewport = _GetMutableViewport(); const short distanceToRight = viewport.RightExclusive() - absoluteCursorPos.X; const short fillLimit = std::min(static_cast(numChars), distanceToRight); - auto eraseIter = OutputCellIterator(L' ', _buffer->GetCurrentAttributes(), fillLimit); + auto eraseIter = OutputCellIterator(UNICODE_SPACE, _buffer->GetCurrentAttributes(), fillLimit); _buffer->Write(eraseIter, absoluteCursorPos); return true; } +// Method description: +// - erases a line of text, either from +// 1. beginning to the cursor's position +// 2. cursor's position to end +// 3. beginning to end +// - depending on the erase type +// Arguments: +// - the erase type +// Return value: +// - true if succeeded, false otherwise +bool Terminal::EraseInLine(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType eraseType) +{ + const auto cursorPos = _buffer->GetCursor().GetPosition(); + const auto viewport = _GetMutableViewport(); + COORD startPos = { 0 }; + startPos.Y = cursorPos.Y; + // nlength determines the number of spaces we need to write + DWORD nlength = 0; + + // Determine startPos.X and nlength by the eraseType + switch (eraseType) + { + case DispatchTypes::EraseType::FromBeginning: + nlength = cursorPos.X - viewport.Left() + 1; + break; + case DispatchTypes::EraseType::ToEnd: + startPos.X = cursorPos.X; + nlength = viewport.RightInclusive() - startPos.X; + break; + case DispatchTypes::EraseType::All: + startPos.X = viewport.Left(); + nlength = viewport.RightInclusive() - startPos.X; + break; + case DispatchTypes::EraseType::Scrollback: + return false; + } + + auto eraseIter = OutputCellIterator(UNICODE_SPACE, _buffer->GetCurrentAttributes(), nlength); + _buffer->Write(eraseIter, startPos); + return true; +} + +// Method description: +// - erases text in the buffer in two ways depending on erase type +// 1. 'erases' all text visible to the user (i.e. the text in the viewport) +// 2. erases all the text in the scrollback +// Arguments: +// - the erase type +// Return Value: +// - true if succeeded, false otherwise +bool Terminal::EraseInDisplay(const DispatchTypes::EraseType eraseType) +{ + // Store the relative cursor position so we can restore it later after we move the viewport + const auto cursorPos = _buffer->GetCursor().GetPosition(); + auto relativeCursor = cursorPos; + _mutableViewport.ConvertToOrigin(&relativeCursor); + + // Initialize the new location of the viewport + // the top and bottom parameters are determined by the eraseType + SMALL_RECT newWin; + newWin.Left = _mutableViewport.Left(); + newWin.Right = _mutableViewport.RightExclusive(); + + if (eraseType == DispatchTypes::EraseType::All) + { + // In this case, we simply move the viewport down, effectively pushing whatever text was on the screen into the scrollback + // and thus 'erasing' the text visible to the user + const auto coordLastChar = _buffer->GetLastNonSpaceCharacter(_mutableViewport); + if (coordLastChar.X == 0 && coordLastChar.Y == 0) + { + // Nothing to clear, just return + return true; + } + + short sNewTop = coordLastChar.Y + 1; + + // Increment the circular buffer only if the new location of the viewport would be 'below' the buffer + const short delta = (sNewTop + _mutableViewport.Height()) - (_buffer->GetSize().Height()); + for (auto i = 0; i < delta; i++) + { + _buffer->IncrementCircularBuffer(); + sNewTop--; + } + + newWin.Top = sNewTop; + newWin.Bottom = sNewTop + _mutableViewport.Height(); + } + else if (eraseType == DispatchTypes::EraseType::Scrollback) + { + // We only want to erase the scrollback, and leave everything else on the screen as it is + // so we grab the text in the viewport and rotate it up to the top of the buffer + COORD scrollFromPos{ 0, 0 }; + _mutableViewport.ConvertFromOrigin(&scrollFromPos); + _buffer->ScrollRows(scrollFromPos.Y, _mutableViewport.Height(), -scrollFromPos.Y); + + // Since we only did a rotation, the text that was in the scrollback is now _below_ where we are going to move the viewport + // and we have to make sure we erase that text + auto eraseStart = _mutableViewport.Height(); + auto eraseEnd = _buffer->GetLastNonSpaceCharacter(_mutableViewport).Y; + for (SHORT i = eraseStart; i <= eraseEnd; i++) + { + _buffer->GetRowByOffset(i).Reset(_buffer->GetCurrentAttributes()); + } + + // Reset the scroll offset now because there's nothing for the user to 'scroll' to + _scrollOffset = 0; + + newWin.Top = 0; + newWin.Bottom = _mutableViewport.Height(); + } + else + { + return false; + } + + // Move the viewport, adjust the scoll bar if needed, and restore the old cursor position + _mutableViewport = Viewport::FromExclusive(newWin); + Terminal::_NotifyScrollEvent(); + SetCursorPosition(relativeCursor.X, relativeCursor.Y); + + return true; +} + bool Terminal::SetWindowTitle(std::wstring_view title) { _title = title; diff --git a/src/cascadia/TerminalCore/TerminalDispatch.cpp b/src/cascadia/TerminalCore/TerminalDispatch.cpp index bd076961584..d5b061d2913 100644 --- a/src/cascadia/TerminalCore/TerminalDispatch.cpp +++ b/src/cascadia/TerminalCore/TerminalDispatch.cpp @@ -47,6 +47,20 @@ bool TerminalDispatch::CursorForward(const unsigned int uiDistance) return _terminalApi.SetCursorPosition(newCursorPos.X, newCursorPos.Y); } +bool TerminalDispatch::CursorBackward(const unsigned int uiDistance) +{ + const auto cursorPos = _terminalApi.GetCursorPosition(); + const COORD newCursorPos{ cursorPos.X - gsl::narrow(uiDistance), cursorPos.Y }; + return _terminalApi.SetCursorPosition(newCursorPos.X, newCursorPos.Y); +} + +bool TerminalDispatch::CursorUp(const unsigned int uiDistance) +{ + const auto cursorPos = _terminalApi.GetCursorPosition(); + const COORD newCursorPos{ cursorPos.X, cursorPos.Y + gsl::narrow(uiDistance) }; + return _terminalApi.SetCursorPosition(newCursorPos.X, newCursorPos.Y); +} + bool TerminalDispatch::EraseCharacters(const unsigned int uiNumChars) { return _terminalApi.EraseCharacters(uiNumChars); @@ -98,9 +112,45 @@ bool TerminalDispatch::SetDefaultBackground(const DWORD dwColor) } // Method Description: -// - For now, this is a hacky backspace -// - TODO: GitHub #1883 -bool TerminalDispatch::EraseInLine(const DispatchTypes::EraseType) +// - Erases characters in the buffer depending on the erase type +// Arguments: +// - eraseType: the erase type (from beginning, to end, or all) +// Return Value: +// True if handled successfully. False otherwise. +bool TerminalDispatch::EraseInLine(const DispatchTypes::EraseType eraseType) +{ + return _terminalApi.EraseInLine(eraseType); +} + +// Method Description: +// - Deletes uiCount number of characters starting from where the cursor is currently +// Arguments: +// - uiCount, the number of characters to delete +// Return Value: +// True if handled successfully. False otherwise. +bool TerminalDispatch::DeleteCharacter(const unsigned int uiCount) +{ + return _terminalApi.DeleteCharacter(uiCount); +} + +// Method Description: +// - Adds uiCount number of spaces starting from where the cursor is currently +// Arguments: +// - uiCount, the number of spaces to add +// Return Value: +// True if handled successfully, false otherwise +bool TerminalDispatch::InsertCharacter(const unsigned int uiCount) +{ + return _terminalApi.InsertCharacter(uiCount); +} + +// Method Description: +// - Moves the viewport and erases text from the buffer depending on the eraseType +// Arguments: +// - eraseType: the desired erase type +// Return Value: +// True if handled successfully. False otherwise +bool TerminalDispatch::EraseInDisplay(const DispatchTypes::EraseType eraseType) { - return _terminalApi.EraseCharacters(1); + return _terminalApi.EraseInDisplay(eraseType); } diff --git a/src/cascadia/TerminalCore/TerminalDispatch.hpp b/src/cascadia/TerminalCore/TerminalDispatch.hpp index e026744f0c7..5fa9758856e 100644 --- a/src/cascadia/TerminalCore/TerminalDispatch.hpp +++ b/src/cascadia/TerminalCore/TerminalDispatch.hpp @@ -20,6 +20,8 @@ class TerminalDispatch : public Microsoft::Console::VirtualTerminal::TermDispatc const unsigned int uiColumn) override; // CUP bool CursorForward(const unsigned int uiDistance) override; + bool CursorBackward(const unsigned int uiDistance) override; + bool CursorUp(const unsigned int uiDistance) override; bool EraseCharacters(const unsigned int uiNumChars) override; bool SetWindowTitle(std::wstring_view title) override; @@ -29,7 +31,10 @@ class TerminalDispatch : public Microsoft::Console::VirtualTerminal::TermDispatc bool SetDefaultForeground(const DWORD dwColor) override; bool SetDefaultBackground(const DWORD dwColor) override; - bool EraseInLine(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType /* eraseType*/) override; // ED + bool EraseInLine(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType eraseType) override; // ED + bool DeleteCharacter(const unsigned int uiCount) override; + bool InsertCharacter(const unsigned int uiCount) override; + bool EraseInDisplay(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType eraseType) override; private: ::Microsoft::Terminal::Core::ITerminalApi& _terminalApi; diff --git a/src/cascadia/TerminalCore/TerminalSelection.cpp b/src/cascadia/TerminalCore/TerminalSelection.cpp index 6b2f381f028..2131a762d24 100644 --- a/src/cascadia/TerminalCore/TerminalSelection.cpp +++ b/src/cascadia/TerminalCore/TerminalSelection.cpp @@ -14,7 +14,7 @@ std::vector Terminal::_GetSelectionRects() const { std::vector selectionArea; - if (!_selectionActive) + if (!IsSelectionActive()) { return selectionArea; } @@ -63,6 +63,27 @@ std::vector Terminal::_GetSelectionRects() const selectionRow.Right = (row == lowerCoord.Y) ? lowerCoord.X : bufferSize.RightInclusive(); } + // expand selection for Double/Triple Click + if (_multiClickSelectionMode == SelectionExpansionMode::Word) + { + const auto cellChar = _buffer->GetCellDataAt(selectionAnchorWithOffset)->Chars(); + if (_isSingleCellSelection() && _isWordDelimiter(cellChar)) + { + // only highlight the cell if you double click a delimiter + } + else + { + selectionRow.Left = _ExpandDoubleClickSelectionLeft({ selectionRow.Left, row }).X; + selectionRow.Right = _ExpandDoubleClickSelectionRight({ selectionRow.Right, row }).X; + } + } + else if (_multiClickSelectionMode == SelectionExpansionMode::Line) + { + selectionRow.Left = 0; + selectionRow.Right = bufferSize.RightInclusive(); + } + + // expand selection for Wide Glyphs selectionRow.Left = _ExpandWideGlyphSelectionLeft(selectionRow.Left, row); selectionRow.Right = _ExpandWideGlyphSelectionRight(selectionRow.Right, row); @@ -121,15 +142,39 @@ const SHORT Terminal::_ExpandWideGlyphSelectionRight(const SHORT xPos, const SHO return position.X; } +// Method Description: +// - Checks if selection is on a single cell +// Return Value: +// - bool representing if selection is only a single cell. Used for copyOnSelect +const bool Terminal::_isSingleCellSelection() const noexcept +{ + return (_selectionAnchor == _endSelectionPosition); +} + // Method Description: // - Checks if selection is active // Return Value: // - bool representing if selection is active. Used to decide copy/paste on right click const bool Terminal::IsSelectionActive() const noexcept { + // A single cell selection is not considered an active selection, + // if it's not allowed + if (!_allowSingleCharSelection && _isSingleCellSelection()) + { + return false; + } return _selectionActive; } +// Method Description: +// - Checks if the CopyOnSelect setting is active +// Return Value: +// - true if feature is active, false otherwise. +const bool Terminal::IsCopyOnSelectActive() const noexcept +{ + return _copyOnSelect; +} + // Method Description: // - Select the sequence between delimiters defined in Settings // Arguments: @@ -142,16 +187,24 @@ void Terminal::DoubleClickSelection(const COORD position) if (_isWordDelimiter(cellChar)) { SetSelectionAnchor(position); + _multiClickSelectionMode = SelectionExpansionMode::Word; return; } // scan leftwards until delimiter is found and // set selection anchor to one right of that spot - _ExpandDoubleClickSelectionLeft(position); + _selectionAnchor = _ExpandDoubleClickSelectionLeft(positionWithOffsets); + THROW_IF_FAILED(ShortSub(_selectionAnchor.Y, gsl::narrow(_ViewStartIndex()), &_selectionAnchor.Y)); + _selectionAnchor_YOffset = gsl::narrow(_ViewStartIndex()); // scan rightwards until delimiter is found and // set endSelectionPosition to one left of that spot - _ExpandDoubleClickSelectionRight(position); + _endSelectionPosition = _ExpandDoubleClickSelectionRight(positionWithOffsets); + THROW_IF_FAILED(ShortSub(_endSelectionPosition.Y, gsl::narrow(_ViewStartIndex()), &_endSelectionPosition.Y)); + _endSelectionPosition_YOffset = gsl::narrow(_ViewStartIndex()); + + _selectionActive = true; + _multiClickSelectionMode = SelectionExpansionMode::Word; } // Method Description: @@ -162,6 +215,8 @@ void Terminal::TripleClickSelection(const COORD position) { SetSelectionAnchor({ 0, position.Y }); SetEndSelectionPosition({ _buffer->GetSize().RightInclusive(), position.Y }); + + _multiClickSelectionMode = SelectionExpansionMode::Line; } // Method Description: @@ -180,7 +235,11 @@ void Terminal::SetSelectionAnchor(const COORD position) _selectionAnchor_YOffset = gsl::narrow(_ViewStartIndex()); _selectionActive = true; + _allowSingleCharSelection = (_copyOnSelect) ? false : true; + SetEndSelectionPosition(position); + + _multiClickSelectionMode = SelectionExpansionMode::Cell; } // Method Description: @@ -197,6 +256,11 @@ void Terminal::SetEndSelectionPosition(const COORD position) // copy value of ViewStartIndex to support scrolling // and update on new buffer output (used in _GetSelectionRects()) _endSelectionPosition_YOffset = gsl::narrow(_ViewStartIndex()); + + if (_copyOnSelect && !_isSingleCellSelection()) + { + _allowSingleCharSelection = true; + } } // Method Description: @@ -210,9 +274,10 @@ void Terminal::SetBoxSelection(const bool isEnabled) noexcept // Method Description: // - clear selection data and disable rendering it -void Terminal::ClearSelection() noexcept +void Terminal::ClearSelection() { _selectionActive = false; + _allowSingleCharSelection = false; _selectionAnchor = { 0, 0 }; _endSelectionPosition = { 0, 0 }; _selectionAnchor_YOffset = 0; @@ -228,24 +293,16 @@ void Terminal::ClearSelection() noexcept // and get text to appear on separate lines. // Return Value: // - wstring text from buffer. If extended to multiple lines, each line is separated by \r\n -const std::wstring Terminal::RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace) const +const TextBuffer::TextAndColor Terminal::RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace) const { std::function GetForegroundColor = std::bind(&Terminal::GetForegroundColor, this, std::placeholders::_1); std::function GetBackgroundColor = std::bind(&Terminal::GetBackgroundColor, this, std::placeholders::_1); - auto data = _buffer->GetTextForClipboard(!_boxSelection, - trimTrailingWhitespace, - _GetSelectionRects(), - GetForegroundColor, - GetBackgroundColor); - - std::wstring result; - for (const auto& text : data.text) - { - result += text; - } - - return result; + return _buffer->GetTextForClipboard(!_boxSelection, + trimTrailingWhitespace, + _GetSelectionRects(), + GetForegroundColor, + GetBackgroundColor); } // Method Description: @@ -253,16 +310,10 @@ const std::wstring Terminal::RetrieveSelectedTextFromBuffer(bool trimTrailingWhi // Arguments: // - position: viewport coordinate for selection // Return Value: -// - update _selectionAnchor to new expanded location -void Terminal::_ExpandDoubleClickSelectionLeft(const COORD position) +// - updated copy of "position" to new expanded location (with vertical offset) +COORD Terminal::_ExpandDoubleClickSelectionLeft(const COORD position) const { - // don't change the value if at/outside the boundary - if (position.X <= 0 || position.X >= _buffer->GetSize().RightInclusive()) - { - return; - } - - COORD positionWithOffsets = _ConvertToBufferCell(position); + COORD positionWithOffsets = position; const auto bufferViewport = _buffer->GetSize(); auto cellChar = _buffer->GetCellDataAt(positionWithOffsets)->Chars(); while (positionWithOffsets.X != 0 && !_isWordDelimiter(cellChar)) @@ -271,16 +322,13 @@ void Terminal::_ExpandDoubleClickSelectionLeft(const COORD position) cellChar = _buffer->GetCellDataAt(positionWithOffsets)->Chars(); } - if (positionWithOffsets.X != 0 || _isWordDelimiter(cellChar)) + if (positionWithOffsets.X != 0 && _isWordDelimiter(cellChar)) { // move off of delimiter to highlight properly bufferViewport.IncrementInBounds(positionWithOffsets); } - THROW_IF_FAILED(ShortSub(positionWithOffsets.Y, gsl::narrow(_ViewStartIndex()), &positionWithOffsets.Y)); - _selectionAnchor = positionWithOffsets; - _selectionAnchor_YOffset = gsl::narrow(_ViewStartIndex()); - _selectionActive = true; + return positionWithOffsets; } // Method Description: @@ -288,16 +336,10 @@ void Terminal::_ExpandDoubleClickSelectionLeft(const COORD position) // Arguments: // - position: viewport coordinate for selection // Return Value: -// - update _endSelectionPosition to new expanded location -void Terminal::_ExpandDoubleClickSelectionRight(const COORD position) +// - updated copy of "position" to new expanded location (with vertical offset) +COORD Terminal::_ExpandDoubleClickSelectionRight(const COORD position) const { - // don't change the value if at/outside the boundary - if (position.X <= 0 || position.X >= _buffer->GetSize().RightInclusive()) - { - return; - } - - COORD positionWithOffsets = _ConvertToBufferCell(position); + COORD positionWithOffsets = position; const auto bufferViewport = _buffer->GetSize(); auto cellChar = _buffer->GetCellDataAt(positionWithOffsets)->Chars(); while (positionWithOffsets.X != _buffer->GetSize().RightInclusive() && !_isWordDelimiter(cellChar)) @@ -306,15 +348,13 @@ void Terminal::_ExpandDoubleClickSelectionRight(const COORD position) cellChar = _buffer->GetCellDataAt(positionWithOffsets)->Chars(); } - if (positionWithOffsets.X != bufferViewport.RightInclusive() || _isWordDelimiter(cellChar)) + if (positionWithOffsets.X != bufferViewport.RightInclusive() && _isWordDelimiter(cellChar)) { // move off of delimiter to highlight properly bufferViewport.DecrementInBounds(positionWithOffsets); } - THROW_IF_FAILED(ShortSub(positionWithOffsets.Y, gsl::narrow(_ViewStartIndex()), &positionWithOffsets.Y)); - _endSelectionPosition = positionWithOffsets; - _endSelectionPosition_YOffset = gsl::narrow(_ViewStartIndex()); + return positionWithOffsets; } // Method Description: @@ -336,7 +376,11 @@ const bool Terminal::_isWordDelimiter(std::wstring_view cellChar) const // - the corresponding location on the buffer const COORD Terminal::_ConvertToBufferCell(const COORD viewportPos) const { + // Force position to be valid COORD positionWithOffsets = viewportPos; + positionWithOffsets.X = std::clamp(viewportPos.X, static_cast(0), _buffer->GetSize().RightInclusive()); + positionWithOffsets.Y = std::clamp(viewportPos.Y, static_cast(0), _buffer->GetSize().BottomInclusive()); + THROW_IF_FAILED(ShortSub(viewportPos.Y, gsl::narrow(_scrollOffset), &positionWithOffsets.Y)); THROW_IF_FAILED(ShortAdd(positionWithOffsets.Y, gsl::narrow(_ViewStartIndex()), &positionWithOffsets.Y)); return positionWithOffsets; diff --git a/src/cascadia/TerminalCore/packages.config b/src/cascadia/TerminalCore/packages.config index 58e49c05c1a..e345a6ccd78 100644 --- a/src/cascadia/TerminalCore/packages.config +++ b/src/cascadia/TerminalCore/packages.config @@ -1,4 +1,4 @@ ο»Ώ - + diff --git a/src/cascadia/TerminalCore/terminalrenderdata.cpp b/src/cascadia/TerminalCore/terminalrenderdata.cpp index b55c03ac948..d34c557b84f 100644 --- a/src/cascadia/TerminalCore/terminalrenderdata.cpp +++ b/src/cascadia/TerminalCore/terminalrenderdata.cpp @@ -117,6 +117,12 @@ std::vector Terminal::GetSelectionRects() n return result; } +void Terminal::SelectNewRegion(const COORD coordStart, const COORD coordEnd) +{ + SetSelectionAnchor(coordStart); + SetEndSelectionPosition(coordEnd); +} + const std::wstring Terminal::GetConsoleTitle() const noexcept { return _title; diff --git a/src/cascadia/TerminalSettings/IControlSettings.idl b/src/cascadia/TerminalSettings/IControlSettings.idl index e706f82b183..69c47072291 100644 --- a/src/cascadia/TerminalSettings/IControlSettings.idl +++ b/src/cascadia/TerminalSettings/IControlSettings.idl @@ -22,7 +22,7 @@ namespace Microsoft.Terminal.Settings Boolean UseAcrylic; Boolean CloseOnExit; Double TintOpacity; - ScrollbarState ScrollState; + ScrollbarState ScrollState; String FontFace; Int32 FontSize; @@ -32,6 +32,7 @@ namespace Microsoft.Terminal.Settings String Commandline; String StartingDirectory; + String StartingTitle; String EnvironmentVariables; String BackgroundImage; diff --git a/src/cascadia/TerminalSettings/ICoreSettings.idl b/src/cascadia/TerminalSettings/ICoreSettings.idl index 99e08d9c0aa..b715bd70868 100644 --- a/src/cascadia/TerminalSettings/ICoreSettings.idl +++ b/src/cascadia/TerminalSettings/ICoreSettings.idl @@ -28,6 +28,7 @@ namespace Microsoft.Terminal.Settings CursorStyle CursorShape; UInt32 CursorHeight; String WordDelimiters; + Boolean CopyOnSelect; }; } diff --git a/src/cascadia/TerminalSettings/TerminalSettings.cpp b/src/cascadia/TerminalSettings/TerminalSettings.cpp index 3ac52e87320..70c2d4f7a5f 100644 --- a/src/cascadia/TerminalSettings/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettings/TerminalSettings.cpp @@ -21,6 +21,7 @@ namespace winrt::Microsoft::Terminal::Settings::implementation _cursorShape{ CursorStyle::Vintage }, _cursorHeight{ DEFAULT_CURSOR_HEIGHT }, _wordDelimiters{ DEFAULT_WORD_DELIMITERS }, + _copyOnSelect{ false }, _useAcrylic{ false }, _closeOnExit{ true }, _tintOpacity{ 0.5 }, @@ -64,7 +65,8 @@ namespace winrt::Microsoft::Terminal::Settings::implementation void TerminalSettings::SetColorTableEntry(int32_t index, uint32_t value) { - THROW_HR_IF(E_INVALIDARG, index > _colorTable.size()); + auto const colorTableCount = gsl::narrow_cast(_colorTable.size()); + THROW_HR_IF(E_INVALIDARG, index >= colorTableCount); _colorTable[index] = value; } @@ -148,6 +150,16 @@ namespace winrt::Microsoft::Terminal::Settings::implementation _wordDelimiters = value; } + bool TerminalSettings::CopyOnSelect() + { + return _copyOnSelect; + } + + void TerminalSettings::CopyOnSelect(bool value) + { + _copyOnSelect = value; + } + bool TerminalSettings::UseAcrylic() { return _useAcrylic; @@ -288,6 +300,16 @@ namespace winrt::Microsoft::Terminal::Settings::implementation _startingDir = value; } + hstring TerminalSettings::StartingTitle() + { + return _startingTitle; + } + + void TerminalSettings::StartingTitle(hstring const& value) + { + _startingTitle = value; + } + hstring TerminalSettings::EnvironmentVariables() { return _envVars; diff --git a/src/cascadia/TerminalSettings/packages.config b/src/cascadia/TerminalSettings/packages.config index 58e49c05c1a..e345a6ccd78 100644 --- a/src/cascadia/TerminalSettings/packages.config +++ b/src/cascadia/TerminalSettings/packages.config @@ -1,4 +1,4 @@ ο»Ώ - + diff --git a/src/cascadia/TerminalSettings/terminalsettings.h b/src/cascadia/TerminalSettings/terminalsettings.h index 39894c6674f..c701631da8a 100644 --- a/src/cascadia/TerminalSettings/terminalsettings.h +++ b/src/cascadia/TerminalSettings/terminalsettings.h @@ -47,6 +47,8 @@ namespace winrt::Microsoft::Terminal::Settings::implementation void CursorHeight(uint32_t value); hstring WordDelimiters(); void WordDelimiters(hstring const& value); + bool CopyOnSelect(); + void CopyOnSelect(bool value); // ------------------------ End of Core Settings ----------------------- bool UseAcrylic(); @@ -83,6 +85,9 @@ namespace winrt::Microsoft::Terminal::Settings::implementation hstring StartingDirectory(); void StartingDirectory(hstring const& value); + hstring StartingTitle(); + void StartingTitle(hstring const& value); + hstring EnvironmentVariables(); void EnvironmentVariables(hstring const& value); @@ -113,8 +118,10 @@ namespace winrt::Microsoft::Terminal::Settings::implementation winrt::Windows::UI::Xaml::Media::Stretch _backgroundImageStretchMode; winrt::Windows::UI::Xaml::HorizontalAlignment _backgroundImageHorizontalAlignment; winrt::Windows::UI::Xaml::VerticalAlignment _backgroundImageVerticalAlignment; + bool _copyOnSelect; hstring _commandline; hstring _startingDir; + hstring _startingTitle; hstring _envVars; Settings::IKeyBindings _keyBindings; Settings::ScrollbarState _scrollbarState; diff --git a/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h b/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h new file mode 100644 index 00000000000..eb5e4011a7c --- /dev/null +++ b/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h @@ -0,0 +1,62 @@ +#pragma once + +#include "precomp.h" +#include + +#include "DefaultSettings.h" + +#include "winrt/Microsoft.Terminal.Settings.h" + +using namespace winrt::Microsoft::Terminal::Settings; + +namespace TerminalCoreUnitTests +{ + class MockTermSettings : public winrt::implements + { + public: + MockTermSettings(int32_t historySize, int32_t initialRows, int32_t initialCols) : + _historySize(historySize), + _initialRows(initialRows), + _initialCols(initialCols) + { + } + + // property getters - all implemented + int32_t HistorySize() { return _historySize; } + int32_t InitialRows() { return _initialRows; } + int32_t InitialCols() { return _initialCols; } + uint32_t DefaultForeground() { return COLOR_WHITE; } + uint32_t DefaultBackground() { return COLOR_BLACK; } + bool SnapOnInput() { return false; } + uint32_t CursorColor() { return COLOR_WHITE; } + CursorStyle CursorShape() const noexcept { return CursorStyle::Vintage; } + uint32_t CursorHeight() { return 42UL; } + winrt::hstring WordDelimiters() { return winrt::to_hstring(DEFAULT_WORD_DELIMITERS.c_str()); } + bool CopyOnSelect() { return _copyOnSelect; } + + // other implemented methods + uint32_t GetColorTableEntry(int32_t) const { return 123; } + + // property setters - all unimplemented + void HistorySize(int32_t) {} + void InitialRows(int32_t) {} + void InitialCols(int32_t) {} + void DefaultForeground(uint32_t) {} + void DefaultBackground(uint32_t) {} + void SnapOnInput(bool) {} + void CursorColor(uint32_t) {} + void CursorShape(CursorStyle const&) noexcept {} + void CursorHeight(uint32_t) {} + void WordDelimiters(winrt::hstring) {} + void CopyOnSelect(bool copyOnSelect) { _copyOnSelect = copyOnSelect; } + + // other unimplemented methods + void SetColorTableEntry(int32_t /* index */, uint32_t /* value */) {} + + private: + int32_t _historySize; + int32_t _initialRows; + int32_t _initialCols; + bool _copyOnSelect{ false }; + }; +} diff --git a/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp index bdaa57012b8..9c3e8e602fa 100644 --- a/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp @@ -4,64 +4,16 @@ #include "precomp.h" #include -#include "DefaultSettings.h" #include "../cascadia/TerminalCore/Terminal.hpp" +#include "MockTermSettings.h" #include "../renderer/inc/DummyRenderTarget.hpp" #include "consoletaeftemplates.hpp" -#include "winrt/Microsoft.Terminal.Settings.h" - using namespace winrt::Microsoft::Terminal::Settings; using namespace Microsoft::Terminal::Core; namespace TerminalCoreUnitTests { - class MockTermSettings : public winrt::implements - { - public: - MockTermSettings(int32_t historySize, int32_t initialRows, int32_t initialCols) : - _historySize(historySize), - _initialRows(initialRows), - _initialCols(initialCols) - { - } - - // property getters - all implemented - int32_t HistorySize() { return _historySize; } - int32_t InitialRows() { return _initialRows; } - int32_t InitialCols() { return _initialCols; } - uint32_t DefaultForeground() { return COLOR_WHITE; } - uint32_t DefaultBackground() { return COLOR_BLACK; } - bool SnapOnInput() { return false; } - uint32_t CursorColor() { return COLOR_WHITE; } - CursorStyle CursorShape() const noexcept { return CursorStyle::Vintage; } - uint32_t CursorHeight() { return 42UL; } - winrt::hstring WordDelimiters() { return winrt::to_hstring(DEFAULT_WORD_DELIMITERS.c_str()); } - - // other implemented methods - uint32_t GetColorTableEntry(int32_t) const { return 123; } - - // property setters - all unimplemented - void HistorySize(int32_t) {} - void InitialRows(int32_t) {} - void InitialCols(int32_t) {} - void DefaultForeground(uint32_t) {} - void DefaultBackground(uint32_t) {} - void SnapOnInput(bool) {} - void CursorColor(uint32_t) {} - void CursorShape(CursorStyle const&) noexcept {} - void CursorHeight(uint32_t) {} - void WordDelimiters(winrt::hstring) {} - - // other unimplemented methods - void SetColorTableEntry(int32_t /* index */, uint32_t /* value */) {} - - private: - int32_t _historySize; - int32_t _initialRows; - int32_t _initialCols; - }; - #define WCS(x) WCSHELPER(x) #define WCSHELPER(x) L#x diff --git a/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp b/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp index ea6cb51b6f0..530f7b29d04 100644 --- a/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp @@ -9,6 +9,7 @@ #include #include "../cascadia/TerminalCore/Terminal.hpp" +#include "../cascadia/UnitTests_TerminalCore/MockTermSettings.h" #include "../renderer/inc/DummyRenderTarget.hpp" #include "consoletaeftemplates.hpp" @@ -16,7 +17,7 @@ using namespace WEX::Logging; using namespace WEX::TestExecution; using namespace Microsoft::Terminal::Core; -using namespace Microsoft::Console::Render; +using namespace winrt::Microsoft::Terminal::Settings; namespace TerminalCoreUnitTests { @@ -181,6 +182,11 @@ namespace TerminalCoreUnitTests TEST_METHOD(SelectWideGlyph_Trailing) { +#ifdef _X86_ + Log::Comment(L"This test is unreliable on x86 but is fine elsewhere. Disabled on x86."); + Log::Result(TestResults::Skipped); + return; +#else Terminal term; DummyRenderTarget emptyRT; term.Create({ 100, 100 }, 0, emptyRT); @@ -206,10 +212,16 @@ namespace TerminalCoreUnitTests auto selection = term.GetViewport().ConvertToOrigin(selectionRects.at(0)).ToInclusive(); VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 4, 10, 5, 10 })); +#endif } TEST_METHOD(SelectWideGlyph_Leading) { +#ifdef _X86_ + Log::Comment(L"This test is unreliable on x86 but is fine elsewhere. Disabled on x86."); + Log::Result(TestResults::Skipped); + return; +#else Terminal term; DummyRenderTarget emptyRT; term.Create({ 100, 100 }, 0, emptyRT); @@ -235,10 +247,16 @@ namespace TerminalCoreUnitTests auto selection = term.GetViewport().ConvertToOrigin(selectionRects.at(0)).ToInclusive(); VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 4, 10, 5, 10 })); +#endif } TEST_METHOD(SelectWideGlyphsInBoxSelection) { +#ifdef _X86_ + Log::Comment(L"This test is unreliable on x86 but is fine elsewhere. Disabled on x86."); + Log::Result(TestResults::Skipped); + return; +#else Terminal term; DummyRenderTarget emptyRT; term.Create({ 100, 100 }, 0, emptyRT); @@ -290,6 +308,259 @@ namespace TerminalCoreUnitTests rowValue++; } +#endif + } + + TEST_METHOD(DoubleClick_GeneralCase) + { + Terminal term; + DummyRenderTarget emptyRT; + term.Create({ 100, 100 }, 0, emptyRT); + + // set word delimiters for terminal + auto settings = winrt::make(0, 100, 100); + term.UpdateSettings(settings); + + // Insert text at position (4,10) + const std::wstring_view text = L"doubleClickMe"; + term.SetCursorPosition(4, 10); + term.Write(text); + + // Simulate double click at (x,y) = (5,10) + auto clickPos = COORD{ 5, 10 }; + term.DoubleClickSelection(clickPos); + + // Simulate renderer calling TriggerSelection and acquiring selection area + auto selectionRects = term.GetSelectionRects(); + + // Validate selection area + VERIFY_ARE_EQUAL(selectionRects.size(), static_cast(1)); + + auto selection = term.GetViewport().ConvertToOrigin(selectionRects.at(0)).ToInclusive(); + VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 4, 10, (4 + gsl::narrow(text.size()) - 1), 10 })); + } + + TEST_METHOD(DoubleClick_Delimiter) + { + Terminal term; + DummyRenderTarget emptyRT; + term.Create({ 100, 100 }, 0, emptyRT); + + // set word delimiters for terminal + auto settings = winrt::make(0, 100, 100); + term.UpdateSettings(settings); + + // Simulate click at (x,y) = (5,10) + auto clickPos = COORD{ 5, 10 }; + term.DoubleClickSelection(clickPos); + + // Simulate renderer calling TriggerSelection and acquiring selection area + auto selectionRects = term.GetSelectionRects(); + + // Validate selection area + VERIFY_ARE_EQUAL(selectionRects.size(), static_cast(1)); + + auto selection = term.GetViewport().ConvertToOrigin(selectionRects.at(0)).ToInclusive(); + VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 5, 10, 5, 10 })); + } + + TEST_METHOD(DoubleClickDrag_Right) + { + Terminal term; + DummyRenderTarget emptyRT; + term.Create({ 100, 100 }, 0, emptyRT); + + // set word delimiters for terminal + auto settings = winrt::make(0, 100, 100); + term.UpdateSettings(settings); + + // Insert text at position (4,10) + const std::wstring_view text = L"doubleClickMe dragThroughHere"; + term.SetCursorPosition(4, 10); + term.Write(text); + + // Simulate double click at (x,y) = (5,10) + term.DoubleClickSelection({ 5, 10 }); + + // Simulate move to (x,y) = (21,10) + // + // buffer: doubleClickMe dragThroughHere + // ^ ^ + // start finish + term.SetEndSelectionPosition({ 21, 10 }); + + // Simulate renderer calling TriggerSelection and acquiring selection area + auto selectionRects = term.GetSelectionRects(); + + // Validate selection area + VERIFY_ARE_EQUAL(selectionRects.size(), static_cast(1)); + + auto selection = term.GetViewport().ConvertToOrigin(selectionRects.at(0)).ToInclusive(); + VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 4, 10, 32, 10 })); + } + + TEST_METHOD(DoubleClickDrag_Left) + { + Terminal term; + DummyRenderTarget emptyRT; + term.Create({ 100, 100 }, 0, emptyRT); + + // set word delimiters for terminal + auto settings = winrt::make(0, 100, 100); + term.UpdateSettings(settings); + + // Insert text at position (21,10) + const std::wstring_view text = L"doubleClickMe dragThroughHere"; + term.SetCursorPosition(4, 10); + term.Write(text); + + // Simulate double click at (x,y) = (21,10) + term.DoubleClickSelection({ 21, 10 }); + + // Simulate move to (x,y) = (5,10) + // + // buffer: doubleClickMe dragThroughHere + // ^ ^ + // finish start + term.SetEndSelectionPosition({ 5, 10 }); + + // Simulate renderer calling TriggerSelection and acquiring selection area + auto selectionRects = term.GetSelectionRects(); + + // Validate selection area + VERIFY_ARE_EQUAL(selectionRects.size(), static_cast(1)); + + auto selection = term.GetViewport().ConvertToOrigin(selectionRects.at(0)).ToInclusive(); + VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 4, 10, 32, 10 })); + } + + TEST_METHOD(TripleClick_GeneralCase) + { + Terminal term; + DummyRenderTarget emptyRT; + term.Create({ 100, 100 }, 0, emptyRT); + + // Simulate click at (x,y) = (5,10) + auto clickPos = COORD{ 5, 10 }; + term.TripleClickSelection(clickPos); + + // Simulate renderer calling TriggerSelection and acquiring selection area + auto selectionRects = term.GetSelectionRects(); + + // Validate selection area + VERIFY_ARE_EQUAL(selectionRects.size(), static_cast(1)); + + auto selection = term.GetViewport().ConvertToOrigin(selectionRects.at(0)).ToInclusive(); + VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 0, 10, 99, 10 })); + } + + TEST_METHOD(TripleClickDrag_Horizontal) + { + Terminal term; + DummyRenderTarget emptyRT; + term.Create({ 100, 100 }, 0, emptyRT); + + // Simulate click at (x,y) = (5,10) + auto clickPos = COORD{ 5, 10 }; + term.TripleClickSelection(clickPos); + + // Simulate move to (x,y) = (7,10) + term.SetEndSelectionPosition({ 7, 10 }); + + // Simulate renderer calling TriggerSelection and acquiring selection area + auto selectionRects = term.GetSelectionRects(); + + // Validate selection area + VERIFY_ARE_EQUAL(selectionRects.size(), static_cast(1)); + + auto selection = term.GetViewport().ConvertToOrigin(selectionRects.at(0)).ToInclusive(); + VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 0, 10, 99, 10 })); + } + + TEST_METHOD(TripleClickDrag_Vertical) + { + Terminal term; + DummyRenderTarget emptyRT; + term.Create({ 100, 100 }, 0, emptyRT); + + // Simulate click at (x,y) = (5,10) + auto clickPos = COORD{ 5, 10 }; + term.TripleClickSelection(clickPos); + + // Simulate move to (x,y) = (5,11) + term.SetEndSelectionPosition({ 5, 11 }); + + // Simulate renderer calling TriggerSelection and acquiring selection area + auto selectionRects = term.GetSelectionRects(); + + // Validate selection area + VERIFY_ARE_EQUAL(selectionRects.size(), static_cast(2)); + + // verify first selection rect + auto selection = term.GetViewport().ConvertToOrigin(selectionRects.at(0)).ToInclusive(); + VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 0, 10, 99, 10 })); + + // verify second selection rect + selection = term.GetViewport().ConvertToOrigin(selectionRects.at(1)).ToInclusive(); + VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 0, 11, 99, 11 })); + } + + TEST_METHOD(CopyOnSelect) + { + Terminal term; + DummyRenderTarget emptyRT; + term.Create({ 100, 100 }, 0, emptyRT); + + // set copyOnSelect for terminal + auto settings = winrt::make(0, 100, 100); + settings.CopyOnSelect(true); + term.UpdateSettings(settings); + + // Simulate click at (x,y) = (5,10) + term.SetSelectionAnchor({ 5, 10 }); + + // Simulate move to (x,y) = (5,10) + // (So, no movement) + term.SetEndSelectionPosition({ 5, 10 }); + + // Case 1: single cell selection not allowed + { + // Simulate renderer calling TriggerSelection and acquiring selection area + auto selectionRects = term.GetSelectionRects(); + + // Validate selection area + VERIFY_ARE_EQUAL(selectionRects.size(), static_cast(0)); + + // single cell selection should not be allowed + // thus, selection is NOT active + VERIFY_IS_FALSE(term.IsSelectionActive()); + } + + // Case 2: move off of single cell + term.SetEndSelectionPosition({ 6, 10 }); + { // Simulate renderer calling TriggerSelection and acquiring selection area + auto selectionRects = term.GetSelectionRects(); + + // Validate selection area + VERIFY_ARE_EQUAL(selectionRects.size(), static_cast(1)); + auto selection = term.GetViewport().ConvertToOrigin(selectionRects.at(0)).ToInclusive(); + VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 5, 10, 6, 10 })); + VERIFY_IS_TRUE(term.IsSelectionActive()); + } + + // Case 3: move back onto single cell (now allowed) + term.SetEndSelectionPosition({ 5, 10 }); + { // Simulate renderer calling TriggerSelection and acquiring selection area + auto selectionRects = term.GetSelectionRects(); + + // Validate selection area + VERIFY_ARE_EQUAL(selectionRects.size(), static_cast(1)); + auto selection = term.GetViewport().ConvertToOrigin(selectionRects.at(0)).ToInclusive(); + VERIFY_ARE_EQUAL(selection, SMALL_RECT({ 5, 10, 5, 10 })); + + // single cell selection should now be allowed + VERIFY_IS_TRUE(term.IsSelectionActive()); + } } }; } diff --git a/src/cascadia/UnitTests_TerminalCore/UnitTests.vcxproj b/src/cascadia/UnitTests_TerminalCore/UnitTests.vcxproj index 1e71d072ccf..af44b123721 100644 --- a/src/cascadia/UnitTests_TerminalCore/UnitTests.vcxproj +++ b/src/cascadia/UnitTests_TerminalCore/UnitTests.vcxproj @@ -28,6 +28,7 @@ + diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 5893c1ad9ea..78ef79f34e5 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -69,6 +69,11 @@ void AppHost::Initialize() // content in Create. _app.SetTitleBarContent({ this, &AppHost::_UpdateTitleBarContent }); } + + // Add an event handler to plumb clicks in the titlebar area down to the + // application layer. + _window->DragRegionClicked([this]() { _app.TitlebarClicked(); }); + _app.RequestedThemeChanged({ this, &AppHost::_UpdateTheme }); _app.Create(); @@ -76,7 +81,7 @@ void AppHost::Initialize() _app.TitleChanged({ this, &AppHost::AppTitleChanged }); _app.LastTabClosed({ this, &AppHost::LastTabClosed }); - AppTitleChanged(_app.GetTitle()); + _window->UpdateTitle(_app.Title()); // Set up the content of the application. If the app has a custom titlebar, // set that content as well. @@ -88,10 +93,11 @@ void AppHost::Initialize() // - Called when the app's title changes. Fires off a window message so we can // update the window's title on the main thread. // Arguments: +// - sender: unused // - newTitle: the string to use as the new window title // Return Value: // - -void AppHost::AppTitleChanged(winrt::hstring newTitle) +void AppHost::AppTitleChanged(const winrt::Windows::Foundation::IInspectable& /*sender*/, winrt::hstring newTitle) { _window->UpdateTitle(newTitle.c_str()); } @@ -99,10 +105,11 @@ void AppHost::AppTitleChanged(winrt::hstring newTitle) // Method Description: // - Called when no tab is remaining to close the window. // Arguments: -// - +// - sender: unused +// - LastTabClosedEventArgs: unused // Return Value: // - -void AppHost::LastTabClosed() +void AppHost::LastTabClosed(const winrt::Windows::Foundation::IInspectable& /*sender*/, const winrt::TerminalApp::LastTabClosedEventArgs& /*args*/) { _window->Close(); } @@ -194,6 +201,13 @@ void AppHost::_HandleCreateWindow(const HWND hwnd, const RECT proposedRect) // If we can't resize the window, that's really okay. We can just go on with // the originally proposed window size. LOG_LAST_ERROR_IF(!succeeded); + + TraceLoggingWrite( + g_hWindowsTerminalProvider, + "WindowCreated", + TraceLoggingDescription("Event emitted upon creating the application window"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); } // Method Description: @@ -204,7 +218,7 @@ void AppHost::_HandleCreateWindow(const HWND hwnd, const RECT proposedRect) // - arg: the UIElement to use as the new Titlebar content. // Return Value: // - -void AppHost::_UpdateTitleBarContent(const winrt::TerminalApp::App&, const winrt::Windows::UI::Xaml::UIElement& arg) +void AppHost::_UpdateTitleBarContent(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::UI::Xaml::UIElement& arg) { if (_useNonClientArea) { diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index 6c926ae3b6b..a66cd93369c 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -14,8 +14,8 @@ class AppHost AppHost() noexcept; virtual ~AppHost(); - void AppTitleChanged(winrt::hstring newTitle); - void LastTabClosed(); + void AppTitleChanged(const winrt::Windows::Foundation::IInspectable& sender, winrt::hstring newTitle); + void LastTabClosed(const winrt::Windows::Foundation::IInspectable& sender, const winrt::TerminalApp::LastTabClosedEventArgs& args); void Initialize(); private: @@ -25,7 +25,7 @@ class AppHost winrt::TerminalApp::App _app; void _HandleCreateWindow(const HWND hwnd, const RECT proposedRect); - void _UpdateTitleBarContent(const winrt::TerminalApp::App& sender, + void _UpdateTitleBarContent(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::UIElement& arg); void _UpdateTheme(const winrt::TerminalApp::App&, const winrt::Windows::UI::Xaml::ElementTheme& arg); diff --git a/src/cascadia/WindowsTerminal/BaseWindow.h b/src/cascadia/WindowsTerminal/BaseWindow.h index 227c738db10..1ae388dcd37 100644 --- a/src/cascadia/WindowsTerminal/BaseWindow.h +++ b/src/cascadia/WindowsTerminal/BaseWindow.h @@ -3,11 +3,16 @@ #pragma once +#include "..\types\IConsoleWindow.hpp" +#include "..\types\WindowUiaProviderBase.hpp" + // Custom window messages #define CM_UPDATE_TITLE (WM_USER) #include +using namespace Microsoft::Console::Types; + template class BaseWindow { @@ -51,6 +56,11 @@ class BaseWindow return HandleDpiChange(_window.get(), wparam, lparam); } + case WM_GETOBJECT: + { + return HandleGetObject(_window.get(), wparam, lparam); + } + case WM_DESTROY: { PostQuitMessage(0); @@ -121,6 +131,22 @@ class BaseWindow return 0; } + [[nodiscard]] LRESULT HandleGetObject(const HWND hWnd, const WPARAM wParam, const LPARAM lParam) + { + LRESULT retVal = 0; + + // If we are receiving a request from Microsoft UI Automation framework, then return the basic UIA COM interface. + if (static_cast(lParam) == static_cast(UiaRootObjectId)) + { + retVal = UiaReturnRawElementProvider(hWnd, wParam, lParam, _GetUiaProvider()); + } + // Otherwise, return 0. We don't implement MS Active Accessibility (the other framework that calls WM_GETOBJECT). + + return retVal; + } + + virtual IRawElementProviderSimple* _GetUiaProvider() = 0; + virtual void OnResize(const UINT width, const UINT height) = 0; virtual void OnMinimize() = 0; virtual void OnRestore() = 0; @@ -135,7 +161,7 @@ class BaseWindow HWND GetHandle() const noexcept { return _window.get(); - }; + } float GetCurrentDpiScale() const noexcept { @@ -187,7 +213,7 @@ class BaseWindow { _title = newTitle; PostMessageW(_window.get(), CM_UPDATE_TITLE, 0, reinterpret_cast(nullptr)); - }; + } protected: using base_type = BaseWindow; diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index d63715442fa..881054bb645 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -166,6 +166,21 @@ void IslandWindow::OnSize(const UINT width, const UINT height) return 0; // eat the message } } + + case WM_NCLBUTTONDOWN: + case WM_NCLBUTTONUP: + case WM_NCMBUTTONDOWN: + case WM_NCMBUTTONUP: + case WM_NCRBUTTONDOWN: + case WM_NCRBUTTONUP: + case WM_NCXBUTTONDOWN: + case WM_NCXBUTTONUP: + { + // If we clicked in the titlebar, raise an event so the app host can + // dispatch an appropriate event. + _DragRegionClickedHandlers(); + break; + } case WM_MENUCHAR: { // GH#891: return this LRESULT here to prevent the app from making a @@ -179,6 +194,30 @@ void IslandWindow::OnSize(const UINT width, const UINT height) return base_type::MessageHandler(message, wparam, lparam); } +// Routine Description: +// - Creates/retrieves a handle to the UI Automation provider COM interfaces +// Arguments: +// - +// Return Value: +// - Pointer to UI Automation provider class/interfaces. +IRawElementProviderSimple* IslandWindow::_GetUiaProvider() +{ + if (nullptr == _pUiaProvider) + { + try + { + _pUiaProvider = WindowUiaProvider::Create(this); + } + catch (...) + { + LOG_HR(wil::ResultFromCaughtException()); + _pUiaProvider = nullptr; + } + } + + return _pUiaProvider; +} + // Method Description: // - Called when the window has been resized (or maximized) // Arguments: @@ -234,3 +273,5 @@ void IslandWindow::UpdateTheme(const winrt::Windows::UI::Xaml::ElementTheme& req // drawing ourselves to match the new theme ::InvalidateRect(_window.get(), nullptr, false); } + +DEFINE_EVENT(IslandWindow, DragRegionClicked, _DragRegionClickedHandlers, winrt::delegate<>); diff --git a/src/cascadia/WindowsTerminal/IslandWindow.h b/src/cascadia/WindowsTerminal/IslandWindow.h index 436a3cf9f1f..98eb76db35b 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -3,10 +3,15 @@ #include "pch.h" #include "BaseWindow.h" +#include "../types/IUiaWindow.h" +#include "WindowUiaProvider.hpp" #include #include +#include "../../cascadia/inc/cppwinrt_utils.h" -class IslandWindow : public BaseWindow +class IslandWindow : + public BaseWindow, + public IUiaWindow { public: IslandWindow() noexcept; @@ -17,6 +22,7 @@ class IslandWindow : public BaseWindow virtual void OnSize(const UINT width, const UINT height); [[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; + IRawElementProviderSimple* _GetUiaProvider(); void OnResize(const UINT width, const UINT height) override; void OnMinimize() override; void OnRestore() override; @@ -29,6 +35,40 @@ class IslandWindow : public BaseWindow void UpdateTheme(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme); +#pragma region IUiaWindow + void ChangeViewport(const SMALL_RECT /*NewWindow*/) + { + // TODO GitHub #1352: Hook up ScreenInfoUiaProvider to WindowUiaProvider + // Relevant comment from zadjii-msft: + /* + In my head for designing this, I'd then have IslandWindow::ChangeViewport + call a callback that AppHost sets, where AppHost will then call into the + TerminalApp to have TerminalApp handle the ChangeViewport call. + (See IslandWindow::SetCreateCallback as an example of a similar + pattern we're using today.) That way, if someone else were trying + to resuse this, they could have their own AppHost (or TerminalApp + equivalent) handle the ChangeViewport call their own way. + */ + return; + }; + + HWND GetWindowHandle() const noexcept override + { + return BaseWindow::GetHandle(); + }; + + [[nodiscard]] HRESULT SignalUia(_In_ EVENTID /*id*/) override { return E_NOTIMPL; }; + [[nodiscard]] HRESULT UiaSetTextAreaFocus() override { return E_NOTIMPL; }; + + RECT GetWindowRect() const noexcept override + { + return BaseWindow::GetWindowRect(); + }; + +#pragma endregion + + DECLARE_EVENT(DragRegionClicked, _DragRegionClickedHandlers, winrt::delegate<>); + protected: void ForceResize() { @@ -38,6 +78,7 @@ class IslandWindow : public BaseWindow } HWND _interopWindowHandle; + WindowUiaProvider* _pUiaProvider; winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource _source; diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index ec1f45a6e99..c486e773532 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -263,14 +263,22 @@ void NonClientIslandWindow::_UpdateDragRegion() // - Hit test the frame for resizing and moving. // Arguments: // - ptMouse: the mouse point being tested, in absolute (NOT WINDOW) coordinates. +// - titlebarIsCaption: If true, we want to treat the titlebar area as +// HTCAPTION, otherwise we'll return HTNOWHERE for the titlebar. // Return Value: // - one of the values from // https://docs.microsoft.com/en-us/windows/desktop/inputdev/wm-nchittest#return-value // corresponding to the area of the window that was hit // NOTE: -// Largely taken from code on: +// - Largely taken from code on: // https://docs.microsoft.com/en-us/windows/desktop/dwm/customframe -[[nodiscard]] LRESULT NonClientIslandWindow::HitTestNCA(POINT ptMouse) const noexcept +// NOTE[2]: Concerning `titlebarIsCaption` +// - We want HTNOWHERE as the return value for WM_NCHITTEST, so that we can get +// mouse presses in the titlebar area. If we return HTCAPTION there, we won't +// get any mouse WMs. However, when we're handling the mouse events, we need +// to know if the mouse was in that are or not, so we'll return HTCAPTION in +// that handler, to differentiate from the rest of the window. +[[nodiscard]] LRESULT NonClientIslandWindow::HitTestNCA(POINT ptMouse, const bool titlebarIsCaption) const noexcept { // Get the window rectangle. RECT rcWindow = BaseWindow::GetWindowRect(); @@ -311,10 +319,11 @@ void NonClientIslandWindow::_UpdateDragRegion() // clang-format off // Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT) + const auto topHt = fOnResizeBorder ? HTTOP : (titlebarIsCaption ? HTCAPTION : HTNOWHERE); LRESULT hitTests[3][3] = { - { HTTOPLEFT, fOnResizeBorder ? HTTOP : HTCAPTION, HTTOPRIGHT }, - { HTLEFT, HTNOWHERE, HTRIGHT }, - { HTBOTTOMLEFT, HTBOTTOM, HTBOTTOMRIGHT }, + { HTTOPLEFT, topHt, HTTOPRIGHT }, + { HTLEFT, HTNOWHERE, HTRIGHT }, + { HTBOTTOMLEFT, HTBOTTOM, HTBOTTOMRIGHT }, }; // clang-format on @@ -511,8 +520,7 @@ RECT NonClientIslandWindow::GetMaxWindowRectInPixels(const RECT* const prcSugges // Handle hit testing in the NCA if not handled by DwmDefWindowProc. if (lRet == 0) { - lRet = HitTestNCA({ GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }); - + lRet = HitTestNCA({ GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }, false); if (lRet != HTNOWHERE) { return lRet; @@ -594,9 +602,14 @@ RECT NonClientIslandWindow::GetMaxWindowRectInPixels(const RECT* const prcSugges { POINT point1 = {}; ::GetCursorPos(&point1); - const auto region = HitTestNCA(point1); + + const auto region = HitTestNCA(point1, true); if (region == HTCAPTION) { + // If we clicked in the titlebar, raise an event so the app host can + // dispatch an appropriate event. + _DragRegionClickedHandlers(); + const auto longParam = MAKELPARAM(point1.x, point1.y); ::SetActiveWindow(_window.get()); ::PostMessage(_window.get(), WM_SYSCOMMAND, SC_MOVE | HTCAPTION, longParam); @@ -623,6 +636,12 @@ RECT NonClientIslandWindow::GetMaxWindowRectInPixels(const RECT* const prcSugges break; } } + case WM_DPICHANGED: + { + auto lprcNewScale = reinterpret_cast(lParam); + OnSize(RECT_WIDTH(lprcNewScale), RECT_HEIGHT(lprcNewScale)); + break; + } } return IslandWindow::MessageHandler(message, wParam, lParam); diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h index 6949b5c64a8..161a182576b 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h @@ -55,7 +55,7 @@ class NonClientIslandWindow : public IslandWindow RECT GetDragAreaRect() const noexcept; - [[nodiscard]] LRESULT HitTestNCA(POINT ptMouse) const noexcept; + [[nodiscard]] LRESULT HitTestNCA(POINT ptMouse, const bool titlebarIsCaption) const noexcept; [[nodiscard]] HRESULT _UpdateFrameMargins() const noexcept; diff --git a/src/cascadia/WindowsTerminal/WindowUiaProvider.cpp b/src/cascadia/WindowsTerminal/WindowUiaProvider.cpp new file mode 100644 index 00000000000..176c692b9bb --- /dev/null +++ b/src/cascadia/WindowsTerminal/WindowUiaProvider.cpp @@ -0,0 +1,163 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +#include "pch.h" + +#include "WindowUiaProvider.hpp" + +#include "../host/renderData.hpp" + +WindowUiaProvider::WindowUiaProvider(Microsoft::Console::Types::IUiaWindow* baseWindow) : + WindowUiaProviderBase(baseWindow) +{ +} + +WindowUiaProvider::~WindowUiaProvider() +{ +} + +WindowUiaProvider* WindowUiaProvider::Create(Microsoft::Console::Types::IUiaWindow* baseWindow) +{ + WindowUiaProvider* pWindowProvider = nullptr; + //Microsoft::Terminal::TermControlUiaProvider* pScreenInfoProvider = nullptr; + try + { + pWindowProvider = new WindowUiaProvider(baseWindow); + + // TODO GitHub #2447: Hook up ScreenInfoUiaProvider to WindowUiaProvider + // This may be needed for the signaling model + /*Globals& g = ServiceLocator::LocateGlobals(); + CONSOLE_INFORMATION& gci = g.getConsoleInformation(); + Microsoft::Console::Render::IRenderData* renderData = &gci.renderData; + + pScreenInfoProvider = new Microsoft::Console::Types::ScreenInfoUiaProvider(renderData, pWindowProvider); + pWindowProvider->_pScreenInfoProvider = pScreenInfoProvider; + */ + + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(pWindowProvider, ApiCall::Create, nullptr); + + return pWindowProvider; + } + catch (...) + { + if (nullptr != pWindowProvider) + { + pWindowProvider->Release(); + } + + LOG_CAUGHT_EXCEPTION(); + + return nullptr; + } +} + +[[nodiscard]] HRESULT WindowUiaProvider::SetTextAreaFocus() +{ + try + { + // TODO GitHub #2447: Hook up ScreenInfoUiaProvider to WindowUiaProvider + // This may be needed for the signaling model + //return _pScreenInfoProvider->Signal(UIA_AutomationFocusChangedEventId); + return E_NOTIMPL; + } + CATCH_RETURN(); +} + +[[nodiscard]] HRESULT WindowUiaProvider::Signal(_In_ EVENTID id) +{ + HRESULT hr = S_OK; + + // ScreenInfoUiaProvider is responsible for signaling selection + // changed events and text changed events + if (id == UIA_Text_TextSelectionChangedEventId || + id == UIA_Text_TextChangedEventId) + { + // TODO GitHub #2447: Hook up ScreenInfoUiaProvider to WindowUiaProvider + // This may be needed for the signaling model + /*if (_pScreenInfoProvider) + { + hr = _pScreenInfoProvider->Signal(id); + } + else + { + hr = E_POINTER; + }*/ + hr = E_POINTER; + return hr; + } + + if (_signalEventFiring.find(id) != _signalEventFiring.end() && + _signalEventFiring[id] == true) + { + return hr; + } + + try + { + _signalEventFiring[id] = true; + } + CATCH_RETURN(); + + IRawElementProviderSimple* pProvider = static_cast(this); + hr = UiaRaiseAutomationEvent(pProvider, id); + _signalEventFiring[id] = false; + + return hr; +} + +#pragma region IRawElementProviderFragment + +IFACEMETHODIMP WindowUiaProvider::Navigate(_In_ NavigateDirection /*direction*/, _COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider) +{ + RETURN_IF_FAILED(_EnsureValidHwnd()); + *ppProvider = nullptr; + HRESULT hr = S_OK; + + // TODO GitHub #2102 or #2447: Hook up ScreenInfoUiaProvider to WindowUiaProvider + // This may be needed for the signaling model + /*if (direction == NavigateDirection_FirstChild || direction == NavigateDirection_LastChild) + { + *ppProvider = _pScreenInfoProvider; + (*ppProvider)->AddRef(); + + // signal that the focus changed + LOG_IF_FAILED(_pScreenInfoProvider->Signal(UIA_AutomationFocusChangedEventId)); + }*/ + + // For the other directions (parent, next, previous) the default of nullptr is correct + return hr; +} + +IFACEMETHODIMP WindowUiaProvider::SetFocus() +{ + RETURN_IF_FAILED(_EnsureValidHwnd()); + return Signal(UIA_AutomationFocusChangedEventId); +} +#pragma endregion + +#pragma region IRawElementProviderFragmentRoot + +IFACEMETHODIMP WindowUiaProvider::ElementProviderFromPoint(_In_ double /*x*/, + _In_ double /*y*/, + _COM_Outptr_result_maybenull_ IRawElementProviderFragment** /*ppProvider*/) +{ + RETURN_IF_FAILED(_EnsureValidHwnd()); + + // TODO GitHub #2447: Hook up ScreenInfoUiaProvider to WindowUiaProvider + // This may be needed for the signaling model + /**ppProvider = _pScreenInfoProvider; + (*ppProvider)->AddRef();*/ + + return S_OK; +} + +IFACEMETHODIMP WindowUiaProvider::GetFocus(_COM_Outptr_result_maybenull_ IRawElementProviderFragment** /*ppProvider*/) +{ + RETURN_IF_FAILED(_EnsureValidHwnd()); + // TODO GitHub #2447: Hook up ScreenInfoUiaProvider to WindowUiaProvider + // This may be needed for the signaling model + //return _pScreenInfoProvider->QueryInterface(IID_PPV_ARGS(ppProvider)); + return S_OK; +} + +#pragma endregion diff --git a/src/cascadia/WindowsTerminal/WindowUiaProvider.hpp b/src/cascadia/WindowsTerminal/WindowUiaProvider.hpp new file mode 100644 index 00000000000..835ba87211b --- /dev/null +++ b/src/cascadia/WindowsTerminal/WindowUiaProvider.hpp @@ -0,0 +1,53 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- windowUiaProvider.hpp + +Abstract: +- This module provides UI Automation access to the console window to + support both automation tests and accessibility (screen reading) + applications. +- Based on examples, sample code, and guidance from + https://msdn.microsoft.com/en-us/library/windows/desktop/ee671596(v=vs.85).aspx + +Author(s): +- Michael Niksa (MiNiksa) 2017 +- Austin Diviness (AustDi) 2017 +- Carlos Zamora (CaZamor) 2019 +--*/ + +#pragma once + +#include "../types/WindowUiaProviderBase.hpp" +#include "../types/IUiaWindow.h" + +class WindowUiaProvider final : + public Microsoft::Console::Types::WindowUiaProviderBase +{ +public: + static WindowUiaProvider* Create(Microsoft::Console::Types::IUiaWindow* baseWindow); + + [[nodiscard]] HRESULT Signal(_In_ EVENTID id) override; + [[nodiscard]] HRESULT SetTextAreaFocus() override; + + // IRawElementProviderFragment methods + IFACEMETHODIMP Navigate(_In_ NavigateDirection direction, + _COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider) override; + IFACEMETHODIMP SetFocus() override; + + // IRawElementProviderFragmentRoot methods + IFACEMETHODIMP ElementProviderFromPoint(_In_ double x, + _In_ double y, + _COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider) override; + IFACEMETHODIMP GetFocus(_COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider) override; + +protected: + const OLECHAR* AutomationIdPropertyName = L"Terminal Window"; + const OLECHAR* ProviderDescriptionPropertyName = L"Microsoft Windows Terminal Window"; + +private: + WindowUiaProvider(Microsoft::Console::Types::IUiaWindow* baseWindow); + ~WindowUiaProvider(); +}; diff --git a/src/cascadia/WindowsTerminal/WindowsTerminal.manifest b/src/cascadia/WindowsTerminal/WindowsTerminal.manifest index ef9516047af..a447bc8fdc2 100644 --- a/src/cascadia/WindowsTerminal/WindowsTerminal.manifest +++ b/src/cascadia/WindowsTerminal/WindowsTerminal.manifest @@ -9,6 +9,7 @@ + diff --git a/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj b/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj index 7cf65caa7d2..f602eaf99a0 100644 --- a/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj +++ b/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj @@ -2,7 +2,7 @@ - + Application @@ -53,6 +53,7 @@ + @@ -62,6 +63,7 @@ + @@ -108,17 +110,17 @@ - - - + + + - - + + + diff --git a/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj b/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj index d535571359b..94784d216ee 100644 --- a/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj +++ b/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj @@ -11,8 +11,6 @@ - - Create @@ -122,4 +120,8 @@ + + + diff --git a/src/cascadia/ut_app/precomp.h b/src/cascadia/ut_app/precomp.h index 5c19aff512f..24638220f0f 100644 --- a/src/cascadia/ut_app/precomp.h +++ b/src/cascadia/ut_app/precomp.h @@ -29,25 +29,14 @@ Author(s): #include #include "consoletaeftemplates.hpp" -// Needed just for XamlIslands to work at all: -#include -#include -#include -#include - // Common includes for most tests: #include "../../inc/argb.h" #include "../../inc/conattrs.hpp" #include "../../types/inc/utils.hpp" #include "../../inc/DefaultSettings.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#include +// Are you thinking about adding WinRT things here? If so, you probably want to +// add your test to TerminalApp.LocalTests, not TerminalApp.UnitTests. The +// UnitTests run in CI, while the LocalTests do not. However, since the CI can't +// run XAML islands or unpackaged WinRT, any tests using those features will +// need to be added to the LocalTests. diff --git a/src/common.build.post.props b/src/common.build.post.props index 5539b05eed4..b4390287b0e 100644 --- a/src/common.build.post.props +++ b/src/common.build.post.props @@ -30,4 +30,12 @@ $(SolutionDir)\dep\;$(CAExcludePath) + + + + + + + + diff --git a/src/cppwinrt.build.post.props b/src/cppwinrt.build.post.props index 38b012cb9ec..6ef2b633728 100644 --- a/src/cppwinrt.build.post.props +++ b/src/cppwinrt.build.post.props @@ -64,13 +64,13 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + diff --git a/src/cppwinrt.build.pre.props b/src/cppwinrt.build.pre.props index d34a9ef3fcf..58c8f3aee90 100644 --- a/src/cppwinrt.build.pre.props +++ b/src/cppwinrt.build.pre.props @@ -3,7 +3,7 @@ - + @@ -94,6 +94,7 @@ pch.h $(IntDir)pch.pch Level4 + true %(AdditionalOptions) /permissive- /bigobj /Zc:twoPhase- /std:c++17 28204 _WINRT_DLL;%(PreprocessorDefinitions) diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index 3ea8d7fb386..cf7ab1ecbe5 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -397,3 +397,37 @@ void VtIo::_ShutdownIfNeeded() ServiceLocator::RundownAndExit(ERROR_BROKEN_PIPE); } } + +// Method Description: +// - Tell the vt renderer to begin a resize operation. During a resize +// operation, the vt renderer should _not_ request to be repainted during a +// text buffer circling event. Any callers of this method should make sure to +// call EndResize to make sure the renderer returns to normal behavior. +// See GH#1795 for context on this method. +// Arguments: +// - +// Return Value: +// - +void VtIo::BeginResize() +{ + if (_pVtRenderEngine) + { + _pVtRenderEngine->BeginResizeRequest(); + } +} + +// Method Description: +// - Tell the vt renderer to end a resize operation. +// See BeginResize for more details. +// See GH#1795 for context on this method. +// Arguments: +// - +// Return Value: +// - +void VtIo::EndResize() +{ + if (_pVtRenderEngine) + { + _pVtRenderEngine->EndResizeRequest(); + } +} diff --git a/src/host/VtIo.hpp b/src/host/VtIo.hpp index dc037b9372a..0516d9ab0ea 100644 --- a/src/host/VtIo.hpp +++ b/src/host/VtIo.hpp @@ -36,6 +36,9 @@ namespace Microsoft::Console::VirtualTerminal void CloseInput() override; void CloseOutput() override; + void BeginResize(); + void EndResize(); + private: // After CreateIoHandlers is called, these will be invalid. wil::unique_hfile _hInput; diff --git a/src/host/cmdline.h b/src/host/cmdline.h index 8dd873aa9d7..6c696c67478 100644 --- a/src/host/cmdline.h +++ b/src/host/cmdline.h @@ -102,9 +102,9 @@ class CommandLine CommandLine(CommandLine const&) = delete; CommandLine& operator=(CommandLine const&) = delete; - [[nodiscard]] NTSTATUS CommandLine::_startCommandListPopup(COOKED_READ_DATA& cookedReadData); - [[nodiscard]] NTSTATUS CommandLine::_startCopyFromCharPopup(COOKED_READ_DATA& cookedReadData); - [[nodiscard]] NTSTATUS CommandLine::_startCopyToCharPopup(COOKED_READ_DATA& cookedReadData); + [[nodiscard]] NTSTATUS _startCommandListPopup(COOKED_READ_DATA& cookedReadData); + [[nodiscard]] NTSTATUS _startCopyFromCharPopup(COOKED_READ_DATA& cookedReadData); + [[nodiscard]] NTSTATUS _startCopyToCharPopup(COOKED_READ_DATA& cookedReadData); void _processHistoryCycling(COOKED_READ_DATA& cookedReadData, const CommandHistory::SearchDirection searchDirection); void _setPromptToOldestCommand(COOKED_READ_DATA& cookedReadData); diff --git a/src/host/directio.cpp b/src/host/directio.cpp index 645b28e2fb6..2eb4ce1b672 100644 --- a/src/host/directio.cpp +++ b/src/host/directio.cpp @@ -273,13 +273,18 @@ void EventsToUnicode(_Inout_ std::deque>& inEvents, { try { - RETURN_NTSTATUS(_DoGetConsoleInput(context, - outEvents, - eventsToRead, - readHandleState, - false, - true, - waiter)); + NTSTATUS Status = _DoGetConsoleInput(context, + outEvents, + eventsToRead, + readHandleState, + false, + true, + waiter); + if (CONSOLE_STATUS_WAIT == Status) + { + return HRESULT_FROM_NT(Status); + } + RETURN_NTSTATUS(Status); } CATCH_RETURN(); } @@ -308,13 +313,18 @@ void EventsToUnicode(_Inout_ std::deque>& inEvents, { try { - RETURN_NTSTATUS(_DoGetConsoleInput(context, - outEvents, - eventsToRead, - readHandleState, - true, - true, - waiter)); + NTSTATUS Status = _DoGetConsoleInput(context, + outEvents, + eventsToRead, + readHandleState, + true, + true, + waiter); + if (CONSOLE_STATUS_WAIT == Status) + { + return HRESULT_FROM_NT(Status); + } + RETURN_NTSTATUS(Status); } CATCH_RETURN(); } @@ -343,13 +353,18 @@ void EventsToUnicode(_Inout_ std::deque>& inEvents, { try { - RETURN_NTSTATUS(_DoGetConsoleInput(context, - outEvents, - eventsToRead, - readHandleState, - false, - false, - waiter)); + NTSTATUS Status = _DoGetConsoleInput(context, + outEvents, + eventsToRead, + readHandleState, + false, + false, + waiter); + if (CONSOLE_STATUS_WAIT == Status) + { + return HRESULT_FROM_NT(Status); + } + RETURN_NTSTATUS(Status); } CATCH_RETURN(); } @@ -378,13 +393,18 @@ void EventsToUnicode(_Inout_ std::deque>& inEvents, { try { - RETURN_NTSTATUS(_DoGetConsoleInput(context, - outEvents, - eventsToRead, - readHandleState, - true, - false, - waiter)); + NTSTATUS Status = _DoGetConsoleInput(context, + outEvents, + eventsToRead, + readHandleState, + true, + false, + waiter); + if (CONSOLE_STATUS_WAIT == Status) + { + return HRESULT_FROM_NT(Status); + } + RETURN_NTSTATUS(Status); } CATCH_RETURN(); } diff --git a/src/host/ft_host/API_PolicyTests.cpp b/src/host/ft_host/API_PolicyTests.cpp index c70e02fd403..89f2bef1c4e 100644 --- a/src/host/ft_host/API_PolicyTests.cpp +++ b/src/host/ft_host/API_PolicyTests.cpp @@ -15,6 +15,7 @@ class PolicyTests #ifdef __INSIDE_WINDOWS BEGIN_TEST_METHOD(WrongWayVerbsUAP) TEST_METHOD_PROPERTY(L"RunAs", L"UAP") + TEST_METHOD_PROPERTY(L"UAP:AppxManifest", L"MUA") END_TEST_METHOD(); #endif diff --git a/src/host/ft_host/Host.FeatureTests.vcxproj b/src/host/ft_host/Host.FeatureTests.vcxproj index 7a8205072a8..ffebea0acd1 100644 --- a/src/host/ft_host/Host.FeatureTests.vcxproj +++ b/src/host/ft_host/Host.FeatureTests.vcxproj @@ -36,6 +36,9 @@ {18d09a24-8240-42d6-8cb6-236eee820263} + + {58a03bb2-df5a-4b66-91a0-7ef3ba01269a} + @@ -56,4 +59,10 @@ - \ No newline at end of file + + + + $(OutDir)\conpty.lib;%(AdditionalDependencies) + + + diff --git a/src/host/getset.cpp b/src/host/getset.cpp index f576c1d9414..7bf5c74d97e 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -632,7 +632,9 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const COORD coordScreenBufferSize = context.GetBufferSize().Dimensions(); + auto& buffer = context.GetActiveBuffer(); + + const COORD coordScreenBufferSize = buffer.GetBufferSize().Dimensions(); // clang-format off RETURN_HR_IF(E_INVALIDARG, (position.X >= coordScreenBufferSize.X || position.Y >= coordScreenBufferSize.Y || @@ -643,7 +645,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont // MSFT: 15813316 - Try to use this SetCursorPosition call to inherit the cursor position. RETURN_IF_FAILED(gci.GetVtIo()->SetCursorPosition(position)); - RETURN_IF_NTSTATUS_FAILED(context.SetCursorPosition(position, true)); + RETURN_IF_NTSTATUS_FAILED(buffer.SetCursorPosition(position, true)); LOG_IF_FAILED(ConsoleImeResizeCompStrView()); @@ -651,7 +653,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont WindowOrigin.X = 0; WindowOrigin.Y = 0; { - const SMALL_RECT currentViewport = context.GetViewport().ToInclusive(); + const SMALL_RECT currentViewport = buffer.GetViewport().ToInclusive(); if (currentViewport.Left > position.X) { WindowOrigin.X = position.X - currentViewport.Left; @@ -671,7 +673,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont } } - RETURN_IF_NTSTATUS_FAILED(context.SetViewportOrigin(false, WindowOrigin, true)); + RETURN_IF_NTSTATUS_FAILED(buffer.SetViewportOrigin(false, WindowOrigin, true)); return S_OK; } @@ -820,6 +822,8 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); + auto& buffer = context.GetActiveBuffer(); + TextAttribute useThisAttr(fillAttribute); // Here we're being a little clever - similar to FillConsoleOutputAttributeImpl @@ -835,10 +839,11 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont // this scenario is highly unlikely, and we can reasonably do this // on their behalf. // see MSFT:19853701 - if (context.InVTMode()) + + if (buffer.InVTMode()) { const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto currentAttributes = context.GetAttributes(); + const auto currentAttributes = buffer.GetAttributes(); const auto bufferLegacy = gci.GenerateLegacyAttributes(currentAttributes); if (bufferLegacy == fillAttribute) { @@ -846,7 +851,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont } } - ScrollRegion(context, source, clip, target, fillCharacter, useThisAttr); + ScrollRegion(buffer, source, clip, target, fillCharacter, useThisAttr); return S_OK; } @@ -1356,24 +1361,22 @@ void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool if (screenInfo.IsCursorInMargins(oldCursorPosition)) { // Cursor is at the top of the viewport - const COORD bufferSize = screenInfo.GetBufferSize().Dimensions(); - // Rectangle to cut out of the existing buffer + // Rectangle to cut out of the existing buffer. This is inclusive. + // It will be clipped to the buffer boundaries so SHORT_MAX gives us the full buffer width. SMALL_RECT srScroll; srScroll.Left = 0; - srScroll.Right = bufferSize.X; + srScroll.Right = SHORT_MAX; srScroll.Top = viewport.Top; - srScroll.Bottom = viewport.Bottom - 1; + srScroll.Bottom = viewport.Bottom; // Paste coordinate for cut text above COORD coordDestination; coordDestination.X = 0; coordDestination.Y = viewport.Top + 1; - SMALL_RECT srClip = viewport; - Status = NTSTATUS_FROM_HRESULT(ServiceLocator::LocateGlobals().api.ScrollConsoleScreenBufferWImpl(screenInfo, srScroll, coordDestination, - srClip, + srScroll, UNICODE_SPACE, screenInfo.GetAttributes().GetLegacyAttributes())); } @@ -2023,6 +2026,7 @@ void DoSrvPrivateSetDefaultTabStops() // Routine Description: // - internal logic for adding or removing lines in the active screen buffer +// this also moves the cursor to the left margin, which is expected behaviour for IL and DL // Parameters: // - count - the number of lines to modify // - insert - true if inserting lines, false if deleting lines @@ -2033,13 +2037,13 @@ void DoSrvPrivateModifyLinesImpl(const unsigned int count, const bool insert) const auto cursorPosition = textBuffer.GetCursor().GetPosition(); if (screenInfo.IsCursorInMargins(cursorPosition)) { - const auto screenEdges = screenInfo.GetBufferSize().ToInclusive(); - // Rectangle to cut out of the existing buffer + // Rectangle to cut out of the existing buffer. This is inclusive. + // It will be clipped to the buffer boundaries so SHORT_MAX gives us the full buffer width. SMALL_RECT srScroll; srScroll.Left = 0; - srScroll.Right = screenEdges.Right - screenEdges.Left; + srScroll.Right = SHORT_MAX; srScroll.Top = cursorPosition.Y; - srScroll.Bottom = screenEdges.Bottom; + srScroll.Bottom = screenInfo.GetViewport().BottomInclusive(); // Paste coordinate for cut text above COORD coordDestination; coordDestination.X = 0; @@ -2052,15 +2056,29 @@ void DoSrvPrivateModifyLinesImpl(const unsigned int count, const bool insert) coordDestination.Y = (cursorPosition.Y) - gsl::narrow(count); } - SMALL_RECT srClip = screenEdges; - srClip.Top = cursorPosition.Y; + // Here we previously called to ScrollConsoleScreenBufferWImpl to + // perform the scrolling operation. However, that function only accepts + // a WORD for the fill attributes. That means we'd lose 256/RGB fidelity + // for fill attributes. So instead, we'll just call ScrollRegion + // ourselves, with the same params that ScrollConsoleScreenBufferWImpl + // would have. + // See microsoft/terminal#832 for more context. + try + { + LockConsole(); + auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); + ScrollRegion(screenInfo, + srScroll, + srScroll, + coordDestination, + UNICODE_SPACE, + screenInfo.GetAttributes()); + } + CATCH_LOG(); - LOG_IF_FAILED(ServiceLocator::LocateGlobals().api.ScrollConsoleScreenBufferWImpl(screenInfo, - srScroll, - coordDestination, - srClip, - UNICODE_SPACE, - screenInfo.GetAttributes().GetLegacyAttributes())); + // The IL and DL controls are also expected to move the cursor to the left margin. + // For now this is just column 0, since we don't yet support DECSLRM. + LOG_IF_NTSTATUS_FAILED(screenInfo.SetCursorPosition({ 0, cursorPosition.Y }, false)); } } diff --git a/src/host/renderData.cpp b/src/host/renderData.cpp index 7b747f62163..92030f03fd2 100644 --- a/src/host/renderData.cpp +++ b/src/host/renderData.cpp @@ -7,13 +7,15 @@ #include "dbcs.h" #include "handle.h" - #include "..\interactivity\inc\ServiceLocator.hpp" #pragma hdrstop using namespace Microsoft::Console::Types; +using namespace Microsoft::Console::Interactivity::Win32; using Microsoft::Console::Interactivity::ServiceLocator; + +#pragma region IBaseData // Routine Description: // - Retrieves the viewport that applies over the data available in the GetTextBuffer() call // Return Value: @@ -45,6 +47,48 @@ const FontInfo& RenderData::GetFontInfo() noexcept return gci.GetActiveOutputBuffer().GetCurrentFont(); } +// Method Description: +// - Retrieves one rectangle per line describing the area of the viewport +// that should be highlighted in some way to represent a user-interactive selection +// Return Value: +// - Vector of Viewports describing the area selected +std::vector RenderData::GetSelectionRects() noexcept +{ + std::vector result; + + try + { + for (const auto& select : Selection::Instance().GetSelectionRects()) + { + result.emplace_back(Viewport::FromInclusive(select)); + } + } + CATCH_LOG(); + + return result; +} + +// Method Description: +// - Lock the console for reading the contents of the buffer. Ensures that the +// contents of the console won't be changed in the middle of a paint +// operation. +// Callers should make sure to also call RenderData::UnlockConsole once +// they're done with any querying they need to do. +void RenderData::LockConsole() noexcept +{ + ::LockConsole(); +} + +// Method Description: +// - Unlocks the console after a call to RenderData::LockConsole. +void RenderData::UnlockConsole() noexcept +{ + ::UnlockConsole(); +} + +#pragma endregion + +#pragma region IRenderData // Routine Description: // - Retrieves the brush colors that should be used in absence of any other color data from // cells in the text buffer. @@ -225,27 +269,6 @@ bool RenderData::IsCursorDoubleWidth() const noexcept return gci.GetActiveOutputBuffer().CursorIsDoubleWidth(); } -// Method Description: -// - Retrieves one rectangle per line describing the area of the viewport -// that should be highlighted in some way to represent a user-interactive selection -// Return Value: -// - Vector of Viewports describing the area selected -std::vector RenderData::GetSelectionRects() noexcept -{ - std::vector result; - - try - { - for (const auto& select : Selection::Instance().GetSelectionRects()) - { - result.emplace_back(Viewport::FromInclusive(select)); - } - } - CATCH_LOG(); - - return result; -} - // Routine Description: // - Checks the user preference as to whether grid line drawing is allowed around the edges of each cell. // - This is for backwards compatibility with old behaviors in the legacy console. @@ -309,21 +332,41 @@ const COLORREF RenderData::GetBackgroundColor(const TextAttribute& attr) const n const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); return gci.LookupBackgroundColor(attr); } +#pragma endregion -// Method Description: -// - Lock the console for reading the contents of the buffer. Ensures that the -// contents of the console won't be changed in the middle of a paint -// operation. -// Callers should make sure to also call RenderData::UnlockConsole once -// they're done with any querying they need to do. -void RenderData::LockConsole() noexcept +#pragma region IUiaData +// Routine Description: +// - Determines whether the selection area is empty. +// Arguments: +// - +// Return Value: +// - True if the selection variables contain valid selection data. False otherwise. +const bool RenderData::IsSelectionActive() const { - ::LockConsole(); + return Selection::Instance().IsAreaSelected(); } -// Method Description: -// - Unlocks the console after a call to RenderData::LockConsole. -void RenderData::UnlockConsole() noexcept +// Routine Description: +// - If a selection exists, clears it and restores the state. +// Will also unblock a blocked write if one exists. +// Arguments: +// - (Uses global state) +// Return Value: +// - +void RenderData::ClearSelection() { - ::UnlockConsole(); + Selection::Instance().ClearSelection(); +} + +// Routine Description: +// - Resets the current selection and selects a new region from the start to end coordinates +// Arguments: +// - coordStart - Position to start selection area from +// - coordEnd - Position to select up to +// Return Value: +// - +void RenderData::SelectNewRegion(const COORD coordStart, const COORD coordEnd) +{ + Selection::Instance().SelectNewRegion(coordStart, coordEnd); } +#pragma endregion diff --git a/src/host/renderData.hpp b/src/host/renderData.hpp index 359d4348ff4..717e7b8418d 100644 --- a/src/host/renderData.hpp +++ b/src/host/renderData.hpp @@ -15,13 +15,25 @@ Author(s): #pragma once #include "..\renderer\inc\IRenderData.hpp" +#include "..\types\IUiaData.h" -class RenderData final : public Microsoft::Console::Render::IRenderData +class RenderData final : + public Microsoft::Console::Render::IRenderData, + public Microsoft::Console::Types::IUiaData { public: +#pragma region BaseData Microsoft::Console::Types::Viewport GetViewport() noexcept override; const TextBuffer& GetTextBuffer() noexcept override; const FontInfo& GetFontInfo() noexcept override; + + std::vector GetSelectionRects() noexcept override; + + void LockConsole() noexcept override; + void UnlockConsole() noexcept override; +#pragma endregion + +#pragma region IRenderData const TextAttribute GetDefaultBrushColors() noexcept override; const COLORREF GetForegroundColor(const TextAttribute& attr) const noexcept override; @@ -40,10 +52,12 @@ class RenderData final : public Microsoft::Console::Render::IRenderData const bool IsGridLineDrawingAllowed() noexcept override; - std::vector GetSelectionRects() noexcept override; - const std::wstring GetConsoleTitle() const noexcept override; +#pragma endregion - void LockConsole() noexcept override; - void UnlockConsole() noexcept override; +#pragma region IUiaData + const bool IsSelectionActive() const override; + void ClearSelection() override; + void SelectNewRegion(const COORD coordStart, const COORD coordEnd) override; +#pragma endregion }; diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index c8bee162166..3fabea0bae6 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -1231,10 +1231,13 @@ void SCREEN_INFORMATION::_InternalSetViewportSize(const COORD* const pcoordSize, // See MSFT:19917443 // If we're in terminal scrolling mode, and we've changed the height of the - // viewport, the new viewport's bottom to the _virtualBottom + // viewport, the new viewport's bottom to the _virtualBottom. + // GH#1206 - Only do this if the viewport is _growing_ in height. This can + // cause unexpected behavior if we try to anchor the _virtualBottom to a + // position that will be greater than the height of the buffer. const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); auto newViewport = Viewport::FromInclusive(srNewViewport); - if (gci.IsTerminalScrolling() && newViewport.Height() != _viewport.Height()) + if (gci.IsTerminalScrolling() && newViewport.Height() >= _viewport.Height()) { const short newTop = static_cast(std::max(0, _virtualBottom - (newViewport.Height() - 1))); @@ -1670,6 +1673,19 @@ bool SCREEN_INFORMATION::IsMaximizedY() const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); NTSTATUS status = STATUS_SUCCESS; + // If we're in conpty mode, suppress any immediate painting we might do + // during the resize. + if (gci.IsInVtIoMode()) + { + gci.GetVtIo()->BeginResize(); + } + auto endResize = wil::scope_exit([&] { + if (gci.IsInVtIoMode()) + { + gci.GetVtIo()->EndResize(); + } + }); + // cancel any active selection before resizing or it will not necessarily line up with the new buffer positions Selection::Instance().ClearSelection(); diff --git a/src/host/screenInfo.hpp b/src/host/screenInfo.hpp index d44a16e05d6..fc6cbbf07c1 100644 --- a/src/host/screenInfo.hpp +++ b/src/host/screenInfo.hpp @@ -39,7 +39,6 @@ Revision History: #include "../server/ObjectHeader.h" #include "../interactivity/inc/IAccessibilityNotifier.hpp" -#include "../interactivity/inc/IConsoleWindow.hpp" #include "../interactivity/inc/IWindowMetrics.hpp" #include "../inc/ITerminalOutputConnection.hpp" @@ -48,6 +47,7 @@ Revision History: #include "../renderer/inc/FontInfoDesired.hpp" #include "../types/inc/Viewport.hpp" +#include "../types/IConsoleWindow.hpp" class ConversionAreaInfo; // forward decl window. circular reference class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console::IIoProvider diff --git a/src/host/scrolling.cpp b/src/host/scrolling.cpp index 1c344b5e594..e45d9aece7e 100644 --- a/src/host/scrolling.cpp +++ b/src/host/scrolling.cpp @@ -11,6 +11,7 @@ using Microsoft::Console::VirtualTerminal::StateMachine; using namespace Microsoft::Console::Interactivity; +using namespace Microsoft::Console::Types; ULONG Scrolling::s_ucWheelScrollLines = 0; ULONG Scrolling::s_ucWheelScrollChars = 0; diff --git a/src/host/selection.cpp b/src/host/selection.cpp index 6dee0d808ad..5a361595f6f 100644 --- a/src/host/selection.cpp +++ b/src/host/selection.cpp @@ -10,6 +10,7 @@ #include "../interactivity/inc/ServiceLocator.hpp" using namespace Microsoft::Console::Interactivity; +using namespace Microsoft::Console::Types; std::unique_ptr Selection::_instance; diff --git a/src/host/selection.hpp b/src/host/selection.hpp index aee97594f34..1d8733479b0 100644 --- a/src/host/selection.hpp +++ b/src/host/selection.hpp @@ -21,7 +21,7 @@ Revision History: #include "input.h" #include "..\interactivity\inc\IAccessibilityNotifier.hpp" -#include "..\interactivity\inc\IConsoleWindow.hpp" +#include "..\types\IConsoleWindow.hpp" class Selection { diff --git a/src/host/srvinit.cpp b/src/host/srvinit.cpp index be91304d2bf..fe32f13d245 100644 --- a/src/host/srvinit.cpp +++ b/src/host/srvinit.cpp @@ -203,7 +203,7 @@ static bool s_IsOnDesktop() if (fRecomputeOwner) { - IConsoleWindow* pWindow = ServiceLocator::LocateConsoleWindow(); + Microsoft::Console::Types::IConsoleWindow* pWindow = ServiceLocator::LocateConsoleWindow(); if (pWindow != nullptr) { pWindow->SetOwner(); diff --git a/src/host/tracing.cpp b/src/host/tracing.cpp index 4b80090ae8b..6c48a599fdd 100644 --- a/src/host/tracing.cpp +++ b/src/host/tracing.cpp @@ -3,10 +3,12 @@ #include "precomp.h" #include "tracing.hpp" -#include "../interactivity/win32/UiaTextRange.hpp" -#include "../interactivity/win32/screenInfoUiaProvider.hpp" -#include "../interactivity/win32/windowUiaProvider.hpp" +#include "../types/UiaTextRangeBase.hpp" +#include "../types/ScreenInfoUiaProviderBase.h" +#include "../types/WindowUiaProviderBase.hpp" + +using namespace Microsoft::Console::Types; using namespace Microsoft::Console::Interactivity::Win32; enum TraceKeywords @@ -245,7 +247,7 @@ void Tracing::s_TraceApi(const CONSOLE_WRITECONSOLEOUTPUTSTRING_MSG* const a) TraceLoggingKeyword(TraceKeywords::API)); } -void Tracing::s_TraceWindowViewport(const Microsoft::Console::Types::Viewport& viewport) +void Tracing::s_TraceWindowViewport(const Viewport& viewport) { TraceLoggingWrite( g_hConhostV2EventTraceProvider, @@ -397,6 +399,8 @@ void __stdcall Tracing::TraceFailure(const wil::FailureInfo& failure) noexcept TraceLoggingLevel(WINEVENT_LEVEL_ERROR)); } +// TODO GitHub #1914: Re-attach Tracing to UIA Tree +#if 0 void Tracing::s_TraceUia(const UiaTextRange* const range, const UiaTextRangeTracing::ApiCall apiCall, const UiaTextRangeTracing::IApiMsg* const apiMsg) @@ -729,7 +733,7 @@ void Tracing::s_TraceUia(const UiaTextRange* const range, } } -void Tracing::s_TraceUia(const ScreenInfoUiaProvider* const /*pProvider*/, +void Tracing::s_TraceUia(const Microsoft::Console::Interactivity::Win32::ScreenInfoUiaProvider* const /*pProvider*/, const ScreenInfoUiaProviderTracing::ApiCall apiCall, const ScreenInfoUiaProviderTracing::IApiMsg* const apiMsg) { @@ -898,22 +902,22 @@ void Tracing::s_TraceUia(const ScreenInfoUiaProvider* const /*pProvider*/, } } -void Tracing::s_TraceUia(const WindowUiaProvider* const /*pProvider*/, - const WindowUiaProviderTracing::ApiCall apiCall, - const WindowUiaProviderTracing::IApiMsg* const apiMsg) +void Tracing::s_TraceUia(const Microsoft::Console::Types::WindowUiaProvider* const /*pProvider*/, + const Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall apiCall, + const Microsoft::Console::Types::WindowUiaProviderTracing::IApiMsg* const apiMsg) { switch (apiCall) { - case WindowUiaProviderTracing::ApiCall::Create: + case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::Create: TraceLoggingWrite( g_hConhostV2EventTraceProvider, "WindowUiaProvider::Create", TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TraceKeywords::UIA)); break; - case WindowUiaProviderTracing::ApiCall::Signal: + case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::Signal: { - const WindowUiaProviderTracing::ApiMessageSignal* const msg = static_cast(apiMsg); + const Microsoft::Console::Types::WindowUiaProviderTracing::ApiMessageSignal* const msg = static_cast(apiMsg); const wchar_t* const eventName = _eventIdToString(msg->Signal); TraceLoggingWrite( g_hConhostV2EventTraceProvider, @@ -924,58 +928,58 @@ void Tracing::s_TraceUia(const WindowUiaProvider* const /*pProvider*/, TraceLoggingKeyword(TraceKeywords::UIA)); break; } - case WindowUiaProviderTracing::ApiCall::AddRef: + case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::AddRef: TraceLoggingWrite( g_hConhostV2EventTraceProvider, "WindowUiaProvider::AddRef", TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TraceKeywords::UIA)); break; - case WindowUiaProviderTracing::ApiCall::Release: + case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::Release: TraceLoggingWrite( g_hConhostV2EventTraceProvider, "WindowUiaProvider::Release", TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TraceKeywords::UIA)); break; - case WindowUiaProviderTracing::ApiCall::QueryInterface: + case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::QueryInterface: TraceLoggingWrite( g_hConhostV2EventTraceProvider, "WindowUiaProvider::QueryInterface", TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TraceKeywords::UIA)); break; - case WindowUiaProviderTracing::ApiCall::GetProviderOptions: + case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::GetProviderOptions: TraceLoggingWrite( g_hConhostV2EventTraceProvider, "WindowUiaProvider::GetProviderOptions", TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TraceKeywords::UIA)); break; - case WindowUiaProviderTracing::ApiCall::GetPatternProvider: + case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::GetPatternProvider: TraceLoggingWrite( g_hConhostV2EventTraceProvider, "WindowUiaProvider::GetPatternProvider", TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TraceKeywords::UIA)); break; - case WindowUiaProviderTracing::ApiCall::GetPropertyValue: + case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::GetPropertyValue: TraceLoggingWrite( g_hConhostV2EventTraceProvider, "WindowUiaProvider::GetPropertyValue", TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TraceKeywords::UIA)); break; - case WindowUiaProviderTracing::ApiCall::GetHostRawElementProvider: + case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::GetHostRawElementProvider: TraceLoggingWrite( g_hConhostV2EventTraceProvider, "WindowUiaProvider::GetHostRawElementProvider", TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TraceKeywords::UIA)); break; - case WindowUiaProviderTracing::ApiCall::Navigate: + case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::Navigate: { - const WindowUiaProviderTracing::ApiMsgNavigate* const msg = static_cast(apiMsg); + const Microsoft::Console::Types::WindowUiaProviderTracing::ApiMsgNavigate* const msg = static_cast(apiMsg); const wchar_t* const direction = _directionToString(msg->Direction); TraceLoggingWrite( g_hConhostV2EventTraceProvider, @@ -985,49 +989,49 @@ void Tracing::s_TraceUia(const WindowUiaProvider* const /*pProvider*/, TraceLoggingKeyword(TraceKeywords::UIA)); break; } - case WindowUiaProviderTracing::ApiCall::GetRuntimeId: + case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::GetRuntimeId: TraceLoggingWrite( g_hConhostV2EventTraceProvider, "WindowUiaProvider::GetRuntimeId", TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TraceKeywords::UIA)); break; - case WindowUiaProviderTracing::ApiCall::GetBoundingRectangle: + case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::GetBoundingRectangle: TraceLoggingWrite( g_hConhostV2EventTraceProvider, "WindowUiaProvider::GetBoundingRectangle", TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TraceKeywords::UIA)); break; - case WindowUiaProviderTracing::ApiCall::GetEmbeddedFragmentRoots: + case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::GetEmbeddedFragmentRoots: TraceLoggingWrite( g_hConhostV2EventTraceProvider, "WindowUiaProvider::GetEmbeddedFragmentRoots", TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TraceKeywords::UIA)); break; - case WindowUiaProviderTracing::ApiCall::SetFocus: + case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::SetFocus: TraceLoggingWrite( g_hConhostV2EventTraceProvider, "WindowUiaProvider::SetFocus", TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TraceKeywords::UIA)); break; - case WindowUiaProviderTracing::ApiCall::GetFragmentRoot: + case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::GetFragmentRoot: TraceLoggingWrite( g_hConhostV2EventTraceProvider, "WindowUiaProvider::GetFragmentRoot", TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TraceKeywords::UIA)); break; - case WindowUiaProviderTracing::ApiCall::ElementProviderFromPoint: + case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::ElementProviderFromPoint: TraceLoggingWrite( g_hConhostV2EventTraceProvider, "WindowUiaProvider::ElementProviderFromPoint", TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TraceKeywords::UIA)); break; - case WindowUiaProviderTracing::ApiCall::GetFocus: + case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::GetFocus: TraceLoggingWrite( g_hConhostV2EventTraceProvider, "WindowUiaProvider::GetFocus", @@ -1038,6 +1042,7 @@ void Tracing::s_TraceUia(const WindowUiaProvider* const /*pProvider*/, break; } } +#endif const wchar_t* const Tracing::_textPatternRangeEndpointToString(int endpoint) { diff --git a/src/host/tracing.hpp b/src/host/tracing.hpp index a54b7c9bc2c..4f5d8b44fcd 100644 --- a/src/host/tracing.hpp +++ b/src/host/tracing.hpp @@ -38,14 +38,6 @@ namespace Microsoft::Console::Interactivity::Win32 enum class ApiCall; struct IApiMsg; } - - class WindowUiaProvider; - - namespace WindowUiaProviderTracing - { - enum class ApiCall; - struct IApiMsg; - } } #if DBG @@ -91,6 +83,8 @@ class Tracing static void __stdcall TraceFailure(const wil::FailureInfo& failure) noexcept; +// TODO GitHub #1914: Re-attach Tracing to UIA Tree +#if 0 static void s_TraceUia(const Microsoft::Console::Interactivity::Win32::UiaTextRange* const range, const Microsoft::Console::Interactivity::Win32::UiaTextRangeTracing::ApiCall apiCall, const Microsoft::Console::Interactivity::Win32::UiaTextRangeTracing::IApiMsg* const apiMsg); @@ -99,9 +93,10 @@ class Tracing const Microsoft::Console::Interactivity::Win32::ScreenInfoUiaProviderTracing::ApiCall apiCall, const Microsoft::Console::Interactivity::Win32::ScreenInfoUiaProviderTracing::IApiMsg* const apiMsg); - static void s_TraceUia(const Microsoft::Console::Interactivity::Win32::WindowUiaProvider* const pProvider, - const Microsoft::Console::Interactivity::Win32::WindowUiaProviderTracing::ApiCall apiCall, - const Microsoft::Console::Interactivity::Win32::WindowUiaProviderTracing::IApiMsg* const apiMsg); + static void s_TraceUia(const Microsoft::Console::Types::WindowUiaProvider* const pProvider, + const Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall apiCall, + const Microsoft::Console::Types::WindowUiaProviderTracing::IApiMsg* const apiMsg); +#endif private: static ULONG s_ulDebugFlag; diff --git a/src/host/ut_host/CodepointWidthDetectorTests.cpp b/src/host/ut_host/CodepointWidthDetectorTests.cpp index ca8dd337389..f7992dc9637 100644 --- a/src/host/ut_host/CodepointWidthDetectorTests.cpp +++ b/src/host/ut_host/CodepointWidthDetectorTests.cpp @@ -32,32 +32,12 @@ class CodepointWidthDetectorTests { TEST_CLASS(CodepointWidthDetectorTests); - TEST_METHOD(CodepointWidthDetectDefersMapPopulation) - { - CodepointWidthDetector widthDetector; - VERIFY_IS_TRUE(widthDetector._map.empty()); - widthDetector.IsWide(UNICODE_SPACE); - VERIFY_IS_TRUE(widthDetector._map.empty()); - // now force checking - widthDetector.GetWidth(emoji); - VERIFY_IS_FALSE(widthDetector._map.empty()); - } - TEST_METHOD(CanLookUpEmoji) { CodepointWidthDetector widthDetector; VERIFY_IS_TRUE(widthDetector.IsWide(emoji)); } - TEST_METHOD(TestUnicodeRangeCompare) - { - CodepointWidthDetector::UnicodeRangeCompare compare; - // test comparing 2 search terms - CodepointWidthDetector::UnicodeRange a{ 0x10 }; - CodepointWidthDetector::UnicodeRange b{ 0x15 }; - VERIFY_IS_TRUE(static_cast(compare(a, b))); - } - TEST_METHOD(CanExtractCodepoint) { CodepointWidthDetector widthDetector; diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index aa56b1b4806..5cb6cd0363e 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -161,13 +161,25 @@ class ScreenBufferTests TEST_METHOD(DontResetColorsAboveVirtualBottom); + TEST_METHOD(ScrollOperations); + TEST_METHOD(InsertChars); + TEST_METHOD(DeleteChars); + TEST_METHOD(ScrollUpInMargins); TEST_METHOD(ScrollDownInMargins); TEST_METHOD(InsertLinesInMargins); TEST_METHOD(DeleteLinesInMargins); TEST_METHOD(ReverseLineFeedInMargins); + TEST_METHOD(InsertDeleteLines256Colors); + TEST_METHOD(SetOriginMode); + + TEST_METHOD(HardResetBuffer); + + TEST_METHOD(RestoreDownAltBufferWithTerminalScrolling); + + TEST_METHOD(ClearAlternateBuffer); }; void ScreenBufferTests::SingleAlternateBufferCreationTest() @@ -3051,6 +3063,507 @@ void ScreenBufferTests::DontResetColorsAboveVirtualBottom() } } +template +void _FillLine(COORD position, T fillContent, TextAttribute fillAttr) +{ + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer(); + auto& row = si.GetTextBuffer().GetRowByOffset(position.Y); + row.WriteCells({ fillContent, fillAttr }, position.X, false); +} + +template +void _FillLine(int line, T fillContent, TextAttribute fillAttr) +{ + _FillLine({ 0, gsl::narrow(line) }, fillContent, fillAttr); +} + +template +void _FillLines(int startLine, int endLine, T fillContent, TextAttribute fillAttr) +{ + for (auto line = startLine; line < endLine; ++line) + { + _FillLine(line, fillContent, fillAttr); + } +} + +template +bool _ValidateLineContains(COORD position, T expectedContent, TextAttribute expectedAttr) +{ + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer(); + auto actual = si.GetCellLineDataAt(position); + auto expected = OutputCellIterator{ expectedContent, expectedAttr }; + while (actual && expected) + { + if (actual->Chars() != expected->Chars() || actual->TextAttr() != expected->TextAttr()) + { + return false; + } + ++actual; + ++expected; + } + return true; +}; + +template +bool _ValidateLineContains(int line, T expectedContent, TextAttribute expectedAttr) +{ + return _ValidateLineContains({ 0, gsl::narrow(line) }, expectedContent, expectedAttr); +} + +template +auto _ValidateLinesContain(int startLine, int endLine, T expectedContent, TextAttribute expectedAttr) +{ + for (auto line = startLine; line < endLine; ++line) + { + if (!_ValidateLineContains(line, expectedContent, expectedAttr)) + { + return false; + } + } + return true; +}; + +void ScreenBufferTests::ScrollOperations() +{ + enum ScrollType : int + { + ScrollUp, + ScrollDown, + InsertLine, + DeleteLine, + ReverseIndex + }; + enum ScrollDirection : int + { + Up, + Down + }; + + ScrollType scrollType; + ScrollDirection scrollDirection; + int scrollMagnitude; + + BEGIN_TEST_METHOD_PROPERTIES() + TEST_METHOD_PROPERTY(L"Data:scrollType", L"{0, 1, 2, 3, 4}") + TEST_METHOD_PROPERTY(L"Data:scrollMagnitude", L"{1, 2, 5}") + END_TEST_METHOD_PROPERTIES() + + VERIFY_SUCCEEDED(TestData::TryGetValue(L"scrollType", (int&)scrollType)); + VERIFY_SUCCEEDED(TestData::TryGetValue(L"scrollMagnitude", scrollMagnitude)); + + std::wstringstream escapeSequence; + switch (scrollType) + { + case ScrollUp: + Log::Comment(L"Testing scroll up (SU)."); + escapeSequence << "\x1b[" << scrollMagnitude << "S"; + scrollDirection = Up; + break; + case ScrollDown: + Log::Comment(L"Testing scroll down (SD)."); + escapeSequence << "\x1b[" << scrollMagnitude << "T"; + scrollDirection = Down; + break; + case InsertLine: + Log::Comment(L"Testing insert line (IL)."); + escapeSequence << "\x1b[" << scrollMagnitude << "L"; + scrollDirection = Down; + break; + case DeleteLine: + Log::Comment(L"Testing delete line (DL)."); + escapeSequence << "\x1b[" << scrollMagnitude << "M"; + scrollDirection = Up; + break; + case ReverseIndex: + Log::Comment(L"Testing reverse index (RI)."); + for (auto i = 0; i < scrollMagnitude; ++i) + { + escapeSequence << "\x1bM"; + } + scrollDirection = Down; + break; + default: + VERIFY_FAIL(); + return; + } + + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer(); + auto& stateMachine = si.GetStateMachine(); + const auto& cursor = si.GetTextBuffer().GetCursor(); + WI_SetFlag(si.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING); + + const auto bufferWidth = si.GetBufferSize().Width(); + const auto bufferHeight = si.GetBufferSize().Height(); + + // Move the viewport down a few lines, and only cover part of the buffer width. + si.SetViewport(Viewport::FromDimensions({ 5, 10 }, { bufferWidth - 10, 10 }), true); + const auto viewportStart = si.GetViewport().Top(); + const auto viewportEnd = si.GetViewport().BottomExclusive(); + + // Fill the entire buffer with Zs. Blue on Green. + const auto bufferChar = L'Z'; + const auto bufferAttr = TextAttribute{ FOREGROUND_BLUE | BACKGROUND_GREEN }; + _FillLines(0, bufferHeight, bufferChar, bufferAttr); + + // Fill the viewport with a range of letters to see if they move. Red on Blue. + const auto viewportAttr = TextAttribute{ FOREGROUND_RED | BACKGROUND_BLUE }; + auto viewportChar = L'A'; + auto viewportLine = viewportStart; + while (viewportLine < viewportEnd) + { + _FillLine(viewportLine++, viewportChar++, viewportAttr); + } + + // Set the background color so that it will be used to fill the revealed area. + si.SetAttributes({ BACKGROUND_RED }); + + // Place the cursor in the center. + auto cursorPos = COORD{ bufferWidth / 2, (viewportStart + viewportEnd) / 2 }; + // Unless this is reverse index, which has to be be at the top of the viewport. + if (scrollType == ReverseIndex) + { + cursorPos.Y = viewportStart; + } + + Log::Comment(L"Set the cursor position and perform the operation."); + VERIFY_SUCCEEDED(si.SetCursorPosition(cursorPos, true)); + stateMachine.ProcessString(escapeSequence.str()); + + // The cursor shouldn't move. + auto expectedCursorPos = cursorPos; + // Unless this is an IL or DL control, which moves the cursor to the left margin. + if (scrollType == InsertLine || scrollType == DeleteLine) + { + expectedCursorPos.X = 0; + } + + Log::Comment(L"Verify expected cursor position."); + VERIFY_ARE_EQUAL(expectedCursorPos, cursor.GetPosition()); + + Log::Comment(L"Field of Zs outside viewport should remain unchanged."); + VERIFY_IS_TRUE(_ValidateLinesContain(0, viewportStart, bufferChar, bufferAttr)); + VERIFY_IS_TRUE(_ValidateLinesContain(viewportEnd, bufferHeight, bufferChar, bufferAttr)); + + // Depending on the direction of scrolling, lines are either deleted or inserted. + const auto deletedLines = scrollDirection == Up ? scrollMagnitude : 0; + const auto insertedLines = scrollDirection == Down ? scrollMagnitude : 0; + + // Insert and delete operations only scroll the viewport below the cursor position. + const auto scrollStart = (scrollType == InsertLine || scrollType == DeleteLine) ? cursorPos.Y : viewportStart; + + // Reset the viewport character and line number for the verification loop. + viewportChar = L'A'; + viewportLine = viewportStart; + + Log::Comment(L"Lines above the scrolled area should remain unchanged."); + while (viewportLine < scrollStart) + { + VERIFY_IS_TRUE(_ValidateLineContains(viewportLine++, viewportChar++, viewportAttr)); + } + + Log::Comment(L"Scrolled area should have moved up/down by given magnitude."); + viewportChar += gsl::narrow(deletedLines); // Characters dropped when deleting + viewportLine += gsl::narrow(insertedLines); // Lines skipped when inserting + while (viewportLine < viewportEnd - deletedLines) + { + VERIFY_IS_TRUE(_ValidateLineContains(viewportLine++, viewportChar++, viewportAttr)); + } + + Log::Comment(L"The revealed area should now be blank, with default buffer attributes."); + const auto revealedStart = scrollDirection == Up ? viewportEnd - deletedLines : scrollStart; + const auto revealedEnd = revealedStart + scrollMagnitude; + VERIFY_IS_TRUE(_ValidateLinesContain(revealedStart, revealedEnd, L' ', si.GetAttributes())); +} + +void ScreenBufferTests::InsertChars() +{ + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer(); + auto& stateMachine = si.GetStateMachine(); + WI_SetFlag(si.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING); + + // Set the buffer width to 40, with a centered viewport of 20. + const auto bufferWidth = 40; + const auto bufferHeight = si.GetBufferSize().Height(); + const auto viewportStart = 10; + const auto viewportEnd = viewportStart + 20; + VERIFY_SUCCEEDED(si.ResizeScreenBuffer({ bufferWidth, bufferHeight }, false)); + si.SetViewport(Viewport::FromExclusive({ viewportStart, 0, viewportEnd, 25 }), true); + + Log::Comment( + L"Test 1: Fill the line with Qs. Write some text within the viewport boundaries. " + L"Then insert 5 spaces at the cursor. Watch spaces get inserted, text slides right " + L"out of the viewport, pushing some of the Qs out of the buffer."); + + const auto insertLine = SHORT{ 10 }; + auto insertPos = SHORT{ 20 }; + + // Place the cursor in the center of the line. + VERIFY_SUCCEEDED(si.SetCursorPosition({ insertPos, insertLine }, true)); + + // Save the cursor position. It shouldn't move for the rest of the test. + const auto& cursor = si.GetTextBuffer().GetCursor(); + auto expectedCursor = cursor.GetPosition(); + + // Fill the entire line with Qs. Blue on Green. + const auto bufferChar = L'Q'; + const auto bufferAttr = TextAttribute{ FOREGROUND_BLUE | BACKGROUND_GREEN }; + _FillLine(insertLine, bufferChar, bufferAttr); + + // Fill the viewport range with text. Red on Blue. + const auto textChars = L"ABCDEFGHIJKLMNOPQRST"; + const auto textAttr = TextAttribute{ FOREGROUND_RED | BACKGROUND_BLUE }; + _FillLine({ viewportStart, insertLine }, textChars, textAttr); + + // Set the background color so that it will be used to fill the revealed area. + si.SetAttributes({ BACKGROUND_RED }); + + // Insert 5 spaces at the cursor position. + // Before: QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ + // After: QQQQQQQQQQABCDEFGHIJ KLMNOPQRSTQQQQQ + Log::Comment(L"Inserting 5 spaces in the middle of the line."); + auto before = si.GetTextBuffer().GetRowByOffset(insertLine).GetText(); + stateMachine.ProcessString(L"\x1b[5@"); + auto after = si.GetTextBuffer().GetRowByOffset(insertLine).GetText(); + Log::Comment(before.c_str(), L"Before"); + Log::Comment(after.c_str(), L" After"); + + // Verify cursor didn't move. + VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition(), L"Verify cursor didn't move from insert operation."); + + // Verify the updated structure of the line. + VERIFY_IS_TRUE(_ValidateLineContains({ 0, insertLine }, L"QQQQQQQQQQ", bufferAttr), + L"Field of Qs left of the viewport should remain unchanged."); + VERIFY_IS_TRUE(_ValidateLineContains({ viewportStart, insertLine }, L"ABCDEFGHIJ", textAttr), + L"First half of the alphabet should remain unchanged."); + VERIFY_IS_TRUE(_ValidateLineContains({ insertPos, insertLine }, L" ", si.GetAttributes()), + L"Spaces should be inserted with the current attributes at the cursor position."); + VERIFY_IS_TRUE(_ValidateLineContains({ insertPos + 5, insertLine }, L"KLMNOPQRST", textAttr), + L"Second half of the alphabet should have moved to the right by the number of spaces inserted."); + VERIFY_IS_TRUE(_ValidateLineContains({ viewportEnd + 5, insertLine }, L"QQQQQ", bufferAttr), + L"Field of Qs right of the viewport should be moved right, half pushed outside the buffer."); + + Log::Comment( + L"Test 2: Inserting at the exact end of the line. Same line structure. " + L"Move cursor to right edge of window and insert > 1 space. " + L"Only 1 should be inserted, everything else unchanged."); + + // Move cursor to right edge. + insertPos = bufferWidth - 1; + VERIFY_SUCCEEDED(si.SetCursorPosition({ insertPos, insertLine }, true)); + expectedCursor = cursor.GetPosition(); + + // Fill the entire line with Qs. Blue on Green. + _FillLine(insertLine, bufferChar, bufferAttr); + + // Fill the viewport range with text. Red on Blue. + _FillLine({ viewportStart, insertLine }, textChars, textAttr); + + // Set the background color so that it will be used to fill the revealed area. + si.SetAttributes({ BACKGROUND_RED }); + + // Insert 5 spaces at the right edge. Only 1 should be inserted. + // Before: QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ + // After: QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQ + Log::Comment(L"Inserting 5 spaces at the right edge of the buffer."); + before = si.GetTextBuffer().GetRowByOffset(insertLine).GetText(); + stateMachine.ProcessString(L"\x1b[5@"); + after = si.GetTextBuffer().GetRowByOffset(insertLine).GetText(); + Log::Comment(before.c_str(), L"Before"); + Log::Comment(after.c_str(), L" After"); + + // Verify cursor didn't move. + VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition(), L"Verify cursor didn't move from insert operation."); + + // Verify the updated structure of the line. + VERIFY_IS_TRUE(_ValidateLineContains({ 0, insertLine }, L"QQQQQQQQQQ", bufferAttr), + L"Field of Qs left of the viewport should remain unchanged."); + VERIFY_IS_TRUE(_ValidateLineContains({ viewportStart, insertLine }, L"ABCDEFGHIJKLMNOPQRST", textAttr), + L"Entire viewport range should remain unchanged."); + VERIFY_IS_TRUE(_ValidateLineContains({ viewportEnd, insertLine }, L"QQQQQQQQQ", bufferAttr), + L"Field of Qs right of the viewport should remain unchanged except for the last spot."); + VERIFY_IS_TRUE(_ValidateLineContains({ insertPos, insertLine }, L" ", si.GetAttributes()), + L"One space should be inserted with the current attributes at the cursor postion."); + + Log::Comment( + L"Test 3: Inserting at the exact beginning of the line. Same line structure. " + L"Move cursor to left edge of buffer and insert > buffer width of space. " + L"The whole row should be replaced with spaces."); + + // Move cursor to left edge. + VERIFY_SUCCEEDED(si.SetCursorPosition({ 0, insertLine }, true)); + expectedCursor = cursor.GetPosition(); + + // Fill the entire line with Qs. Blue on Green. + _FillLine(insertLine, bufferChar, bufferAttr); + + // Fill the viewport range with text. Red on Blue. + _FillLine({ viewportStart, insertLine }, textChars, textAttr); + + // Insert greater than the buffer width at the left edge. The entire line should be erased. + // Before: QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ + // After: + Log::Comment(L"Inserting 100 spaces at the left edge of the buffer."); + before = si.GetTextBuffer().GetRowByOffset(insertLine).GetText(); + stateMachine.ProcessString(L"\x1b[100@"); + after = si.GetTextBuffer().GetRowByOffset(insertLine).GetText(); + Log::Comment(before.c_str(), L"Before"); + Log::Comment(after.c_str(), L" After"); + + // Verify cursor didn't move. + VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition(), L"Verify cursor didn't move from insert operation."); + + // Verify the updated structure of the line. + VERIFY_IS_TRUE(_ValidateLineContains(insertLine, L' ', si.GetAttributes()), + L"A whole line of spaces was inserted at the start, erasing the line."); +} + +void ScreenBufferTests::DeleteChars() +{ + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer(); + auto& stateMachine = si.GetStateMachine(); + WI_SetFlag(si.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING); + + // Set the buffer width to 40, with a centered viewport of 20. + const auto bufferWidth = 40; + const auto bufferHeight = si.GetBufferSize().Height(); + const auto viewportStart = 10; + const auto viewportEnd = viewportStart + 20; + VERIFY_SUCCEEDED(si.ResizeScreenBuffer({ bufferWidth, bufferHeight }, false)); + si.SetViewport(Viewport::FromExclusive({ viewportStart, 0, viewportEnd, 25 }), true); + + Log::Comment( + L"Test 1: Fill the line with Qs. Write some text within the viewport boundaries. " + L"Then delete 5 characters at the cursor. Watch the rest of the line slide left, " + L"replacing the deleted characters, with spaces inserted at the end of the line."); + + const auto deleteLine = SHORT{ 10 }; + auto deletePos = SHORT{ 20 }; + + // Place the cursor in the center of the line. + VERIFY_SUCCEEDED(si.SetCursorPosition({ deletePos, deleteLine }, true)); + + // Save the cursor position. It shouldn't move for the rest of the test. + const auto& cursor = si.GetTextBuffer().GetCursor(); + auto expectedCursor = cursor.GetPosition(); + + // Fill the entire line with Qs. Blue on Green. + const auto bufferChar = L'Q'; + const auto bufferAttr = TextAttribute{ FOREGROUND_BLUE | BACKGROUND_GREEN }; + _FillLine(deleteLine, bufferChar, bufferAttr); + + // Fill the viewport range with text. Red on Blue. + const auto textChars = L"ABCDEFGHIJKLMNOPQRST"; + const auto textAttr = TextAttribute{ FOREGROUND_RED | BACKGROUND_BLUE }; + _FillLine({ viewportStart, deleteLine }, textChars, textAttr); + + // Set the background color so that it will be used to fill the revealed area. + si.SetAttributes({ BACKGROUND_RED }); + + // Delete 5 characters at the cursor position. + // Before: QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ + // After: QQQQQQQQQQABCDEFGHIJPQRSTQQQQQQQQQQ + Log::Comment(L"Deleting 5 characters in the middle of the line."); + auto before = si.GetTextBuffer().GetRowByOffset(deleteLine).GetText(); + stateMachine.ProcessString(L"\x1b[5P"); + auto after = si.GetTextBuffer().GetRowByOffset(deleteLine).GetText(); + Log::Comment(before.c_str(), L"Before"); + Log::Comment(after.c_str(), L" After"); + + // Verify cursor didn't move. + VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition(), L"Verify cursor didn't move from delete operation."); + + // Verify the updated structure of the line. + VERIFY_IS_TRUE(_ValidateLineContains({ 0, deleteLine }, L"QQQQQQQQQQ", bufferAttr), + L"Field of Qs left of the viewport should remain unchanged."); + VERIFY_IS_TRUE(_ValidateLineContains({ viewportStart, deleteLine }, L"ABCDEFGHIJ", textAttr), + L"First half of the alphabet should remain unchanged."); + VERIFY_IS_TRUE(_ValidateLineContains({ deletePos, deleteLine }, L"PQRST", textAttr), + L"Only half of the second part of the alphabet remains."); + VERIFY_IS_TRUE(_ValidateLineContains({ viewportEnd - 5, deleteLine }, L"QQQQQQQQQQ", bufferAttr), + L"Field of Qs right of the viewport should be moved left."); + VERIFY_IS_TRUE(_ValidateLineContains({ bufferWidth - 5, deleteLine }, L" ", si.GetAttributes()), + L"The rest of the line should be replaced with spaces with the current attributes."); + + Log::Comment( + L"Test 2: Deleting at the exact end of the line. Same line structure. " + L"Move cursor to right edge of window and delete > 1 character. " + L"Only 1 should be deleted, everything else unchanged."); + + // Move cursor to right edge. + deletePos = bufferWidth - 1; + VERIFY_SUCCEEDED(si.SetCursorPosition({ deletePos, deleteLine }, true)); + expectedCursor = cursor.GetPosition(); + + // Fill the entire line with Qs. Blue on Green. + _FillLine(deleteLine, bufferChar, bufferAttr); + + // Fill the viewport range with text. Red on Blue. + _FillLine({ viewportStart, deleteLine }, textChars, textAttr); + + // Set the background color so that it will be used to fill the revealed area. + si.SetAttributes({ BACKGROUND_RED }); + + // Delete 5 characters at the right edge. Only 1 should be deleted. + // Before: QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ + // After: QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQ + Log::Comment(L"Deleting 5 characters at the right edge of the buffer."); + before = si.GetTextBuffer().GetRowByOffset(deleteLine).GetText(); + stateMachine.ProcessString(L"\x1b[5P"); + after = si.GetTextBuffer().GetRowByOffset(deleteLine).GetText(); + Log::Comment(before.c_str(), L"Before"); + Log::Comment(after.c_str(), L" After"); + + // Verify cursor didn't move. + VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition(), L"Verify cursor didn't move from delete operation."); + + // Verify the updated structure of the line. + VERIFY_IS_TRUE(_ValidateLineContains({ 0, deleteLine }, L"QQQQQQQQQQ", bufferAttr), + L"Field of Qs left of the viewport should remain unchanged."); + VERIFY_IS_TRUE(_ValidateLineContains({ viewportStart, deleteLine }, L"ABCDEFGHIJKLMNOPQRST", textAttr), + L"Entire viewport range should remain unchanged."); + VERIFY_IS_TRUE(_ValidateLineContains({ viewportEnd, deleteLine }, L"QQQQQQQQQ", bufferAttr), + L"Field of Qs right of the viewport should remain unchanged except for the last spot."); + VERIFY_IS_TRUE(_ValidateLineContains({ deletePos, deleteLine }, L" ", si.GetAttributes()), + L"One character should be erased with the current attributes at the cursor postion."); + + Log::Comment( + L"Test 3: Deleting at the exact beginning of the line. Same line structure. " + L"Move cursor to left edge of buffer and delete > buffer width of characters. " + L"The whole row should be replaced with spaces."); + + // Move cursor to left edge. + VERIFY_SUCCEEDED(si.SetCursorPosition({ 0, deleteLine }, true)); + expectedCursor = cursor.GetPosition(); + + // Fill the entire line with Qs. Blue on Green. + _FillLine(deleteLine, bufferChar, bufferAttr); + + // Fill the viewport range with text. Red on Blue. + _FillLine({ viewportStart, deleteLine }, textChars, textAttr); + + // Delete greater than the buffer width at the left edge. The entire line should be erased. + // Before: QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ + // After: + Log::Comment(L"Deleting 100 characters at the left edge of the buffer."); + before = si.GetTextBuffer().GetRowByOffset(deleteLine).GetText(); + stateMachine.ProcessString(L"\x1b[100P"); + after = si.GetTextBuffer().GetRowByOffset(deleteLine).GetText(); + Log::Comment(before.c_str(), L"Before"); + Log::Comment(after.c_str(), L" After"); + + // Verify cursor didn't move. + VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition(), L"Verify cursor didn't move from delete operation."); + + // Verify the updated structure of the line. + VERIFY_IS_TRUE(_ValidateLineContains(deleteLine, L' ', si.GetAttributes()), + L"A whole line of spaces was inserted from the right, erasing the line."); +} + void _CommonScrollingSetup() { // Used for testing MSFT:20204600 @@ -3246,7 +3759,8 @@ void ScreenBufferTests::InsertLinesInMargins() Log::Comment(NoThrowString().Format( L"viewport=%s", VerifyOutputTraits::ToString(si.GetViewport().ToInclusive()).GetBuffer())); - VERIFY_ARE_EQUAL(4, cursor.GetPosition().X); + // Verify cursor moved to left margin. + VERIFY_ARE_EQUAL(0, cursor.GetPosition().X); VERIFY_ARE_EQUAL(2, cursor.GetPosition().Y); { auto iter0 = tbi.GetCellDataAt({ 0, 0 }); @@ -3280,7 +3794,8 @@ void ScreenBufferTests::InsertLinesInMargins() Log::Comment(NoThrowString().Format( L"viewport=%s", VerifyOutputTraits::ToString(si.GetViewport().ToInclusive()).GetBuffer())); - VERIFY_ARE_EQUAL(4, cursor.GetPosition().X); + // Verify cursor moved to left margin. + VERIFY_ARE_EQUAL(0, cursor.GetPosition().X); VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y); { auto iter0 = tbi.GetCellDataAt({ 0, 0 }); @@ -3321,7 +3836,8 @@ void ScreenBufferTests::DeleteLinesInMargins() Log::Comment(NoThrowString().Format( L"viewport=%s", VerifyOutputTraits::ToString(si.GetViewport().ToInclusive()).GetBuffer())); - VERIFY_ARE_EQUAL(4, cursor.GetPosition().X); + // Verify cursor moved to left margin. + VERIFY_ARE_EQUAL(0, cursor.GetPosition().X); VERIFY_ARE_EQUAL(2, cursor.GetPosition().Y); { auto iter0 = tbi.GetCellDataAt({ 0, 0 }); @@ -3355,7 +3871,8 @@ void ScreenBufferTests::DeleteLinesInMargins() Log::Comment(NoThrowString().Format( L"viewport=%s", VerifyOutputTraits::ToString(si.GetViewport().ToInclusive()).GetBuffer())); - VERIFY_ARE_EQUAL(4, cursor.GetPosition().X); + // Verify cursor moved to left margin. + VERIFY_ARE_EQUAL(0, cursor.GetPosition().X); VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y); { auto iter0 = tbi.GetCellDataAt({ 0, 0 }); @@ -3449,6 +3966,103 @@ void ScreenBufferTests::ReverseLineFeedInMargins() } } +void ScreenBufferTests::InsertDeleteLines256Colors() +{ + BEGIN_TEST_METHOD_PROPERTIES() + TEST_METHOD_PROPERTY(L"Data:insert", L"{false, true}") + TEST_METHOD_PROPERTY(L"Data:colorStyle", L"{0, 1, 2}") + END_TEST_METHOD_PROPERTIES(); + + // colorStyle will be used to control whether we use a color from the 16 + // color table, a color from the 256 color table, or a pure RGB color. + const int Use16Color = 0; + const int Use256Color = 1; + const int UseRGBColor = 2; + + bool insert; + int colorStyle; + VERIFY_SUCCEEDED(TestData::TryGetValue(L"insert", insert), L"whether to insert(true) or delete(false) lines"); + VERIFY_SUCCEEDED(TestData::TryGetValue(L"colorStyle", colorStyle), L"controls whether to use the 16 color table, 256 table, or RGB colors"); + + // This test is largely taken from repro code from + // https://github.com/microsoft/terminal/issues/832#issuecomment-507447272 + Log::Comment( + L"Sets the attributes to a 256/RGB color, then scrolls some lines with" + L" DL. Verifies the rows are cleared with the attributes we'd expect."); + + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer(); + auto& tbi = si.GetTextBuffer(); + auto& stateMachine = si.GetStateMachine(); + auto& cursor = si.GetTextBuffer().GetCursor(); + + TextAttribute expectedAttr{ si.GetAttributes() }; + std::wstring sgrSeq = L"\x1b[48;5;2m"; + if (colorStyle == Use16Color) + { + expectedAttr.SetBackground(gci.GetColorTableEntry(2)); + } + else if (colorStyle == Use256Color) + { + expectedAttr.SetBackground(gci.GetColorTableEntry(20)); + sgrSeq = L"\x1b[48;5;20m"; + } + else if (colorStyle == UseRGBColor) + { + expectedAttr.SetBackground(RGB(1, 2, 3)); + sgrSeq = L"\x1b[48;2;1;2;3m"; + } + + // Set some scrolling margins + stateMachine.ProcessString(L"\x1b[1;3r"); + + // Set the BG color to the table index 2, as a 256-color sequence + stateMachine.ProcessString(sgrSeq); + + VERIFY_ARE_EQUAL(expectedAttr, si.GetAttributes()); + + // Move to home + stateMachine.ProcessString(L"\x1b[H"); + + // Insert/Delete 10 lines + stateMachine.ProcessString(insert ? L"\x1b[10L" : L"\x1b[10M"); + + Log::Comment(NoThrowString().Format( + L"cursor=%s", VerifyOutputTraits::ToString(cursor.GetPosition()).GetBuffer())); + Log::Comment(NoThrowString().Format( + L"viewport=%s", VerifyOutputTraits::ToString(si.GetViewport().ToInclusive()).GetBuffer())); + + VERIFY_ARE_EQUAL(0, cursor.GetPosition().X); + VERIFY_ARE_EQUAL(0, cursor.GetPosition().Y); + + stateMachine.ProcessString(L"foo"); + Log::Comment(NoThrowString().Format( + L"cursor=%s", VerifyOutputTraits::ToString(cursor.GetPosition()).GetBuffer())); + VERIFY_ARE_EQUAL(3, cursor.GetPosition().X); + VERIFY_ARE_EQUAL(0, cursor.GetPosition().Y); + { + auto iter00 = tbi.GetCellDataAt({ 0, 0 }); + auto iter10 = tbi.GetCellDataAt({ 1, 0 }); + auto iter20 = tbi.GetCellDataAt({ 2, 0 }); + auto iter30 = tbi.GetCellDataAt({ 3, 0 }); + auto iter01 = tbi.GetCellDataAt({ 0, 1 }); + auto iter02 = tbi.GetCellDataAt({ 0, 2 }); + VERIFY_ARE_EQUAL(L"f", iter00->Chars()); + VERIFY_ARE_EQUAL(L"o", iter10->Chars()); + VERIFY_ARE_EQUAL(L"o", iter20->Chars()); + VERIFY_ARE_EQUAL(L"\x20", iter30->Chars()); + VERIFY_ARE_EQUAL(L"\x20", iter01->Chars()); + VERIFY_ARE_EQUAL(L"\x20", iter02->Chars()); + + VERIFY_ARE_EQUAL(expectedAttr, iter00->TextAttr()); + VERIFY_ARE_EQUAL(expectedAttr, iter10->TextAttr()); + VERIFY_ARE_EQUAL(expectedAttr, iter20->TextAttr()); + VERIFY_ARE_EQUAL(expectedAttr, iter30->TextAttr()); + VERIFY_ARE_EQUAL(expectedAttr, iter01->TextAttr()); + VERIFY_ARE_EQUAL(expectedAttr, iter02->TextAttr()); + } +} + void ScreenBufferTests::SetOriginMode() { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); @@ -3516,3 +4130,220 @@ void ScreenBufferTests::SetOriginMode() // Reset DECOM so we don't affect future tests stateMachine.ProcessString(L"\x1B[?6l"); } + +void ScreenBufferTests::HardResetBuffer() +{ + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer(); + auto& stateMachine = si.GetStateMachine(); + const auto& viewport = si.GetViewport(); + const auto& cursor = si.GetTextBuffer().GetCursor(); + WI_SetFlag(si.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING); + + auto isBufferClear = [&]() { + auto offset = 0; + auto width = si.GetBufferSize().Width(); + for (auto iter = si.GetCellDataAt({}); iter; ++iter, ++offset) + { + if (iter->Chars() != L" " || iter->TextAttr() != TextAttribute{}) + { + Log::Comment(NoThrowString().Format( + L"Buffer not clear at (X:%d, Y:%d)", + offset % width, + offset / width)); + return false; + } + } + return true; + }; + + const auto resetToInitialState = L"\033c"; + + Log::Comment(L"Start with a clear buffer, viewport and cursor at 0,0"); + si.SetAttributes(TextAttribute()); + si.ClearTextData(); + VERIFY_SUCCEEDED(si.SetViewportOrigin(true, { 0, 0 }, true)); + VERIFY_SUCCEEDED(si.SetCursorPosition({ 0, 0 }, true)); + VERIFY_IS_TRUE(isBufferClear()); + + Log::Comment(L"Write a single line of text to the buffer"); + stateMachine.ProcessString(L"Hello World!\n"); + VERIFY_IS_FALSE(isBufferClear()); + VERIFY_ARE_EQUAL(COORD({ 0, 1 }), cursor.GetPosition()); + + Log::Comment(L"After a reset, buffer should be clear, with cursor at 0,0"); + stateMachine.ProcessString(resetToInitialState); + VERIFY_IS_TRUE(isBufferClear()); + VERIFY_ARE_EQUAL(COORD({ 0, 0 }), cursor.GetPosition()); + + Log::Comment(L"Set the background color to red"); + stateMachine.ProcessString(L"\x1b[41m"); + Log::Comment(L"Write multiple pages of text to the buffer"); + for (auto i = 0; i < viewport.Height() * 2; i++) + { + stateMachine.ProcessString(L"Hello World!\n"); + } + VERIFY_IS_FALSE(isBufferClear()); + VERIFY_IS_GREATER_THAN(viewport.Top(), viewport.Height()); + VERIFY_IS_GREATER_THAN(cursor.GetPosition().Y, viewport.Height()); + + Log::Comment(L"After a reset, buffer should be clear, with viewport and cursor at 0,0"); + stateMachine.ProcessString(resetToInitialState); + VERIFY_IS_TRUE(isBufferClear()); + VERIFY_ARE_EQUAL(COORD({ 0, 0 }), viewport.Origin()); + VERIFY_ARE_EQUAL(COORD({ 0, 0 }), cursor.GetPosition()); +} + +void ScreenBufferTests::RestoreDownAltBufferWithTerminalScrolling() +{ + // This is a test for microsoft/terminal#1206. Refer to that issue for more + // context + + CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + gci.SetTerminalScrolling(true); + gci.LockConsole(); // Lock must be taken to manipulate buffer. + auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); + + auto& siMain = gci.GetActiveOutputBuffer(); + COORD const coordFontSize = siMain.GetScreenFontSize(); + siMain._virtualBottom = siMain._viewport.BottomInclusive(); + + auto originalView = siMain._viewport; + + VERIFY_IS_NULL(siMain._psiMainBuffer); + VERIFY_IS_NULL(siMain._psiAlternateBuffer); + + Log::Comment(L"Create an alternate buffer"); + if (VERIFY_IS_TRUE(NT_SUCCESS(siMain.UseAlternateScreenBuffer()))) + { + VERIFY_IS_NOT_NULL(siMain._psiAlternateBuffer); + auto& altBuffer = *siMain._psiAlternateBuffer; + VERIFY_ARE_EQUAL(0, altBuffer._viewport.Top()); + VERIFY_ARE_EQUAL(altBuffer._viewport.BottomInclusive(), altBuffer._virtualBottom); + + auto useMain = wil::scope_exit([&] { altBuffer.UseMainScreenBuffer(); }); + + const COORD originalSize = originalView.Dimensions(); + const COORD doubledSize = { originalSize.X * 2, originalSize.Y * 2 }; + + // Create some RECTs, which are dimensions in pixels, because + // ProcessResizeWindow needs to work on rects in screen _pixel_ + // dimensions, not character sizes. + RECT originalClientRect{ 0 }, maximizedClientRect{ 0 }; + + originalClientRect.right = originalSize.X * coordFontSize.X; + originalClientRect.bottom = originalSize.Y * coordFontSize.Y; + + maximizedClientRect.right = doubledSize.X * coordFontSize.X; + maximizedClientRect.bottom = doubledSize.Y * coordFontSize.Y; + + Log::Comment(NoThrowString().Format( + L"Emulate a maximize")); + // Note that just calling _InternalSetViewportSize does not hit the + // exceptional case here. There's other logic farther down the stack + // that triggers it. + altBuffer.ProcessResizeWindow(&maximizedClientRect, &originalClientRect); + + VERIFY_ARE_EQUAL(0, altBuffer._viewport.Top()); + VERIFY_ARE_EQUAL(altBuffer._viewport.BottomInclusive(), altBuffer._virtualBottom); + + Log::Comment(NoThrowString().Format( + L"Emulate a restore down")); + + altBuffer.ProcessResizeWindow(&originalClientRect, &maximizedClientRect); + + // Before the bugfix, this would fail, with the top being roughly 80, + // halfway into the buffer, with the bottom being anchored to the old + // size. + VERIFY_ARE_EQUAL(0, altBuffer._viewport.Top()); + VERIFY_ARE_EQUAL(altBuffer._viewport.BottomInclusive(), altBuffer._virtualBottom); + } +} + +void ScreenBufferTests::ClearAlternateBuffer() +{ + // This is a test for microsoft/terminal#1189. Refer to that issue for more + // context + + CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto& g = ServiceLocator::LocateGlobals(); + gci.LockConsole(); // Lock must be taken to manipulate buffer. + auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); + + auto& siMain = gci.GetActiveOutputBuffer(); + auto WriteText = [&](TextBuffer& tbi) { + // Write text to buffer + auto& stateMachine = siMain.GetStateMachine(); + auto& cursor = tbi.GetCursor(); + stateMachine.ProcessString(L"foo\nfoo"); + VERIFY_ARE_EQUAL(cursor.GetPosition().X, 3); + VERIFY_ARE_EQUAL(cursor.GetPosition().Y, 1); + }; + + auto VerifyText = [&](TextBuffer& tbi) { + // Verify written text in buffer + { + auto iter00 = tbi.GetCellDataAt({ 0, 0 }); + auto iter10 = tbi.GetCellDataAt({ 1, 0 }); + auto iter20 = tbi.GetCellDataAt({ 2, 0 }); + auto iter30 = tbi.GetCellDataAt({ 3, 0 }); + auto iter01 = tbi.GetCellDataAt({ 0, 1 }); + auto iter02 = tbi.GetCellDataAt({ 1, 1 }); + auto iter03 = tbi.GetCellDataAt({ 2, 1 }); + VERIFY_ARE_EQUAL(L"f", iter00->Chars()); + VERIFY_ARE_EQUAL(L"o", iter10->Chars()); + VERIFY_ARE_EQUAL(L"o", iter20->Chars()); + VERIFY_ARE_EQUAL(L"\x20", iter30->Chars()); + VERIFY_ARE_EQUAL(L"f", iter01->Chars()); + VERIFY_ARE_EQUAL(L"o", iter02->Chars()); + VERIFY_ARE_EQUAL(L"o", iter03->Chars()); + } + }; + + WriteText(siMain.GetTextBuffer()); + VerifyText(siMain.GetTextBuffer()); + + Log::Comment(L"Create an alternate buffer"); + if (VERIFY_IS_TRUE(NT_SUCCESS(siMain.UseAlternateScreenBuffer()))) + { + VERIFY_IS_NOT_NULL(siMain._psiAlternateBuffer); + auto& altBuffer = *siMain._psiAlternateBuffer; + VERIFY_ARE_EQUAL(0, altBuffer._viewport.Top()); + VERIFY_ARE_EQUAL(altBuffer._viewport.BottomInclusive(), altBuffer._virtualBottom); + + auto useMain = wil::scope_exit([&] { altBuffer.UseMainScreenBuffer(); }); + + WriteText(altBuffer.GetTextBuffer()); + VerifyText(altBuffer.GetTextBuffer()); + +#pragma region Test ScrollConsoleScreenBufferWImpl() + // Clear text of alt buffer (same params as in CMD) + VERIFY_SUCCEEDED(g.api.ScrollConsoleScreenBufferWImpl(siMain, + { 0, 0, 120, 9001 }, + { 0, -9001 }, + std::nullopt, + L' ', + 7)); + + // Verify text is now gone + VERIFY_ARE_EQUAL(L" ", altBuffer.GetTextBuffer().GetCellDataAt({ 0, 0 })->Chars()); +#pragma endregion + +#pragma region Test SetConsoleCursorPositionImpl() + // Reset cursor position as we do with CLS command (same params as in CMD) + VERIFY_SUCCEEDED(g.api.SetConsoleCursorPositionImpl(siMain, { 0 })); + + // Verify state of alt buffer + auto& altBufferCursor = altBuffer.GetTextBuffer().GetCursor(); + VERIFY_ARE_EQUAL(altBufferCursor.GetPosition().X, 0); + VERIFY_ARE_EQUAL(altBufferCursor.GetPosition().Y, 0); +#pragma endregion + } + + // Verify state of main buffer is untouched + auto& cursor = siMain.GetTextBuffer().GetCursor(); + VERIFY_ARE_EQUAL(cursor.GetPosition().X, 3); + VERIFY_ARE_EQUAL(cursor.GetPosition().Y, 1); + + VerifyText(siMain.GetTextBuffer()); +} diff --git a/src/host/ut_host/ViewportTests.cpp b/src/host/ut_host/ViewportTests.cpp index d82028d9a53..c5bdc3aa9e6 100644 --- a/src/host/ut_host/ViewportTests.cpp +++ b/src/host/ut_host/ViewportTests.cpp @@ -1044,18 +1044,8 @@ class ViewportTests const auto original = Viewport::FromInclusive(srOriginal); const auto remove = original; - std::vector expected; - expected.emplace_back(Viewport::FromDimensions(original.Origin(), { 0, 0 })); - const auto actual = Viewport::Subtract(original, remove); - VERIFY_ARE_EQUAL(expected.size(), actual.size(), L"Same number of viewports in expected and actual"); - Log::Comment(L"Now validate that each viewport has the expected area."); - for (size_t i = 0; i < expected.size(); i++) - { - const auto& exp = expected.at(i); - const auto& act = actual.at(i); - VERIFY_ARE_EQUAL(exp, act); - } + VERIFY_ARE_EQUAL(0u, actual.size(), L"There should be no viewports returned"); } }; diff --git a/src/inc/DefaultSettings.h b/src/inc/DefaultSettings.h index 99ae30aed78..ed6d8f2a2f7 100644 --- a/src/inc/DefaultSettings.h +++ b/src/inc/DefaultSettings.h @@ -26,16 +26,21 @@ constexpr COLORREF DEFAULT_BACKGROUND_WITH_ALPHA = OPACITY_OPAQUE | DEFAULT_BACK constexpr COLORREF POWERSHELL_BLUE = RGB(1, 36, 86); constexpr short DEFAULT_HISTORY_SIZE = 9001; + +#pragma warning(push) +#pragma warning(disable : 26426) +// TODO GH 2674, don't disable this warning, move to std::wstring_view or something like that. const std::wstring DEFAULT_FONT_FACE{ L"Consolas" }; -constexpr int DEFAULT_FONT_SIZE = 10; +constexpr int DEFAULT_FONT_SIZE = 12; constexpr int DEFAULT_ROWS = 30; constexpr int DEFAULT_COLS = 120; -const std::wstring DEFAULT_PADDING{ L"0, 0, 0, 0" }; +const std::wstring DEFAULT_PADDING{ L"8, 8, 8, 8" }; const std::wstring DEFAULT_STARTING_DIRECTORY{ L"%USERPROFILE%" }; constexpr COLORREF DEFAULT_CURSOR_COLOR = COLOR_WHITE; constexpr COLORREF DEFAULT_CURSOR_HEIGHT = 25; const std::wstring DEFAULT_WORD_DELIMITERS{ L" ./\\()\"'-:,.;<>~!@#$%^&*|+=[]{}~?\u2502" }; +#pragma warning(pop) diff --git a/src/inc/LibraryIncludes.h b/src/inc/LibraryIncludes.h index eb4ae8db0b7..d50e2e16394 100644 --- a/src/inc/LibraryIncludes.h +++ b/src/inc/LibraryIncludes.h @@ -43,6 +43,7 @@ #include #include #include +#include // WIL #include diff --git a/src/inc/conpty-universal.h b/src/inc/conpty-universal.h index 5d6031fd79b..9fc8742d771 100644 --- a/src/inc/conpty-universal.h +++ b/src/inc/conpty-universal.h @@ -206,6 +206,9 @@ bool SignalResizeWindow(const HANDLE hSignal, // - hSignal: A handle to the pipe for writing signal messages to the pty. // - piPty: The PROCESS_INFORMATION of the pty process. NOTE: This is *not* the // PROCESS_INFORMATION of the process that's created as a result the cmdline. +// - startupInfo : A STARTUPINFO struct to use as additional information to pass +// into the created conhost process. Conhost may pass some of the properties +// in this struct to its child process, notably, the lpTitle. // - extraEnvVars : A map of pairs of (Name, Value) representing additional // environment variable strings and values to be set in the client process // environment. May override any already present in parent process. @@ -221,6 +224,7 @@ bool SignalResizeWindow(const HANDLE hSignal, HANDLE* const hSignal, PROCESS_INFORMATION* const piPty, DWORD dwCreationFlags = 0, + const STARTUPINFO startupInfo = { 0 }, const EnvironmentVariableMapW& extraEnvVars = {}) noexcept { // Create some anon pipes so we can pass handles down and into the console. @@ -268,7 +272,7 @@ bool SignalResizeWindow(const HANDLE hSignal, conhostCmdline += L" -- "; conhostCmdline += cmdline; - STARTUPINFO si = { 0 }; + STARTUPINFO si = startupInfo; si.cb = sizeof(STARTUPINFOW); si.hStdInput = inPipeConhostSide; si.hStdOutput = outPipeConhostSide; diff --git a/src/interactivity/base/ServiceLocator.cpp b/src/interactivity/base/ServiceLocator.cpp index 858c25a027a..ef8411c5389 100644 --- a/src/interactivity/base/ServiceLocator.cpp +++ b/src/interactivity/base/ServiceLocator.cpp @@ -9,6 +9,7 @@ #pragma hdrstop +using namespace Microsoft::Console::Types; using namespace Microsoft::Console::Interactivity; #pragma region Private Static Member Initialization diff --git a/src/interactivity/base/lib/InteractivityBase.vcxproj b/src/interactivity/base/lib/InteractivityBase.vcxproj index f8d69520075..57075e72c8c 100644 --- a/src/interactivity/base/lib/InteractivityBase.vcxproj +++ b/src/interactivity/base/lib/InteractivityBase.vcxproj @@ -23,7 +23,6 @@ - diff --git a/src/interactivity/base/lib/InteractivityBase.vcxproj.filters b/src/interactivity/base/lib/InteractivityBase.vcxproj.filters index a47641e36e4..0266e68564b 100644 --- a/src/interactivity/base/lib/InteractivityBase.vcxproj.filters +++ b/src/interactivity/base/lib/InteractivityBase.vcxproj.filters @@ -50,9 +50,6 @@ Header Files - - Header Files - Header Files @@ -75,4 +72,7 @@ Header Files + + + \ No newline at end of file diff --git a/src/interactivity/inc/ServiceLocator.hpp b/src/interactivity/inc/ServiceLocator.hpp index fcd1f0936cd..d81d77d666b 100644 --- a/src/interactivity/inc/ServiceLocator.hpp +++ b/src/interactivity/inc/ServiceLocator.hpp @@ -16,13 +16,15 @@ Author(s): #pragma once #include "IInteractivityFactory.hpp" -#include "IConsoleWindow.hpp" +#include "../types/IConsoleWindow.hpp" #include "../../host/globals.h" #include #pragma hdrstop +using namespace Microsoft::Console::Types; + namespace Microsoft::Console::Interactivity { class ServiceLocator final diff --git a/src/interactivity/win32/AccessibilityNotifier.cpp b/src/interactivity/win32/AccessibilityNotifier.cpp index a6d99b18203..e7eeaaca6f0 100644 --- a/src/interactivity/win32/AccessibilityNotifier.cpp +++ b/src/interactivity/win32/AccessibilityNotifier.cpp @@ -8,6 +8,7 @@ #include "..\inc\ServiceLocator.hpp" #include "ConsoleControl.hpp" +using namespace Microsoft::Console::Types; using namespace Microsoft::Console::Interactivity::Win32; void AccessibilityNotifier::NotifyConsoleCaretEvent(_In_ RECT rectangle) diff --git a/src/interactivity/win32/Clipboard.cpp b/src/interactivity/win32/Clipboard.cpp index 874afd37dd7..54a48b4048a 100644 --- a/src/interactivity/win32/Clipboard.cpp +++ b/src/interactivity/win32/Clipboard.cpp @@ -239,246 +239,6 @@ TextBuffer::TextAndColor Clipboard::RetrieveTextFromBuffer(const SCREEN_INFORMAT GetBackgroundColor); } -// Routine Description: -// - Generates a CF_HTML compliant structure based on the passed in text and color data -// Arguments: -// - rows - the text and color data we will format & encapsulate -// Return Value: -// - string containing the generated HTML -std::string Clipboard::GenHTML(const TextBuffer::TextAndColor& rows) -{ - std::string szClipboard; // we will build the data going back in this string buffer - - try - { - std::string const szHtmlClipFormat = - "Version:0.9\r\n" - "StartHTML:%010d\r\n" - "EndHTML:%010d\r\n" - "StartFragment:%010d\r\n" - "EndFragment:%010d\r\n" - "StartSelection:%010d\r\n" - "EndSelection:%010d\r\n"; - - // measure clip header - size_t const cbHeader = 157; // when formats are expanded, there will be 157 bytes in the header. - - std::string const szHtmlHeader = - "Windows Console Host"; - size_t const cbHtmlHeader = szHtmlHeader.size(); - - std::string const szHtmlFragStart = ""; - std::string const szHtmlFragEnd = ""; - std::string const szHtmlFooter = ""; - size_t const cbHtmlFooter = szHtmlFooter.size(); - - std::string const szDivOuterBackgroundPattern = R"X(
)X"; - - size_t const cbDivOuter = 55; - std::string szDivOuter; - szDivOuter.reserve(cbDivOuter); - - std::string const szSpanFontSizePattern = R"X()X"; - - const auto& fontData = ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer().GetCurrentFont(); - int const iFontHeightPoints = fontData.GetUnscaledSize().Y * 72 / ServiceLocator::LocateGlobals().dpi; - size_t const cbSpanFontSize = 28 + (iFontHeightPoints / 10) + 1; - - std::string szSpanFontSize; - szSpanFontSize.resize(cbSpanFontSize + 1); // reserve space for null after string for sprintf - sprintf_s(szSpanFontSize.data(), cbSpanFontSize + 1, szSpanFontSizePattern.data(), iFontHeightPoints); - szSpanFontSize.resize(cbSpanFontSize); //chop off null at end - - std::string const szSpanStartPattern = R"X()X"; - - size_t const cbSpanStart = 53; // when format is expanded, there will be 53 bytes per color pattern. - std::string szSpanStart; - szSpanStart.resize(cbSpanStart + 1); // +1 for null terminator - - std::string const szSpanStartFontPattern = R"X()X"; - size_t const cbSpanStartFontPattern = 41; - - std::string const szSpanStartFontConstant = R"X()X"; - size_t const cbSpanStartFontConstant = 37; - - std::string szSpanStartFont; - size_t cbSpanStartFont; - bool fDeleteSpanStartFont = false; - - std::wstring const wszFontFaceName = fontData.GetFaceName(); - size_t const cchFontFaceName = wszFontFaceName.size(); - if (cchFontFaceName > 0) - { - // measure and create buffer to convert face name to UTF8 - int const cbNeeded = WideCharToMultiByte(CP_UTF8, 0, wszFontFaceName.data(), static_cast(cchFontFaceName), nullptr, 0, nullptr, nullptr); - std::string szBuffer; - szBuffer.resize(cbNeeded); - - // do conversion - WideCharToMultiByte(CP_UTF8, 0, wszFontFaceName.data(), static_cast(cchFontFaceName), szBuffer.data(), cbNeeded, nullptr, nullptr); - - // format converted font name into pattern - std::string const szFinalFontPattern = R"X()X"; - size_t const cbBytesNeeded = szFinalFontPattern.length(); - - fDeleteSpanStartFont = true; - szSpanStartFont = szFinalFontPattern; - cbSpanStartFont = cbBytesNeeded; - } - else - { - szSpanStartFont = szSpanStartFontConstant; - cbSpanStartFont = cbSpanStartFontConstant; - } - - std::string const szSpanEnd = ""; - std::string const szDivEnd = "
"; - - // Start building the HTML formated string to return - // First we have to add the required header and then - // some standard HTML boiler plate required for CF_HTML - // as part of the HTML Clipboard format - szClipboard.append(cbHeader, 'H'); // reserve space for a header we fill in later - szClipboard.append(szHtmlHeader); - szClipboard.append(szHtmlFragStart); - - COLORREF iBgColor = rows.BkAttr.at(0).at(0); - - szDivOuter.resize(cbDivOuter + 1); - sprintf_s(szDivOuter.data(), cbDivOuter + 1, szDivOuterBackgroundPattern.data(), GetRValue(iBgColor), GetGValue(iBgColor), GetBValue(iBgColor)); - szDivOuter.resize(cbDivOuter); - szClipboard.append(szDivOuter); - - // copy font face start - szClipboard.append(szSpanStartFont); - - // copy font size start - szClipboard.append(szSpanFontSize); - - bool bColorFound = false; - - // copy all text into the final clipboard data handle. There should be no nulls between rows of - // characters, but there should be a \0 at the end. - for (UINT iRow = 0; iRow < rows.text.size(); iRow++) - { - size_t cbStartOffset = 0; - size_t cchCharsToPrint = 0; - - COLORREF const Blackness = RGB(0x00, 0x00, 0x00); - COLORREF fgColor = Blackness; - COLORREF bkColor = Blackness; - - for (UINT iCol = 0; iCol < rows.text.at(iRow).length(); iCol++) - { - bool fColorDelta = false; - - if (!bColorFound) - { - fgColor = rows.FgAttr.at(iRow).at(iCol); - bkColor = rows.BkAttr.at(iRow).at(iCol); - bColorFound = true; - fColorDelta = true; - } - else if ((rows.FgAttr.at(iRow).at(iCol) != fgColor) || (rows.BkAttr.at(iRow).at(iCol) != bkColor)) - { - fgColor = rows.FgAttr.at(iRow).at(iCol); - bkColor = rows.BkAttr.at(iRow).at(iCol); - fColorDelta = true; - } - - if (fColorDelta) - { - if (cchCharsToPrint > 0) - { - // write accumulated characters to stream .... - std::string TempBuff; - int const cbTempCharsNeeded = WideCharToMultiByte(CP_UTF8, 0, rows.text[iRow].data() + cbStartOffset, static_cast(cchCharsToPrint), nullptr, 0, nullptr, nullptr); - TempBuff.resize(cbTempCharsNeeded); - WideCharToMultiByte(CP_UTF8, 0, rows.text[iRow].data() + cbStartOffset, static_cast(cchCharsToPrint), TempBuff.data(), cbTempCharsNeeded, nullptr, nullptr); - szClipboard.append(TempBuff); - cbStartOffset += cchCharsToPrint; - cchCharsToPrint = 0; - - // close previous span - szClipboard += szSpanEnd; - } - - // start new span - - // format with color then copy formatted string - szSpanStart.resize(cbSpanStart + 1); // add room for null - sprintf_s(szSpanStart.data(), cbSpanStart + 1, szSpanStartPattern.data(), GetRValue(fgColor), GetGValue(fgColor), GetBValue(fgColor), GetRValue(bkColor), GetGValue(bkColor), GetBValue(bkColor)); - szSpanStart.resize(cbSpanStart); // chop null from sprintf - szClipboard.append(szSpanStart); - } - - // accumulate 1 character - cchCharsToPrint++; - } - - PCWCHAR pwchAccumulateStart = rows.text.at(iRow).data() + cbStartOffset; - - // write accumulated characters to stream - std::string CharsConverted; - int cbCharsConverted = WideCharToMultiByte(CP_UTF8, 0, pwchAccumulateStart, static_cast(cchCharsToPrint), nullptr, 0, nullptr, nullptr); - CharsConverted.resize(cbCharsConverted); - WideCharToMultiByte(CP_UTF8, 0, pwchAccumulateStart, static_cast(cchCharsToPrint), CharsConverted.data(), cbCharsConverted, nullptr, nullptr); - szClipboard.append(CharsConverted); - } - - if (bColorFound) - { - // copy end span - szClipboard.append(szSpanEnd); - } - - // after we have copied all text we must wrap up - // with a standard set of HTML boilerplate required - // by CF_HTML - - // copy end font size span - szClipboard.append(szSpanEnd); - - // copy end font face span - szClipboard.append(szSpanEnd); - - // copy end background color span - szClipboard.append(szDivEnd); - - // copy HTML end fragment - szClipboard.append(szHtmlFragEnd); - - // copy HTML footer - szClipboard.append(szHtmlFooter); - - // null terminate the clipboard data - szClipboard += '\0'; - - // we are done generating formating & building HTML for the selection - // prepare the header text with the byte counts now that we know them - size_t const cbHtmlStart = cbHeader; // bytecount to start of HTML context - size_t const cbHtmlEnd = szClipboard.size() - 1; // don't count the null at the end - size_t const cbFragStart = cbHeader + cbHtmlHeader; // bytecount to start of selection fragment - size_t const cbFragEnd = cbHtmlEnd - cbHtmlFooter; - - // push the values into the required HTML 0.9 header format - std::string szHtmlClipHeaderFinal; - szHtmlClipHeaderFinal.resize(cbHeader + 1); // add room for a null - sprintf_s(szHtmlClipHeaderFinal.data(), cbHeader + 1, szHtmlClipFormat.data(), cbHtmlStart, cbHtmlEnd, cbFragStart, cbFragEnd, cbFragStart, cbFragEnd); - szHtmlClipHeaderFinal.resize(cbHeader); // chop off the null - - // overwrite the reserved space with the actual header & offsets we calculated - szClipboard.replace(0, cbHeader, szHtmlClipHeaderFinal.data()); - } - catch (...) - { - LOG_HR(wil::ResultFromCaughtException()); - szClipboard.clear(); // dont return a partial html fragment... - } - - return szClipboard; -} - // Routine Description: // - Copies the text given onto the global system clipboard. // Arguments: @@ -515,7 +275,9 @@ void Clipboard::CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, if (fAlsoCopyHtml) { - std::string HTMLToPlaceOnClip = GenHTML(rows); + const auto& fontData = ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer().GetCurrentFont(); + int const iFontHeightPoints = fontData.GetUnscaledSize().Y * 72 / ServiceLocator::LocateGlobals().dpi; + std::string HTMLToPlaceOnClip = TextBuffer::GenHTML(rows, iFontHeightPoints, fontData.GetFaceName(), "Windows Console Host"); const size_t cbNeededHTML = HTMLToPlaceOnClip.size(); if (cbNeededHTML) { diff --git a/src/interactivity/win32/WindowMetrics.cpp b/src/interactivity/win32/WindowMetrics.cpp index b985faf8375..690e20f789b 100644 --- a/src/interactivity/win32/WindowMetrics.cpp +++ b/src/interactivity/win32/WindowMetrics.cpp @@ -96,7 +96,7 @@ RECT WindowMetrics::GetMaxWindowRectInPixels(const RECT* const prcSuggested, _Ou // NOTE: We must use the nearest monitor because sometimes the system moves the window around into strange spots while performing snap and Win+D operations. // Those operations won't work correctly if we use MONITOR_DEFAULTTOPRIMARY. - IConsoleWindow* pWindow = ServiceLocator::LocateConsoleWindow(); + auto pWindow = ServiceLocator::LocateConsoleWindow(); if (pWindow == nullptr || (TRUE != EqualRect(&rc, &rcZero))) { // For invalid window handles or when we were passed a non-zero suggestion rectangle, get the monitor from the rect. @@ -258,7 +258,7 @@ void WindowMetrics::ConvertRect(_Inout_ RECT* const prc, const ConvertRectangle DWORD dwStyle = 0; DWORD dwExStyle = 0; - IConsoleWindow* pWindow = ServiceLocator::LocateConsoleWindow(); + Microsoft::Console::Types::IConsoleWindow* pWindow = ServiceLocator::LocateConsoleWindow(); if (pWindow != nullptr) { dwStyle = GetWindowStyle(pWindow->GetWindowHandle()); diff --git a/src/interactivity/win32/clipboard.hpp b/src/interactivity/win32/clipboard.hpp index 00da8aaf2df..d187ba51b7c 100644 --- a/src/interactivity/win32/clipboard.hpp +++ b/src/interactivity/win32/clipboard.hpp @@ -45,7 +45,6 @@ namespace Microsoft::Console::Interactivity::Win32 const std::vector& selectionRects); void CopyHTMLToClipboard(const TextBuffer::TextAndColor& rows); - std::string GenHTML(const TextBuffer::TextAndColor& rows); void CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, _In_ bool const fAlsoCopyHtml); bool FilterCharacterOnPaste(_Inout_ WCHAR* const pwch); diff --git a/src/interactivity/win32/find.cpp b/src/interactivity/win32/find.cpp index 91124cec480..e1702d586ae 100644 --- a/src/interactivity/win32/find.cpp +++ b/src/interactivity/win32/find.cpp @@ -86,7 +86,7 @@ INT_PTR CALLBACK FindDialogProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM l void DoFind() { Globals& g = ServiceLocator::LocateGlobals(); - IConsoleWindow* const pWindow = ServiceLocator::LocateConsoleWindow(); + Microsoft::Console::Types::IConsoleWindow* const pWindow = ServiceLocator::LocateConsoleWindow(); UnlockConsole(); if (pWindow != nullptr) diff --git a/src/interactivity/win32/lib/win32.LIB.vcxproj b/src/interactivity/win32/lib/win32.LIB.vcxproj index 659a554486d..d9afadfee87 100644 --- a/src/interactivity/win32/lib/win32.LIB.vcxproj +++ b/src/interactivity/win32/lib/win32.LIB.vcxproj @@ -21,7 +21,7 @@ - + @@ -45,7 +45,7 @@ - + diff --git a/src/interactivity/win32/lib/win32.LIB.vcxproj.filters b/src/interactivity/win32/lib/win32.LIB.vcxproj.filters index 900e08aa14e..4a4ad011590 100644 --- a/src/interactivity/win32/lib/win32.LIB.vcxproj.filters +++ b/src/interactivity/win32/lib/win32.LIB.vcxproj.filters @@ -45,9 +45,6 @@ Source Files - - Source Files - Source Files @@ -69,13 +66,16 @@ Source Files - + Source Files Source Files - + + Source Files + + Source Files
@@ -113,9 +113,6 @@ Header Files - - Header Files - Header Files @@ -134,14 +131,20 @@ Header Files - + Header Files Header Files - + Header Files + + Header Files + +
+ + \ No newline at end of file diff --git a/src/interactivity/win32/screenInfoUiaProvider.cpp b/src/interactivity/win32/screenInfoUiaProvider.cpp index c8127e6c66b..9b525e33ce9 100644 --- a/src/interactivity/win32/screenInfoUiaProvider.cpp +++ b/src/interactivity/win32/screenInfoUiaProvider.cpp @@ -4,254 +4,31 @@ #include "precomp.h" #include "screenInfoUiaProvider.hpp" -#include "../../host/screenInfo.hpp" -#include "../inc/ServiceLocator.hpp" +#include "..\types\WindowUiaProviderBase.hpp" -#include "windowUiaProvider.hpp" -#include "window.hpp" -#include "windowdpiapi.hpp" - -#include "UiaTextRange.hpp" - -using namespace Microsoft::Console::Interactivity::Win32; -using namespace Microsoft::Console::Interactivity::Win32::ScreenInfoUiaProviderTracing; +using namespace Microsoft::Console::Types; +using namespace Microsoft::Console::Render; using namespace Microsoft::Console::Interactivity; -// A helper function to create a SafeArray Version of an int array of a specified length -SAFEARRAY* BuildIntSafeArray(_In_reads_(length) const int* const data, const int length) -{ - SAFEARRAY* psa = SafeArrayCreateVector(VT_I4, 0, length); - if (psa != nullptr) - { - for (long i = 0; i < length; i++) - { - if (FAILED(SafeArrayPutElement(psa, &i, (void*)&(data[i])))) - { - SafeArrayDestroy(psa); - psa = nullptr; - break; - } - } - } - - return psa; -} +using namespace Microsoft::Console::Interactivity::Win32; -ScreenInfoUiaProvider::ScreenInfoUiaProvider(_In_ WindowUiaProvider* const pUiaParent) : +ScreenInfoUiaProvider::ScreenInfoUiaProvider(_In_ IUiaData* pData, + _In_ WindowUiaProviderBase* const pUiaParent) : _pUiaParent(THROW_HR_IF_NULL(E_INVALIDARG, pUiaParent)), - _signalFiringMapping{}, - _cRefs(1) -{ - Tracing::s_TraceUia(nullptr, ApiCall::Constructor, nullptr); -} - -ScreenInfoUiaProvider::~ScreenInfoUiaProvider() -{ -} - -[[nodiscard]] HRESULT ScreenInfoUiaProvider::Signal(_In_ EVENTID id) -{ - HRESULT hr = S_OK; - // check to see if we're already firing this particular event - if (_signalFiringMapping.find(id) != _signalFiringMapping.end() && - _signalFiringMapping[id] == true) - { - return hr; - } - - try - { - _signalFiringMapping[id] = true; - } - CATCH_RETURN(); - - IRawElementProviderSimple* pProvider = static_cast(this); - hr = UiaRaiseAutomationEvent(pProvider, id); - _signalFiringMapping[id] = false; - - // tracing - ApiMsgSignal apiMsg; - apiMsg.Signal = id; - Tracing::s_TraceUia(this, ApiCall::Signal, &apiMsg); - return hr; -} - -#pragma region IUnknown - -IFACEMETHODIMP_(ULONG) -ScreenInfoUiaProvider::AddRef() -{ - Tracing::s_TraceUia(this, ApiCall::AddRef, nullptr); - return InterlockedIncrement(&_cRefs); -} - -IFACEMETHODIMP_(ULONG) -ScreenInfoUiaProvider::Release() -{ - Tracing::s_TraceUia(this, ApiCall::Release, nullptr); - long val = InterlockedDecrement(&_cRefs); - if (val == 0) - { - delete this; - } - return val; -} - -IFACEMETHODIMP ScreenInfoUiaProvider::QueryInterface(_In_ REFIID riid, - _COM_Outptr_result_maybenull_ void** ppInterface) -{ - Tracing::s_TraceUia(this, ApiCall::QueryInterface, nullptr); - if (riid == __uuidof(IUnknown)) - { - *ppInterface = static_cast(this); - } - else if (riid == __uuidof(IRawElementProviderSimple)) - { - *ppInterface = static_cast(this); - } - else if (riid == __uuidof(IRawElementProviderFragment)) - { - *ppInterface = static_cast(this); - } - else if (riid == __uuidof(ITextProvider)) - { - *ppInterface = static_cast(this); - } - else - { - *ppInterface = nullptr; - return E_NOINTERFACE; - } - - (static_cast(*ppInterface))->AddRef(); - - return S_OK; -} - -#pragma endregion - -#pragma region IRawElementProviderSimple - -// Implementation of IRawElementProviderSimple::get_ProviderOptions. -// Gets UI Automation provider options. -IFACEMETHODIMP ScreenInfoUiaProvider::get_ProviderOptions(_Out_ ProviderOptions* pOptions) + ScreenInfoUiaProviderBase(THROW_HR_IF_NULL(E_INVALIDARG, pData)) { - Tracing::s_TraceUia(this, ApiCall::GetProviderOptions, nullptr); - *pOptions = ProviderOptions_ServerSideProvider; - return S_OK; -} - -// Implementation of IRawElementProviderSimple::get_PatternProvider. -// Gets the object that supports ISelectionPattern. -IFACEMETHODIMP ScreenInfoUiaProvider::GetPatternProvider(_In_ PATTERNID patternId, - _COM_Outptr_result_maybenull_ IUnknown** ppInterface) -{ - Tracing::s_TraceUia(this, ApiCall::GetPatternProvider, nullptr); - - *ppInterface = nullptr; - HRESULT hr = S_OK; - - if (patternId == UIA_TextPatternId) - { - hr = this->QueryInterface(__uuidof(ITextProvider), reinterpret_cast(ppInterface)); - if (FAILED(hr)) - { - *ppInterface = nullptr; - } - } - return hr; } -// Implementation of IRawElementProviderSimple::get_PropertyValue. -// Gets custom properties. -IFACEMETHODIMP ScreenInfoUiaProvider::GetPropertyValue(_In_ PROPERTYID propertyId, - _Out_ VARIANT* pVariant) -{ - Tracing::s_TraceUia(this, ApiCall::GetPropertyValue, nullptr); - - pVariant->vt = VT_EMPTY; - - // Returning the default will leave the property as the default - // so we only really need to touch it for the properties we want to implement - if (propertyId == UIA_ControlTypePropertyId) - { - // This control is the Document control type, implying that it is - // a complex document that supports text pattern - pVariant->vt = VT_I4; - pVariant->lVal = UIA_DocumentControlTypeId; - } - else if (propertyId == UIA_NamePropertyId) - { - // TODO: MSFT: 7960168 - These strings should be localized text in the final UIA work - pVariant->bstrVal = SysAllocString(L"Text Area"); - if (pVariant->bstrVal != nullptr) - { - pVariant->vt = VT_BSTR; - } - } - else if (propertyId == UIA_AutomationIdPropertyId) - { - pVariant->bstrVal = SysAllocString(L"Text Area"); - if (pVariant->bstrVal != nullptr) - { - pVariant->vt = VT_BSTR; - } - } - else if (propertyId == UIA_IsControlElementPropertyId) - { - pVariant->vt = VT_BOOL; - pVariant->boolVal = VARIANT_TRUE; - } - else if (propertyId == UIA_IsContentElementPropertyId) - { - pVariant->vt = VT_BOOL; - pVariant->boolVal = VARIANT_TRUE; - } - else if (propertyId == UIA_IsKeyboardFocusablePropertyId) - { - pVariant->vt = VT_BOOL; - pVariant->boolVal = VARIANT_TRUE; - } - else if (propertyId == UIA_HasKeyboardFocusPropertyId) - { - pVariant->vt = VT_BOOL; - pVariant->boolVal = VARIANT_TRUE; - } - else if (propertyId == UIA_ProviderDescriptionPropertyId) - { - pVariant->bstrVal = SysAllocString(L"Microsoft Console Host: Screen Information Text Area"); - if (pVariant->bstrVal != nullptr) - { - pVariant->vt = VT_BSTR; - } - } - else if (propertyId == UIA_IsEnabledPropertyId) - { - pVariant->vt = VT_BOOL; - pVariant->boolVal = VARIANT_TRUE; - } - - return S_OK; -} - -IFACEMETHODIMP ScreenInfoUiaProvider::get_HostRawElementProvider(_COM_Outptr_result_maybenull_ IRawElementProviderSimple** ppProvider) -{ - Tracing::s_TraceUia(this, ApiCall::GetHostRawElementProvider, nullptr); - *ppProvider = nullptr; - - return S_OK; -} -#pragma endregion - -#pragma region IRawElementProviderFragment - IFACEMETHODIMP ScreenInfoUiaProvider::Navigate(_In_ NavigateDirection direction, _COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider) { - ApiMsgNavigate apiMsg; - apiMsg.Direction = direction; - Tracing::s_TraceUia(this, ApiCall::Navigate, &apiMsg); + RETURN_HR_IF(E_INVALIDARG, ppProvider == nullptr); *ppProvider = nullptr; + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + /*ApiMsgNavigate apiMsg; + apiMsg.Direction = direction; + Tracing::s_TraceUia(this, ApiCall::Navigate, &apiMsg);*/ + if (direction == NavigateDirection_Parent) { try @@ -270,28 +47,12 @@ IFACEMETHODIMP ScreenInfoUiaProvider::Navigate(_In_ NavigateDirection direction, return S_OK; } -IFACEMETHODIMP ScreenInfoUiaProvider::GetRuntimeId(_Outptr_result_maybenull_ SAFEARRAY** ppRuntimeId) -{ - Tracing::s_TraceUia(this, ApiCall::GetRuntimeId, nullptr); - // Root defers this to host, others must implement it... - *ppRuntimeId = nullptr; - - // AppendRuntimeId is a magic Number that tells UIAutomation to Append its own Runtime ID(From the HWND) - int rId[] = { UiaAppendRuntimeId, -1 }; - // BuildIntSafeArray is a custom function to hide the SafeArray creation - *ppRuntimeId = BuildIntSafeArray(rId, 2); - RETURN_IF_NULL_ALLOC(*ppRuntimeId); - - return S_OK; -} - IFACEMETHODIMP ScreenInfoUiaProvider::get_BoundingRectangle(_Out_ UiaRect* pRect) { - Tracing::s_TraceUia(this, ApiCall::GetBoundingRectangle, nullptr); - const IConsoleWindow* const pIConsoleWindow = _getIConsoleWindow(); - RETURN_HR_IF_NULL((HRESULT)UIA_E_ELEMENTNOTAVAILABLE, pIConsoleWindow); + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::GetBoundingRectangle, nullptr); - RECT rc = pIConsoleWindow->GetWindowRect(); + RECT rc = _pUiaParent->GetWindowRect(); pRect->left = rc.left; pRect->top = rc.top; @@ -301,22 +62,13 @@ IFACEMETHODIMP ScreenInfoUiaProvider::get_BoundingRectangle(_Out_ UiaRect* pRect return S_OK; } -IFACEMETHODIMP ScreenInfoUiaProvider::GetEmbeddedFragmentRoots(_Outptr_result_maybenull_ SAFEARRAY** ppRoots) -{ - Tracing::s_TraceUia(this, ApiCall::GetEmbeddedFragmentRoots, nullptr); - *ppRoots = nullptr; - return S_OK; -} - -IFACEMETHODIMP ScreenInfoUiaProvider::SetFocus() -{ - Tracing::s_TraceUia(this, ApiCall::SetFocus, nullptr); - return Signal(UIA_AutomationFocusChangedEventId); -} - IFACEMETHODIMP ScreenInfoUiaProvider::get_FragmentRoot(_COM_Outptr_result_maybenull_ IRawElementProviderFragmentRoot** ppProvider) { - Tracing::s_TraceUia(this, ApiCall::GetFragmentRoot, nullptr); + RETURN_HR_IF(E_INVALIDARG, ppProvider == nullptr); + *ppProvider = nullptr; + + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::GetFragmentRoot, nullptr); try { _pUiaParent->QueryInterface(IID_PPV_ARGS(ppProvider)); @@ -330,293 +82,51 @@ IFACEMETHODIMP ScreenInfoUiaProvider::get_FragmentRoot(_COM_Outptr_result_mayben return S_OK; } -#pragma endregion - -#pragma region ITextProvider - -IFACEMETHODIMP ScreenInfoUiaProvider::GetSelection(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) +HWND ScreenInfoUiaProvider::GetWindowHandle() const { - CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - ApiMsgGetSelection apiMsg; - gci.LockConsole(); - auto Unlock = wil::scope_exit([&] { - gci.UnlockConsole(); - }); - - *ppRetVal = nullptr; - HRESULT hr = S_OK; - - if (!Selection::Instance().IsAreaSelected()) - { - apiMsg.AreaSelected = false; - apiMsg.SelectionRowCount = 1; - // return a degenerate range at the cursor position - SCREEN_INFORMATION& screenInfo = _getScreenInfo(); - const Cursor& cursor = screenInfo.GetTextBuffer().GetCursor(); - - // make a safe array - *ppRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, 1); - if (*ppRetVal == nullptr) - { - return E_OUTOFMEMORY; - } - - IRawElementProviderSimple* pProvider; - hr = this->QueryInterface(IID_PPV_ARGS(&pProvider)); - if (FAILED(hr)) - { - SafeArrayDestroy(*ppRetVal); - *ppRetVal = nullptr; - return hr; - } - - UiaTextRange* range; - try - { - range = UiaTextRange::Create(pProvider, - cursor); - } - catch (...) - { - range = nullptr; - hr = wil::ResultFromCaughtException(); - } - (static_cast(pProvider))->Release(); - if (range == nullptr) - { - SafeArrayDestroy(*ppRetVal); - *ppRetVal = nullptr; - return hr; - } - - LONG currentIndex = 0; - hr = SafeArrayPutElement(*ppRetVal, ¤tIndex, reinterpret_cast(range)); - if (FAILED(hr)) - { - SafeArrayDestroy(*ppRetVal); - *ppRetVal = nullptr; - return hr; - } - } - else - { - // get the selection ranges - std::deque ranges; - IRawElementProviderSimple* pProvider; - RETURN_IF_FAILED(QueryInterface(IID_PPV_ARGS(&pProvider))); - try - { - ranges = UiaTextRange::GetSelectionRanges(pProvider); - } - catch (...) - { - hr = wil::ResultFromCaughtException(); - } - pProvider->Release(); - RETURN_IF_FAILED(hr); - - apiMsg.AreaSelected = true; - apiMsg.SelectionRowCount = static_cast(ranges.size()); - - // make a safe array - *ppRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, static_cast(ranges.size())); - if (*ppRetVal == nullptr) - { - return E_OUTOFMEMORY; - } - - // fill the safe array - for (LONG i = 0; i < static_cast(ranges.size()); ++i) - { - hr = SafeArrayPutElement(*ppRetVal, &i, reinterpret_cast(ranges[i])); - if (FAILED(hr)) - { - SafeArrayDestroy(*ppRetVal); - *ppRetVal = nullptr; - while (!ranges.empty()) - { - UiaTextRange* pRange = ranges[0]; - ranges.pop_front(); - pRange->Release(); - } - return hr; - } - } - } - - Tracing::s_TraceUia(this, ApiCall::GetSelection, &apiMsg); - return S_OK; + return _pUiaParent->GetWindowHandle(); } -IFACEMETHODIMP ScreenInfoUiaProvider::GetVisibleRanges(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) +void ScreenInfoUiaProvider::ChangeViewport(const SMALL_RECT NewWindow) { - Tracing::s_TraceUia(this, ApiCall::GetVisibleRanges, nullptr); - CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - - gci.LockConsole(); - auto Unlock = wil::scope_exit([&] { - gci.UnlockConsole(); - }); - - const SCREEN_INFORMATION& screenInfo = _getScreenInfo(); - const auto viewport = screenInfo.GetViewport(); - const COORD screenBufferCoords = _getScreenBufferCoords(); - const int totalLines = screenBufferCoords.Y; - - // make a safe array - const size_t rowCount = viewport.Height(); - *ppRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, static_cast(rowCount)); - if (*ppRetVal == nullptr) - { - return E_OUTOFMEMORY; - } - - // stuff each visible line in the safearray - for (size_t i = 0; i < rowCount; ++i) - { - const int lineNumber = (viewport.Top() + i) % totalLines; - const int start = lineNumber * screenBufferCoords.X; - // - 1 to get the last column in the row - const int end = start + screenBufferCoords.X - 1; - - IRawElementProviderSimple* pProvider; - HRESULT hr = this->QueryInterface(IID_PPV_ARGS(&pProvider)); - if (FAILED(hr)) - { - SafeArrayDestroy(*ppRetVal); - *ppRetVal = nullptr; - return hr; - } - - UiaTextRange* range; - try - { - range = UiaTextRange::Create(pProvider, - start, - end, - false); - } - catch (...) - { - range = nullptr; - hr = wil::ResultFromCaughtException(); - } - (static_cast(pProvider))->Release(); - - if (range == nullptr) - { - SafeArrayDestroy(*ppRetVal); - *ppRetVal = nullptr; - return hr; - } - - LONG currentIndex = static_cast(i); - hr = SafeArrayPutElement(*ppRetVal, ¤tIndex, reinterpret_cast(range)); - if (FAILED(hr)) - { - SafeArrayDestroy(*ppRetVal); - *ppRetVal = nullptr; - return hr; - } - } - return S_OK; -} - -IFACEMETHODIMP ScreenInfoUiaProvider::RangeFromChild(_In_ IRawElementProviderSimple* /*childElement*/, - _COM_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) -{ - Tracing::s_TraceUia(this, ApiCall::RangeFromChild, nullptr); - - IRawElementProviderSimple* pProvider; - RETURN_IF_FAILED(this->QueryInterface(IID_PPV_ARGS(&pProvider))); - - HRESULT hr = S_OK; - try - { - *ppRetVal = UiaTextRange::Create(pProvider); - } - catch (...) - { - *ppRetVal = nullptr; - hr = wil::ResultFromCaughtException(); - } - (static_cast(pProvider))->Release(); - - return hr; + _pUiaParent->ChangeViewport(NewWindow); } -IFACEMETHODIMP ScreenInfoUiaProvider::RangeFromPoint(_In_ UiaPoint point, - _COM_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) +std::deque ScreenInfoUiaProvider::GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider) { - Tracing::s_TraceUia(this, ApiCall::RangeFromPoint, nullptr); - IRawElementProviderSimple* pProvider; - RETURN_IF_FAILED(this->QueryInterface(IID_PPV_ARGS(&pProvider))); + std::deque result; - HRESULT hr = S_OK; - try - { - *ppRetVal = UiaTextRange::Create(pProvider, - point); - } - catch (...) + auto ranges = UiaTextRange::GetSelectionRanges(_pData, pProvider); + while (!ranges.empty()) { - *ppRetVal = nullptr; - hr = wil::ResultFromCaughtException(); + result.emplace_back(ranges.back()); + ranges.pop_back(); } - (static_cast(pProvider))->Release(); - return hr; + return result; } -IFACEMETHODIMP ScreenInfoUiaProvider::get_DocumentRange(_COM_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) +UiaTextRangeBase* ScreenInfoUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider) { - Tracing::s_TraceUia(this, ApiCall::GetDocumentRange, nullptr); - IRawElementProviderSimple* pProvider; - RETURN_IF_FAILED(this->QueryInterface(IID_PPV_ARGS(&pProvider))); - - HRESULT hr = S_OK; - try - { - *ppRetVal = UiaTextRange::Create(pProvider); - } - catch (...) - { - *ppRetVal = nullptr; - hr = wil::ResultFromCaughtException(); - } - (static_cast(pProvider))->Release(); - - if (*ppRetVal) - { - (*ppRetVal)->ExpandToEnclosingUnit(TextUnit::TextUnit_Document); - } - - return hr; -} - -IFACEMETHODIMP ScreenInfoUiaProvider::get_SupportedTextSelection(_Out_ SupportedTextSelection* pRetVal) -{ - Tracing::s_TraceUia(this, ApiCall::GetSupportedTextSelection, nullptr); - *pRetVal = SupportedTextSelection::SupportedTextSelection_Single; - return S_OK; + return UiaTextRange::Create(_pData, pProvider); } -#pragma endregion - -const COORD ScreenInfoUiaProvider::_getScreenBufferCoords() const +UiaTextRangeBase* ScreenInfoUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, + const Cursor& cursor) { - const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - return gci.GetScreenBufferSize(); + return UiaTextRange::Create(_pData, pProvider, cursor); } -SCREEN_INFORMATION& ScreenInfoUiaProvider::_getScreenInfo() +UiaTextRangeBase* ScreenInfoUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, + const Endpoint start, + const Endpoint end, + const bool degenerate) { - CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - THROW_HR_IF(E_POINTER, !gci.HasActiveOutputBuffer()); - return gci.GetActiveOutputBuffer(); + return UiaTextRange::Create(_pData, pProvider, start, end, degenerate); } -IConsoleWindow* const ScreenInfoUiaProvider::_getIConsoleWindow() +UiaTextRangeBase* ScreenInfoUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, + const UiaPoint point) { - return ServiceLocator::LocateConsoleWindow(); + return UiaTextRange::Create(_pData, pProvider, point); } diff --git a/src/interactivity/win32/screenInfoUiaProvider.hpp b/src/interactivity/win32/screenInfoUiaProvider.hpp index 695105a1f5f..b7c29ce3cf3 100644 --- a/src/interactivity/win32/screenInfoUiaProvider.hpp +++ b/src/interactivity/win32/screenInfoUiaProvider.hpp @@ -9,143 +9,60 @@ Module Name: - This module provides UI Automation access to the screen buffer to support both automation tests and accessibility (screen reading) applications. +- This is the ConHost extension of ScreenInfoUiaProviderBase.hpp - Based on examples, sample code, and guidance from https://msdn.microsoft.com/en-us/library/windows/desktop/ee671596(v=vs.85).aspx Author(s): -- Michael Niksa (MiNiksa) 2017 -- Austin Diviness (AustDi) 2017 +- Carlos Zamora (CaZamor) 2019 --*/ #pragma once #include "precomp.h" - -// Forward declare, prevent circular ref. -class SCREEN_INFORMATION; +#include "..\types\ScreenInfoUiaProviderBase.h" +#include "..\types\UiaTextRangeBase.hpp" +#include "uiaTextRange.hpp" namespace Microsoft::Console::Interactivity::Win32 { - class Window; - - class WindowUiaProvider; - - class ScreenInfoUiaProvider final : - public IRawElementProviderSimple, - public IRawElementProviderFragment, - public ITextProvider + class ScreenInfoUiaProvider final : public Microsoft::Console::Types::ScreenInfoUiaProviderBase { public: - ScreenInfoUiaProvider(_In_ WindowUiaProvider* const pUiaParent); - virtual ~ScreenInfoUiaProvider(); - - [[nodiscard]] HRESULT Signal(_In_ EVENTID id); - - // IUnknown methods - IFACEMETHODIMP_(ULONG) - AddRef(); - IFACEMETHODIMP_(ULONG) - Release(); - IFACEMETHODIMP QueryInterface(_In_ REFIID riid, - _COM_Outptr_result_maybenull_ void** ppInterface); - - // IRawElementProviderSimple methods - IFACEMETHODIMP get_ProviderOptions(_Out_ ProviderOptions* pOptions); - IFACEMETHODIMP GetPatternProvider(_In_ PATTERNID iid, - _COM_Outptr_result_maybenull_ IUnknown** ppInterface); - IFACEMETHODIMP GetPropertyValue(_In_ PROPERTYID idProp, - _Out_ VARIANT* pVariant); - IFACEMETHODIMP get_HostRawElementProvider(_COM_Outptr_result_maybenull_ IRawElementProviderSimple** ppProvider); + ScreenInfoUiaProvider(_In_ Microsoft::Console::Types::IUiaData* pData, + _In_ Microsoft::Console::Types::WindowUiaProviderBase* const pUiaParent); // IRawElementProviderFragment methods IFACEMETHODIMP Navigate(_In_ NavigateDirection direction, - _COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider); - IFACEMETHODIMP GetRuntimeId(_Outptr_result_maybenull_ SAFEARRAY** ppRuntimeId); - IFACEMETHODIMP get_BoundingRectangle(_Out_ UiaRect* pRect); - IFACEMETHODIMP GetEmbeddedFragmentRoots(_Outptr_result_maybenull_ SAFEARRAY** ppRoots); - IFACEMETHODIMP SetFocus(); - IFACEMETHODIMP get_FragmentRoot(_COM_Outptr_result_maybenull_ IRawElementProviderFragmentRoot** ppProvider); - - // ITextProvider - IFACEMETHODIMP GetSelection(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal); - IFACEMETHODIMP GetVisibleRanges(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal); - IFACEMETHODIMP RangeFromChild(_In_ IRawElementProviderSimple* childElement, - _COM_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal); - IFACEMETHODIMP RangeFromPoint(_In_ UiaPoint point, - _COM_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal); - IFACEMETHODIMP get_DocumentRange(_COM_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal); - IFACEMETHODIMP get_SupportedTextSelection(_Out_ SupportedTextSelection* pRetVal); - - private: - // Ref counter for COM object - ULONG _cRefs; - - // weak reference to uia parent - WindowUiaProvider* const _pUiaParent; + _COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider) override; + IFACEMETHODIMP get_BoundingRectangle(_Out_ UiaRect* pRect) override; + IFACEMETHODIMP get_FragmentRoot(_COM_Outptr_result_maybenull_ IRawElementProviderFragmentRoot** ppProvider) override; - // this is used to prevent the object from - // signaling an event while it is already in the - // process of signalling another event. - // This fixes a problem with JAWS where it would - // call a public method that calls - // UiaRaiseAutomationEvent to signal something - // happened, which JAWS then detects the signal - // and calls the same method in response, - // eventually overflowing the stack. - // We aren't using this as a cheap locking - // mechanism for multi-threaded code. - std::map _signalFiringMapping; + HWND GetWindowHandle() const; + void ChangeViewport(const SMALL_RECT NewWindow); - const COORD _getScreenBufferCoords() const; - static SCREEN_INFORMATION& _getScreenInfo(); - static IConsoleWindow* const _getIConsoleWindow(); - }; + protected: + std::deque GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider) override; - namespace ScreenInfoUiaProviderTracing - { - enum class ApiCall - { - Constructor, - Signal, - AddRef, - Release, - QueryInterface, - GetProviderOptions, - GetPatternProvider, - GetPropertyValue, - GetHostRawElementProvider, - Navigate, - GetRuntimeId, - GetBoundingRectangle, - GetEmbeddedFragmentRoots, - SetFocus, - GetFragmentRoot, - GetSelection, - GetVisibleRanges, - RangeFromChild, - RangeFromPoint, - GetDocumentRange, - GetSupportedTextSelection - }; + // degenerate range + Microsoft::Console::Types::UiaTextRangeBase* CreateTextRange(_In_ IRawElementProviderSimple* const pProvider) override; - struct IApiMsg - { - }; + // degenerate range at cursor position + Microsoft::Console::Types::UiaTextRangeBase* CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, + const Cursor& cursor) override; - struct ApiMsgSignal : public IApiMsg - { - EVENTID Signal; - }; + // specific endpoint range + Microsoft::Console::Types::UiaTextRangeBase* CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, + const Endpoint start, + const Endpoint end, + const bool degenerate) override; - struct ApiMsgNavigate : public IApiMsg - { - NavigateDirection Direction; - }; + // range from a UiaPoint + Microsoft::Console::Types::UiaTextRangeBase* CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, + const UiaPoint point) override; - struct ApiMsgGetSelection : public IApiMsg - { - bool AreaSelected; - unsigned int SelectionRowCount; - }; - } + private: + // weak reference to uia parent + Microsoft::Console::Types::WindowUiaProviderBase* const _pUiaParent; + }; } diff --git a/src/interactivity/win32/uiaTextRange.cpp b/src/interactivity/win32/uiaTextRange.cpp new file mode 100644 index 00000000000..7751da3042f --- /dev/null +++ b/src/interactivity/win32/uiaTextRange.cpp @@ -0,0 +1,281 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" + +#include "uiaTextRange.hpp" +#include "screenInfoUiaProvider.hpp" +#include "..\host\search.h" +#include "..\interactivity\inc\ServiceLocator.hpp" + +using namespace Microsoft::Console::Types; +using namespace Microsoft::Console::Interactivity::Win32; +using Microsoft::Console::Interactivity::ServiceLocator; + +std::deque UiaTextRange::GetSelectionRanges(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* pProvider) +{ + std::deque ranges; + + // get the selection rects + const auto rectangles = pData->GetSelectionRects(); + + // create a range for each row + for (const auto& rect : rectangles) + { + ScreenInfoRow currentRow = rect.Top(); + Endpoint start = _screenInfoRowToEndpoint(pData, currentRow) + rect.Left(); + Endpoint end = _screenInfoRowToEndpoint(pData, currentRow) + rect.RightInclusive(); + UiaTextRange* range = UiaTextRange::Create(pData, + pProvider, + start, + end, + false); + if (range == nullptr) + { + // something when wrong, clean up and throw + while (!ranges.empty()) + { + UiaTextRangeBase* temp = ranges[0]; + ranges.pop_front(); + temp->Release(); + } + THROW_HR(E_INVALIDARG); + } + else + { + ranges.push_back(range); + } + } + return ranges; +} + +UiaTextRange* UiaTextRange::Create(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider) +{ + UiaTextRange* range = nullptr; + ; + try + { + range = new UiaTextRange(pData, pProvider); + } + catch (...) + { + range = nullptr; + } + + if (range) + { + pProvider->AddRef(); + } + return range; +} + +UiaTextRange* UiaTextRange::Create(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const Cursor& cursor) +{ + UiaTextRange* range = nullptr; + try + { + range = new UiaTextRange(pData, pProvider, cursor); + } + catch (...) + { + range = nullptr; + } + + if (range) + { + pProvider->AddRef(); + } + return range; +} + +UiaTextRange* UiaTextRange::Create(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const Endpoint start, + const Endpoint end, + const bool degenerate) +{ + UiaTextRange* range = nullptr; + try + { + range = new UiaTextRange(pData, + pProvider, + start, + end, + degenerate); + } + catch (...) + { + range = nullptr; + } + + if (range) + { + pProvider->AddRef(); + } + return range; +} + +UiaTextRange* UiaTextRange::Create(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const UiaPoint point) +{ + UiaTextRange* range = nullptr; + try + { + range = new UiaTextRange(pData, pProvider, point); + } + catch (...) + { + range = nullptr; + } + + if (range) + { + pProvider->AddRef(); + } + return range; +} + +// degenerate range constructor. +UiaTextRange::UiaTextRange(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider) : + UiaTextRangeBase(pData, pProvider) +{ +} + +UiaTextRange::UiaTextRange(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const Cursor& cursor) : + UiaTextRangeBase(pData, pProvider, cursor) +{ +} + +UiaTextRange::UiaTextRange(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const Endpoint start, + const Endpoint end, + const bool degenerate) : + UiaTextRangeBase(pData, pProvider, start, end, degenerate) +{ +} + +// returns a degenerate text range of the start of the row closest to the y value of point +UiaTextRange::UiaTextRange(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const UiaPoint point) : + UiaTextRangeBase(pData, pProvider) +{ + Initialize(point); +} + +IFACEMETHODIMP UiaTextRange::Clone(_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) +{ + RETURN_HR_IF(E_INVALIDARG, ppRetVal == nullptr); + *ppRetVal = nullptr; + try + { + *ppRetVal = new UiaTextRange(*this); + } + catch (...) + { + *ppRetVal = nullptr; + return wil::ResultFromCaughtException(); + } + if (*ppRetVal == nullptr) + { + return E_OUTOFMEMORY; + } + +#if defined(_DEBUG) && defined(UiaTextRangeBase_DEBUG_MSGS) + OutputDebugString(L"Clone\n"); + std::wstringstream ss; + ss << _id << L" cloned to " << (static_cast(*ppRetVal))->_id; + std::wstring str = ss.str(); + OutputDebugString(str.c_str()); + OutputDebugString(L"\n"); +#endif + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + // tracing + /*ApiMsgClone apiMsg; + apiMsg.CloneId = static_cast(*ppRetVal)->GetId(); + Tracing::s_TraceUia(this, ApiCall::Clone, &apiMsg);*/ + + return S_OK; +} + +IFACEMETHODIMP UiaTextRange::FindText(_In_ BSTR text, + _In_ BOOL searchBackward, + _In_ BOOL ignoreCase, + _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) +{ + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::FindText, nullptr); + RETURN_HR_IF(E_INVALIDARG, ppRetVal == nullptr); + *ppRetVal = nullptr; + try + { + const std::wstring wstr{ text, SysStringLen(text) }; + const auto sensitivity = ignoreCase ? Search::Sensitivity::CaseInsensitive : Search::Sensitivity::CaseSensitive; + + auto searchDirection = Search::Direction::Forward; + Endpoint searchAnchor = _start; + if (searchBackward) + { + searchDirection = Search::Direction::Backward; + searchAnchor = _end; + } + + CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + THROW_HR_IF(E_POINTER, !gci.HasActiveOutputBuffer()); + const auto& screenInfo = gci.GetActiveOutputBuffer().GetActiveBuffer(); + Search searcher{ screenInfo, wstr, searchDirection, sensitivity, _endpointToCoord(_pData, searchAnchor) }; + + HRESULT hr = S_OK; + if (searcher.FindNext()) + { + const auto foundLocation = searcher.GetFoundLocation(); + const Endpoint start = _coordToEndpoint(_pData, foundLocation.first); + const Endpoint end = _coordToEndpoint(_pData, foundLocation.second); + // make sure what was found is within the bounds of the current range + if ((searchDirection == Search::Direction::Forward && end < _end) || + (searchDirection == Search::Direction::Backward && start > _start)) + { + hr = Clone(ppRetVal); + if (SUCCEEDED(hr)) + { + UiaTextRange& range = static_cast(**ppRetVal); + range._start = start; + range._end = end; + range._degenerate = false; + } + } + } + return hr; + } + CATCH_RETURN(); +} + +void UiaTextRange::_ChangeViewport(const SMALL_RECT NewWindow) +{ + auto provider = static_cast(_pProvider.get()); + provider->ChangeViewport(NewWindow); +} + +void UiaTextRange::_TranslatePointToScreen(LPPOINT clientPoint) const +{ + ClientToScreen(_getWindowHandle(), clientPoint); +} + +void UiaTextRange::_TranslatePointFromScreen(LPPOINT screenPoint) const +{ + ScreenToClient(_getWindowHandle(), screenPoint); +} + +HWND UiaTextRange::_getWindowHandle() const +{ + const auto provider = static_cast(_pProvider.get()); + return provider->GetWindowHandle(); +} diff --git a/src/interactivity/win32/uiaTextRange.hpp b/src/interactivity/win32/uiaTextRange.hpp new file mode 100644 index 00000000000..998ed313604 --- /dev/null +++ b/src/interactivity/win32/uiaTextRange.hpp @@ -0,0 +1,90 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- UiaTextRange.hpp + +Abstract: +- This module provides UI Automation access to the text of the console + window to support both automation tests and accessibility (screen + reading) applications. + +Author(s): +- Carlos Zamora (CaZamor) 2019 +--*/ + +#pragma once + +#include "precomp.h" +#include "..\types\UiaTextRangeBase.hpp" + +namespace Microsoft::Console::Interactivity::Win32 +{ + class UiaTextRange final : public Microsoft::Console::Types::UiaTextRangeBase + { + public: + static std::deque GetSelectionRanges(_In_ Microsoft::Console::Types::IUiaData* pData, + _In_ IRawElementProviderSimple* pProvider); + + // degenerate range + static UiaTextRange* Create(_In_ Microsoft::Console::Types::IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider); + + // degenerate range at cursor position + static UiaTextRange* Create(_In_ Microsoft::Console::Types::IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const Cursor& cursor); + + // specific endpoint range + static UiaTextRange* Create(_In_ Microsoft::Console::Types::IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const Endpoint start, + const Endpoint end, + const bool degenerate); + + // range from a UiaPoint + static UiaTextRange* Create(_In_ Microsoft::Console::Types::IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const UiaPoint point); + + IFACEMETHODIMP Clone(_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) override; + IFACEMETHODIMP FindText(_In_ BSTR text, + _In_ BOOL searchBackward, + _In_ BOOL ignoreCase, + _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) override; + + protected: + void _ChangeViewport(const SMALL_RECT NewWindow) override; + void _TranslatePointToScreen(LPPOINT clientPoint) const override; + void _TranslatePointFromScreen(LPPOINT screenPoint) const override; + + private: + // degenerate range + UiaTextRange(_In_ Microsoft::Console::Types::IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider); + + // degenerate range at cursor position + UiaTextRange(_In_ Microsoft::Console::Types::IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const Cursor& cursor); + + // specific endpoint range + UiaTextRange(_In_ Microsoft::Console::Types::IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const Endpoint start, + const Endpoint end, + const bool degenerate); + + // range from a UiaPoint + UiaTextRange(_In_ Microsoft::Console::Types::IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const UiaPoint point); + + HWND _getWindowHandle() const; + +#ifdef UNIT_TESTING + friend class ::UiaTextRangeTests; +#endif + }; +} diff --git a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp index f9314b5435e..54570294cac 100644 --- a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp +++ b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp @@ -6,7 +6,7 @@ #include "..\..\inc\consoletaeftemplates.hpp" #include "CommonState.hpp" -#include "UiaTextRange.hpp" +#include "uiaTextRange.hpp" #include "../../../buffer/out/textBuffer.hpp" using namespace WEX::Common; @@ -75,6 +75,7 @@ class UiaTextRangeTests SCREEN_INFORMATION* _pScreenInfo; TextBuffer* _pTextBuffer; UiaTextRange* _range; + IUiaData* _pUiaData; TEST_METHOD_SETUP(MethodSetup) { @@ -88,6 +89,7 @@ class UiaTextRangeTests // set up pointers _pScreenInfo = &gci.GetActiveOutputBuffer(); _pTextBuffer = &_pScreenInfo->GetTextBuffer(); + _pUiaData = &gci.renderData; // fill text buffer with text for (UINT i = 0; i < _pTextBuffer->TotalRowCount(); ++i) @@ -102,6 +104,7 @@ class UiaTextRangeTests // set up default range _range = new UiaTextRange{ + _pUiaData, &_dummyProvider, 0, 0, @@ -121,6 +124,7 @@ class UiaTextRangeTests _pScreenInfo = nullptr; _pTextBuffer = nullptr; + _pUiaData = nullptr; return true; } @@ -134,24 +138,26 @@ class UiaTextRangeTests { // make a degenerate range and verify that it reports degenerate UiaTextRange degenerate{ + _pUiaData, &_dummyProvider, 20, 19, true }; VERIFY_IS_TRUE(degenerate.IsDegenerate()); - VERIFY_ARE_EQUAL(0u, degenerate._rowCountInRange()); + VERIFY_ARE_EQUAL(0u, degenerate._rowCountInRange(_pUiaData)); VERIFY_ARE_EQUAL(degenerate._start, degenerate._end); // make a non-degenerate range and verify that it reports as such UiaTextRange notDegenerate1{ + _pUiaData, &_dummyProvider, 20, 20, false }; VERIFY_IS_FALSE(notDegenerate1.IsDegenerate()); - VERIFY_ARE_EQUAL(1u, notDegenerate1._rowCountInRange()); + VERIFY_ARE_EQUAL(1u, notDegenerate1._rowCountInRange(_pUiaData)); } TEST_METHOD(CanCheckIfScreenInfoRowIsInViewport) @@ -212,7 +218,7 @@ class UiaTextRangeTests const auto rowWidth = _getRowWidth(); for (auto i = 0; i < 300; ++i) { - VERIFY_ARE_EQUAL(i / rowWidth, _range->_endpointToTextBufferRow(i)); + VERIFY_ARE_EQUAL(i / rowWidth, _range->_endpointToTextBufferRow(_pUiaData, i)); } } @@ -221,9 +227,9 @@ class UiaTextRangeTests const auto rowWidth = _getRowWidth(); for (unsigned int i = 0; i < 5; ++i) { - VERIFY_ARE_EQUAL(i * rowWidth, _range->_textBufferRowToEndpoint(i)); + VERIFY_ARE_EQUAL(i * rowWidth, _range->_textBufferRowToEndpoint(_pUiaData, i)); // make sure that the translation is reversible - VERIFY_ARE_EQUAL(i, _range->_endpointToTextBufferRow(_range->_textBufferRowToEndpoint(i))); + VERIFY_ARE_EQUAL(i, _range->_endpointToTextBufferRow(_pUiaData, _range->_textBufferRowToEndpoint(_pUiaData, i))); } } @@ -232,7 +238,7 @@ class UiaTextRangeTests const auto rowWidth = _getRowWidth(); for (unsigned int i = 0; i < 5; ++i) { - VERIFY_ARE_EQUAL(i, _range->_textBufferRowToScreenInfoRow(_range->_screenInfoRowToTextBufferRow(i))); + VERIFY_ARE_EQUAL(i, _range->_textBufferRowToScreenInfoRow(_pUiaData, _range->_screenInfoRowToTextBufferRow(_pUiaData, i))); } } @@ -242,7 +248,7 @@ class UiaTextRangeTests for (auto i = 0; i < 300; ++i) { const auto column = i % rowWidth; - VERIFY_ARE_EQUAL(column, _range->_endpointToColumn(i)); + VERIFY_ARE_EQUAL(column, _range->_endpointToColumn(_pUiaData, i)); } } @@ -250,13 +256,13 @@ class UiaTextRangeTests { const auto totalRows = _pTextBuffer->TotalRowCount(); VERIFY_ARE_EQUAL(totalRows, - _range->_getTotalRows()); + _range->_getTotalRows(_pUiaData)); } TEST_METHOD(CanGetRowWidth) { const auto rowWidth = _getRowWidth(); - VERIFY_ARE_EQUAL(rowWidth, _range->_getRowWidth()); + VERIFY_ARE_EQUAL(rowWidth, _range->_getRowWidth(_pUiaData)); } TEST_METHOD(CanNormalizeRow) @@ -273,7 +279,7 @@ class UiaTextRangeTests for (auto it = rowMappings.begin(); it != rowMappings.end(); ++it) { - VERIFY_ARE_EQUAL(static_cast(it->second), _range->_normalizeRow(it->first)); + VERIFY_ARE_EQUAL(static_cast(it->second), _range->_normalizeRow(_pUiaData, it->first)); } } @@ -323,7 +329,8 @@ class UiaTextRangeTests for (auto data : testData) { VERIFY_ARE_EQUAL(std::get<4>(data), - UiaTextRange::_compareScreenCoords(std::get<0>(data), + UiaTextRange::_compareScreenCoords(_pUiaData, + std::get<0>(data), std::get<1>(data), std::get<2>(data), std::get<3>(data))); @@ -393,8 +400,8 @@ class UiaTextRangeTests }, 5, 5, - UiaTextRange::_screenInfoRowToEndpoint(2) + 6, - UiaTextRange::_screenInfoRowToEndpoint(2) + 6 + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 2) + 6, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, 2) + 6 }, { @@ -410,8 +417,8 @@ class UiaTextRangeTests }, 5, 0, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow) + lastColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow) + lastColumnIndex + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex }, { @@ -427,8 +434,8 @@ class UiaTextRangeTests }, 5, 5, - UiaTextRange::_screenInfoRowToEndpoint(topRow + 1) + 4, - UiaTextRange::_screenInfoRowToEndpoint(topRow + 1) + 4 + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 1) + 4, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 1) + 4 }, { @@ -444,8 +451,8 @@ class UiaTextRangeTests }, -5, -5, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + (lastColumnIndex - 4), - UiaTextRange::_screenInfoRowToEndpoint(topRow) + (lastColumnIndex - 4) + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + (lastColumnIndex - 4), + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + (lastColumnIndex - 4) } }; // clang-format on @@ -454,7 +461,8 @@ class UiaTextRangeTests { Log::Comment(std::get<0>(data).c_str()); int amountMoved; - std::pair newEndpoints = UiaTextRange::_moveByCharacter(std::get<2>(data), + std::pair newEndpoints = UiaTextRange::_moveByCharacter(_pUiaData, + std::get<2>(data), std::get<1>(data), &amountMoved); @@ -493,8 +501,8 @@ class UiaTextRangeTests }, -4, 0, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + lastColumnIndex + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex }, { @@ -510,8 +518,8 @@ class UiaTextRangeTests }, 4, 4, - UiaTextRange::_screenInfoRowToEndpoint(topRow + 4) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(topRow + 4) + lastColumnIndex + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 4) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 4) + lastColumnIndex }, { @@ -527,8 +535,8 @@ class UiaTextRangeTests }, 3, 0, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow) + lastColumnIndex + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex }, { @@ -544,8 +552,8 @@ class UiaTextRangeTests }, -3, -3, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow - 3) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow - 3) + lastColumnIndex + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow - 3) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow - 3) + lastColumnIndex }, { @@ -561,8 +569,8 @@ class UiaTextRangeTests }, -1, 0, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + firstColumnIndex + 5, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + lastColumnIndex + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex + 5, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex }, { @@ -578,8 +586,8 @@ class UiaTextRangeTests }, 1, 0, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow) + firstColumnIndex + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + firstColumnIndex } }; // clang-format on @@ -588,7 +596,8 @@ class UiaTextRangeTests { Log::Comment(std::get<0>(data).c_str()); int amountMoved; - std::pair newEndpoints = UiaTextRange::_moveByLine(std::get<2>(data), + std::pair newEndpoints = UiaTextRange::_moveByLine(_pUiaData, + std::get<2>(data), std::get<1>(data), &amountMoved); @@ -630,8 +639,8 @@ class UiaTextRangeTests -1, 0, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex, false }, @@ -649,8 +658,8 @@ class UiaTextRangeTests -5, -3, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex, false }, @@ -668,8 +677,8 @@ class UiaTextRangeTests -5, -4, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, false }, @@ -687,8 +696,8 @@ class UiaTextRangeTests -7, -7, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + 3, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + 3, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 3, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 3, true }, @@ -706,8 +715,8 @@ class UiaTextRangeTests 1, 0, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, false }, @@ -725,8 +734,8 @@ class UiaTextRangeTests 5, 3, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, false }, @@ -744,8 +753,8 @@ class UiaTextRangeTests 5, 4, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow) + lastColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, false }, @@ -763,8 +772,8 @@ class UiaTextRangeTests 7, 7, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + 12, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + 12, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 12, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 12, true }, }; @@ -775,7 +784,8 @@ class UiaTextRangeTests Log::Comment(std::get<0>(data).c_str()); std::tuple result; int amountMoved; - result = UiaTextRange::_moveEndpointByUnitCharacter(std::get<2>(data), + result = UiaTextRange::_moveEndpointByUnitCharacter(_pUiaData, + std::get<2>(data), std::get<4>(data), std::get<1>(data), &amountMoved); @@ -819,8 +829,8 @@ class UiaTextRangeTests 1, 1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(topRow + 1) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 1) + lastColumnIndex, false }, @@ -838,8 +848,8 @@ class UiaTextRangeTests -2, -2, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(topRow + 1) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(topRow + 3) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 1) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 3) + lastColumnIndex, false }, @@ -857,8 +867,8 @@ class UiaTextRangeTests 2, 2, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(topRow + 3) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(topRow + 5) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 3) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 5) + lastColumnIndex, false }, @@ -876,8 +886,8 @@ class UiaTextRangeTests -1, -1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(topRow + 1) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(topRow + 5) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 1) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 5) + lastColumnIndex, false }, @@ -895,8 +905,8 @@ class UiaTextRangeTests -1, -1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex, false }, @@ -914,8 +924,8 @@ class UiaTextRangeTests -1, 0, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + lastColumnIndex, false }, @@ -933,8 +943,8 @@ class UiaTextRangeTests 1, 1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, false }, @@ -952,8 +962,8 @@ class UiaTextRangeTests 1, 0, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, false }, @@ -971,8 +981,8 @@ class UiaTextRangeTests 1, 1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow) + lastColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, true }, @@ -990,8 +1000,8 @@ class UiaTextRangeTests -1, -1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, true } }; @@ -1002,7 +1012,8 @@ class UiaTextRangeTests Log::Comment(std::get<0>(data).c_str()); std::tuple result; int amountMoved; - result = UiaTextRange::_moveEndpointByUnitLine(std::get<2>(data), + result = UiaTextRange::_moveEndpointByUnitLine(_pUiaData, + std::get<2>(data), std::get<4>(data), std::get<1>(data), &amountMoved); @@ -1046,8 +1057,8 @@ class UiaTextRangeTests 1, 1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + firstColumnIndex + 4, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex + 4, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, false }, @@ -1065,8 +1076,8 @@ class UiaTextRangeTests -1, -1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + 4, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + 4, false }, @@ -1084,8 +1095,8 @@ class UiaTextRangeTests 1, 0, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(topRow + 3) + firstColumnIndex + 2, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 3) + firstColumnIndex + 2, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, false }, @@ -1103,8 +1114,8 @@ class UiaTextRangeTests -1, 0, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(topRow + 5) + 6, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow + 5) + 6, false }, @@ -1122,8 +1133,8 @@ class UiaTextRangeTests -1, -1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_End, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + firstColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(topRow) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, topRow) + firstColumnIndex, true }, @@ -1141,8 +1152,8 @@ class UiaTextRangeTests 1, 1, TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow) + lastColumnIndex, - UiaTextRange::_screenInfoRowToEndpoint(bottomRow) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, + UiaTextRange::_screenInfoRowToEndpoint(_pUiaData, bottomRow) + lastColumnIndex, true } }; @@ -1153,7 +1164,8 @@ class UiaTextRangeTests Log::Comment(std::get<0>(data).c_str()); std::tuple result; int amountMoved; - result = UiaTextRange::_moveEndpointByUnitDocument(std::get<2>(data), + result = UiaTextRange::_moveEndpointByUnitDocument(_pUiaData, + std::get<2>(data), std::get<4>(data), std::get<1>(data), &amountMoved); diff --git a/src/interactivity/win32/window.cpp b/src/interactivity/win32/window.cpp index 5156ec90b59..a8451f3b0c9 100644 --- a/src/interactivity/win32/window.cpp +++ b/src/interactivity/win32/window.cpp @@ -11,7 +11,6 @@ #include "windowdpiapi.hpp" #include "windowmetrics.hpp" #include "windowtheme.hpp" -#include "windowUiaProvider.hpp" #include "..\..\host\globals.h" #include "..\..\host\dbcs.h" @@ -32,6 +31,7 @@ #include "..\inc\ServiceLocator.hpp" #include "..\..\types\inc\Viewport.hpp" +#include "..\interactivity\win32\windowUiaProvider.hpp" // The following default masks are used in creating windows // Make sure that these flags match when switching to fullscreen and back @@ -314,16 +314,16 @@ void Window::_UpdateSystemMetrics() const if (useDx) { - status = NTSTATUS_FROM_HRESULT(pDxEngine->SetHwnd(hWnd)); + status = NTSTATUS_FROM_WIN32(HRESULT_CODE((pDxEngine->SetHwnd(hWnd)))); if (NT_SUCCESS(status)) { - status = NTSTATUS_FROM_HRESULT(pDxEngine->Enable()); + status = NTSTATUS_FROM_WIN32(HRESULT_CODE((pDxEngine->Enable()))); } } else { - status = NTSTATUS_FROM_HRESULT(pGdiEngine->SetHwnd(hWnd)); + status = NTSTATUS_FROM_WIN32(HRESULT_CODE((pGdiEngine->SetHwnd(hWnd)))); } if (NT_SUCCESS(status)) @@ -979,7 +979,7 @@ void Window::s_CalculateWindowRect(const COORD coordWindowInChars, prectWindow->bottom = prectWindow->top + RECT_HEIGHT(&rectProposed); } -RECT Window::GetWindowRect() const +RECT Window::GetWindowRect() const noexcept { RECT rc = { 0 }; ::GetWindowRect(GetWindowHandle(), &rc); @@ -1287,7 +1287,7 @@ IRawElementProviderSimple* Window::_GetUiaProvider() { try { - _pUiaProvider = WindowUiaProvider::Create(); + _pUiaProvider = WindowUiaProvider::Create(this); } catch (...) { diff --git a/src/interactivity/win32/window.hpp b/src/interactivity/win32/window.hpp index dc884c008a4..c753d9f345e 100644 --- a/src/interactivity/win32/window.hpp +++ b/src/interactivity/win32/window.hpp @@ -14,13 +14,13 @@ Author(s): --*/ #pragma once -#include "..\inc\IConsoleWindow.hpp" +#include "..\types\IConsoleWindow.hpp" namespace Microsoft::Console::Interactivity::Win32 { class WindowUiaProvider; - class Window final : public IConsoleWindow + class Window final : public Microsoft::Console::Types::IConsoleWindow { public: [[nodiscard]] static NTSTATUS CreateInstance(_In_ Settings* const pSettings, @@ -30,7 +30,7 @@ namespace Microsoft::Console::Interactivity::Win32 ~Window(); - RECT GetWindowRect() const; + RECT GetWindowRect() const noexcept; HWND GetWindowHandle() const; SCREEN_INFORMATION& GetScreenInfo(); const SCREEN_INFORMATION& GetScreenInfo() const; diff --git a/src/interactivity/win32/windowUiaProvider.cpp b/src/interactivity/win32/windowUiaProvider.cpp index 89806da35a2..de320b12760 100644 --- a/src/interactivity/win32/windowUiaProvider.cpp +++ b/src/interactivity/win32/windowUiaProvider.cpp @@ -3,20 +3,18 @@ #include "precomp.h" #include "windowUiaProvider.hpp" -#include "window.hpp" - #include "screenInfoUiaProvider.hpp" -#include "UiaTextRange.hpp" +#include "../types/IUiaData.h" +#include "../host/renderData.hpp" #include "../inc/ServiceLocator.hpp" +using namespace Microsoft::Console::Types; using namespace Microsoft::Console::Interactivity::Win32; -using namespace Microsoft::Console::Interactivity::Win32::WindowUiaProviderTracing; -WindowUiaProvider::WindowUiaProvider() : - _signalEventFiring{}, +WindowUiaProvider::WindowUiaProvider(IConsoleWindow* baseWindow) : _pScreenInfoProvider{ nullptr }, - _cRefs(1) + WindowUiaProviderBase(baseWindow) { } @@ -28,17 +26,23 @@ WindowUiaProvider::~WindowUiaProvider() } } -WindowUiaProvider* WindowUiaProvider::Create() +WindowUiaProvider* WindowUiaProvider::Create(IConsoleWindow* baseWindow) { WindowUiaProvider* pWindowProvider = nullptr; ScreenInfoUiaProvider* pScreenInfoProvider = nullptr; try { - pWindowProvider = new WindowUiaProvider(); - pScreenInfoProvider = new ScreenInfoUiaProvider(pWindowProvider); + pWindowProvider = new WindowUiaProvider(baseWindow); + + Globals& g = ServiceLocator::LocateGlobals(); + CONSOLE_INFORMATION& gci = g.getConsoleInformation(); + IUiaData* uiaData = &gci.renderData; + + pScreenInfoProvider = new ScreenInfoUiaProvider(uiaData, pWindowProvider); pWindowProvider->_pScreenInfoProvider = pScreenInfoProvider; - Tracing::s_TraceUia(pWindowProvider, ApiCall::Create, nullptr); + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(pWindowProvider, ApiCall::Create, nullptr); return pWindowProvider; } @@ -60,6 +64,15 @@ WindowUiaProvider* WindowUiaProvider::Create() } } +[[nodiscard]] HRESULT WindowUiaProvider::SetTextAreaFocus() +{ + try + { + return _pScreenInfoProvider->Signal(UIA_AutomationFocusChangedEventId); + } + CATCH_RETURN(); +} + [[nodiscard]] HRESULT WindowUiaProvider::Signal(_In_ EVENTID id) { HRESULT hr = S_OK; @@ -96,183 +109,13 @@ WindowUiaProvider* WindowUiaProvider::Create() hr = UiaRaiseAutomationEvent(pProvider, id); _signalEventFiring[id] = false; - // tracing - ApiMessageSignal apiMsg; - apiMsg.Signal = id; - Tracing::s_TraceUia(this, ApiCall::Signal, &apiMsg); - return hr; } -[[nodiscard]] HRESULT WindowUiaProvider::SetTextAreaFocus() -{ - try - { - return _pScreenInfoProvider->Signal(UIA_AutomationFocusChangedEventId); - } - CATCH_RETURN(); -} - -#pragma region IUnknown - -IFACEMETHODIMP_(ULONG) -WindowUiaProvider::AddRef() -{ - Tracing::s_TraceUia(this, ApiCall::AddRef, nullptr); - return InterlockedIncrement(&_cRefs); -} - -IFACEMETHODIMP_(ULONG) -WindowUiaProvider::Release() -{ - Tracing::s_TraceUia(this, ApiCall::Release, nullptr); - long val = InterlockedDecrement(&_cRefs); - if (val == 0) - { - delete this; - } - return val; -} - -IFACEMETHODIMP WindowUiaProvider::QueryInterface(_In_ REFIID riid, _COM_Outptr_result_maybenull_ void** ppInterface) -{ - Tracing::s_TraceUia(this, ApiCall::QueryInterface, nullptr); - if (riid == __uuidof(IUnknown)) - { - *ppInterface = static_cast(this); - } - else if (riid == __uuidof(IRawElementProviderSimple)) - { - *ppInterface = static_cast(this); - } - else if (riid == __uuidof(IRawElementProviderFragment)) - { - *ppInterface = static_cast(this); - } - else if (riid == __uuidof(IRawElementProviderFragmentRoot)) - { - *ppInterface = static_cast(this); - } - else - { - *ppInterface = nullptr; - return E_NOINTERFACE; - } - - (static_cast(*ppInterface))->AddRef(); - - return S_OK; -} - -#pragma endregion - -#pragma region IRawElementProviderSimple - -// Implementation of IRawElementProviderSimple::get_ProviderOptions. -// Gets UI Automation provider options. -IFACEMETHODIMP WindowUiaProvider::get_ProviderOptions(_Out_ ProviderOptions* pOptions) -{ - Tracing::s_TraceUia(this, ApiCall::GetProviderOptions, nullptr); - RETURN_IF_FAILED(_EnsureValidHwnd()); - - *pOptions = ProviderOptions_ServerSideProvider; - return S_OK; -} - -// Implementation of IRawElementProviderSimple::get_PatternProvider. -// Gets the object that supports ISelectionPattern. -IFACEMETHODIMP WindowUiaProvider::GetPatternProvider(_In_ PATTERNID /*patternId*/, - _COM_Outptr_result_maybenull_ IUnknown** ppInterface) -{ - Tracing::s_TraceUia(this, ApiCall::GetPatternProvider, nullptr); - *ppInterface = nullptr; - RETURN_IF_FAILED(_EnsureValidHwnd()); - - return S_OK; -} - -// Implementation of IRawElementProviderSimple::get_PropertyValue. -// Gets custom properties. -IFACEMETHODIMP WindowUiaProvider::GetPropertyValue(_In_ PROPERTYID propertyId, _Out_ VARIANT* pVariant) -{ - Tracing::s_TraceUia(this, ApiCall::GetPropertyValue, nullptr); - RETURN_IF_FAILED(_EnsureValidHwnd()); - - pVariant->vt = VT_EMPTY; - - // Returning the default will leave the property as the default - // so we only really need to touch it for the properties we want to implement - if (propertyId == UIA_ControlTypePropertyId) - { - pVariant->vt = VT_I4; - pVariant->lVal = UIA_WindowControlTypeId; - } - else if (propertyId == UIA_AutomationIdPropertyId) - { - pVariant->bstrVal = SysAllocString(L"Console Window"); - if (pVariant->bstrVal != nullptr) - { - pVariant->vt = VT_BSTR; - } - } - else if (propertyId == UIA_IsControlElementPropertyId) - { - pVariant->vt = VT_BOOL; - pVariant->boolVal = VARIANT_TRUE; - } - else if (propertyId == UIA_IsContentElementPropertyId) - { - pVariant->vt = VT_BOOL; - pVariant->boolVal = VARIANT_TRUE; - } - else if (propertyId == UIA_IsKeyboardFocusablePropertyId) - { - pVariant->vt = VT_BOOL; - pVariant->boolVal = VARIANT_TRUE; - } - else if (propertyId == UIA_HasKeyboardFocusPropertyId) - { - pVariant->vt = VT_BOOL; - pVariant->boolVal = VARIANT_TRUE; - } - else if (propertyId == UIA_ProviderDescriptionPropertyId) - { - pVariant->bstrVal = SysAllocString(L"Microsoft Console Host Window"); - if (pVariant->bstrVal != nullptr) - { - pVariant->vt = VT_BSTR; - } - } - - return S_OK; -} - -// Implementation of IRawElementProviderSimple::get_HostRawElementProvider. -// Gets the default UI Automation provider for the host window. This provider -// supplies many properties. -IFACEMETHODIMP WindowUiaProvider::get_HostRawElementProvider(_COM_Outptr_result_maybenull_ IRawElementProviderSimple** ppProvider) -{ - Tracing::s_TraceUia(this, ApiCall::GetHostRawElementProvider, nullptr); - try - { - const HWND hwnd = _GetWindowHandle(); - return UiaHostProviderFromHwnd(hwnd, ppProvider); - } - catch (...) - { - return static_cast(UIA_E_ELEMENTNOTAVAILABLE); - } -} -#pragma endregion - #pragma region IRawElementProviderFragment IFACEMETHODIMP WindowUiaProvider::Navigate(_In_ NavigateDirection direction, _COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider) { - ApiMsgNavigate apiMsg; - apiMsg.Direction = direction; - Tracing::s_TraceUia(this, ApiCall::Navigate, &apiMsg); - RETURN_IF_FAILED(_EnsureValidHwnd()); *ppProvider = nullptr; HRESULT hr = S_OK; @@ -290,60 +133,11 @@ IFACEMETHODIMP WindowUiaProvider::Navigate(_In_ NavigateDirection direction, _CO return hr; } -IFACEMETHODIMP WindowUiaProvider::GetRuntimeId(_Outptr_result_maybenull_ SAFEARRAY** ppRuntimeId) -{ - Tracing::s_TraceUia(this, ApiCall::GetRuntimeId, nullptr); - RETURN_IF_FAILED(_EnsureValidHwnd()); - // Root defers this to host, others must implement it... - *ppRuntimeId = nullptr; - - return S_OK; -} - -IFACEMETHODIMP WindowUiaProvider::get_BoundingRectangle(_Out_ UiaRect* pRect) -{ - Tracing::s_TraceUia(this, ApiCall::GetBoundingRectangle, nullptr); - RETURN_IF_FAILED(_EnsureValidHwnd()); - - const IConsoleWindow* const pIConsoleWindow = _getIConsoleWindow(); - RETURN_HR_IF_NULL((HRESULT)UIA_E_ELEMENTNOTAVAILABLE, pIConsoleWindow); - - RECT const rc = pIConsoleWindow->GetWindowRect(); - - pRect->left = rc.left; - pRect->top = rc.top; - pRect->width = rc.right - rc.left; - pRect->height = rc.bottom - rc.top; - - return S_OK; -} - -IFACEMETHODIMP WindowUiaProvider::GetEmbeddedFragmentRoots(_Outptr_result_maybenull_ SAFEARRAY** ppRoots) -{ - Tracing::s_TraceUia(this, ApiCall::GetEmbeddedFragmentRoots, nullptr); - RETURN_IF_FAILED(_EnsureValidHwnd()); - - *ppRoots = nullptr; - return S_OK; -} - IFACEMETHODIMP WindowUiaProvider::SetFocus() { - Tracing::s_TraceUia(this, ApiCall::SetFocus, nullptr); RETURN_IF_FAILED(_EnsureValidHwnd()); return Signal(UIA_AutomationFocusChangedEventId); } - -IFACEMETHODIMP WindowUiaProvider::get_FragmentRoot(_COM_Outptr_result_maybenull_ IRawElementProviderFragmentRoot** ppProvider) -{ - Tracing::s_TraceUia(this, ApiCall::GetFragmentRoot, nullptr); - RETURN_IF_FAILED(_EnsureValidHwnd()); - - *ppProvider = this; - AddRef(); - return S_OK; -} - #pragma endregion #pragma region IRawElementProviderFragmentRoot @@ -352,7 +146,6 @@ IFACEMETHODIMP WindowUiaProvider::ElementProviderFromPoint(_In_ double /*x*/, _In_ double /*y*/, _COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider) { - Tracing::s_TraceUia(this, ApiCall::ElementProviderFromPoint, nullptr); RETURN_IF_FAILED(_EnsureValidHwnd()); *ppProvider = _pScreenInfoProvider; @@ -363,33 +156,8 @@ IFACEMETHODIMP WindowUiaProvider::ElementProviderFromPoint(_In_ double /*x*/, IFACEMETHODIMP WindowUiaProvider::GetFocus(_COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider) { - Tracing::s_TraceUia(this, ApiCall::GetFocus, nullptr); RETURN_IF_FAILED(_EnsureValidHwnd()); return _pScreenInfoProvider->QueryInterface(IID_PPV_ARGS(ppProvider)); } #pragma endregion - -HWND WindowUiaProvider::_GetWindowHandle() const -{ - IConsoleWindow* const pIConsoleWindow = _getIConsoleWindow(); - THROW_HR_IF_NULL(E_POINTER, pIConsoleWindow); - - return pIConsoleWindow->GetWindowHandle(); -} - -[[nodiscard]] HRESULT WindowUiaProvider::_EnsureValidHwnd() const -{ - try - { - HWND const hwnd = _GetWindowHandle(); - RETURN_HR_IF((HRESULT)UIA_E_ELEMENTNOTAVAILABLE, !(IsWindow(hwnd))); - } - CATCH_RETURN(); - return S_OK; -} - -Microsoft::Console::Interactivity::IConsoleWindow* const WindowUiaProvider::_getIConsoleWindow() -{ - return Microsoft::Console::Interactivity::ServiceLocator::LocateConsoleWindow(); -} diff --git a/src/interactivity/win32/windowUiaProvider.hpp b/src/interactivity/win32/windowUiaProvider.hpp index a14135e21cc..777ad2f8bd7 100644 --- a/src/interactivity/win32/windowUiaProvider.hpp +++ b/src/interactivity/win32/windowUiaProvider.hpp @@ -20,117 +20,43 @@ Author(s): #pragma once #include "precomp.h" +#include "../types/WindowUiaProviderBase.hpp" -namespace Microsoft::Console::Interactivity::Win32 +namespace Microsoft::Console::Types { - // Forward declare, prevent circular ref. - class Window; - class ScreenInfoUiaProvider; + class IConsoleWindow; +} +namespace Microsoft::Console::Interactivity::Win32 +{ class WindowUiaProvider final : - public IRawElementProviderSimple, - public IRawElementProviderFragment, - public IRawElementProviderFragmentRoot + public Microsoft::Console::Types::WindowUiaProviderBase { public: - static WindowUiaProvider* Create(); - virtual ~WindowUiaProvider(); + static WindowUiaProvider* Create(Microsoft::Console::Types::IConsoleWindow* baseWindow); - [[nodiscard]] HRESULT Signal(_In_ EVENTID id); - [[nodiscard]] HRESULT SetTextAreaFocus(); - - // IUnknown methods - IFACEMETHODIMP_(ULONG) - AddRef(); - IFACEMETHODIMP_(ULONG) - Release(); - IFACEMETHODIMP QueryInterface(_In_ REFIID riid, - _COM_Outptr_result_maybenull_ void** ppInterface); - - // IRawElementProviderSimple methods - IFACEMETHODIMP get_ProviderOptions(_Out_ ProviderOptions* pOptions); - IFACEMETHODIMP GetPatternProvider(_In_ PATTERNID iid, - _COM_Outptr_result_maybenull_ IUnknown** ppInterface); - IFACEMETHODIMP GetPropertyValue(_In_ PROPERTYID idProp, - _Out_ VARIANT* pVariant); - IFACEMETHODIMP get_HostRawElementProvider(_COM_Outptr_result_maybenull_ IRawElementProviderSimple** ppProvider); + [[nodiscard]] HRESULT Signal(_In_ EVENTID id) override; + [[nodiscard]] HRESULT SetTextAreaFocus() override; // IRawElementProviderFragment methods IFACEMETHODIMP Navigate(_In_ NavigateDirection direction, - _COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider); - IFACEMETHODIMP GetRuntimeId(_Outptr_result_maybenull_ SAFEARRAY** ppRuntimeId); - IFACEMETHODIMP get_BoundingRectangle(_Out_ UiaRect* pRect); - IFACEMETHODIMP GetEmbeddedFragmentRoots(_Outptr_result_maybenull_ SAFEARRAY** ppRoots); - IFACEMETHODIMP SetFocus(); - IFACEMETHODIMP get_FragmentRoot(_COM_Outptr_result_maybenull_ IRawElementProviderFragmentRoot** ppProvider); + _COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider) override; + IFACEMETHODIMP SetFocus() override; // IRawElementProviderFragmentRoot methods IFACEMETHODIMP ElementProviderFromPoint(_In_ double x, _In_ double y, - _COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider); - IFACEMETHODIMP GetFocus(_COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider); - - private: - WindowUiaProvider(); + _COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider) override; + IFACEMETHODIMP GetFocus(_COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider) override; - HWND _GetWindowHandle() const; - [[nodiscard]] HRESULT _EnsureValidHwnd() const; - static IConsoleWindow* const _getIConsoleWindow(); + protected: + const OLECHAR* AutomationIdPropertyName = L"Console Window"; + const OLECHAR* ProviderDescriptionPropertyName = L"Microsoft Console Host Window"; - // this is used to prevent the object from - // signaling an event while it is already in the - // process of signalling another event. - // This fixes a problem with JAWS where it would - // call a public method that calls - // UiaRaiseAutomationEvent to signal something - // happened, which JAWS then detects the signal - // and calls the same method in response, - // eventually overflowing the stack. - // We aren't using this as a cheap locking - // mechanism for multi-threaded code. - std::map _signalEventFiring; + private: + WindowUiaProvider(Microsoft::Console::Types::IConsoleWindow* baseWindow); + ~WindowUiaProvider(); ScreenInfoUiaProvider* _pScreenInfoProvider; - - // Ref counter for COM object - ULONG _cRefs; }; - - namespace WindowUiaProviderTracing - { - enum class ApiCall - { - Create, - Signal, - AddRef, - Release, - QueryInterface, - GetProviderOptions, - GetPatternProvider, - GetPropertyValue, - GetHostRawElementProvider, - Navigate, - GetRuntimeId, - GetBoundingRectangle, - GetEmbeddedFragmentRoots, - SetFocus, - GetFragmentRoot, - ElementProviderFromPoint, - GetFocus - }; - - struct IApiMsg - { - }; - - struct ApiMessageSignal : public IApiMsg - { - EVENTID Signal; - }; - - struct ApiMsgNavigate : public IApiMsg - { - NavigateDirection Direction; - }; - } } diff --git a/src/interactivity/win32/windowproc.cpp b/src/interactivity/win32/windowproc.cpp index 5957eb65fa6..9292bef9823 100644 --- a/src/interactivity/win32/windowproc.cpp +++ b/src/interactivity/win32/windowproc.cpp @@ -25,9 +25,10 @@ #include "..\inc\ServiceLocator.hpp" -#include "../interactivity/win32/windowtheme.hpp" -#include "../interactivity/win32/windowUiaProvider.hpp" -#include "../interactivity/win32/CustomWindowMessages.h" +#include "..\interactivity\win32\windowtheme.hpp" +#include "..\interactivity\win32\CustomWindowMessages.h" + +#include "..\interactivity\win32\windowUiaProvider.hpp" #include #include diff --git a/src/propsheet/OptionsPage.cpp b/src/propsheet/OptionsPage.cpp index 8ce4a638af1..27561a4fdd4 100644 --- a/src/propsheet/OptionsPage.cpp +++ b/src/propsheet/OptionsPage.cpp @@ -3,6 +3,30 @@ #include "precomp.h" +void InitializeCursorSize(const HWND hOptionsDlg) +{ + unsigned int newRadioValue = IDD_CURSOR_ADVANCED; + if (gpStateInfo->CursorType != 0) + { + // IDD_CURSOR_ADVANCED is used as a placeholder for when a + // non-legacy shape is selected. + newRadioValue = IDD_CURSOR_ADVANCED; + } + else if (gpStateInfo->CursorSize <= 25) + { + newRadioValue = IDD_CURSOR_SMALL; + } + else if (gpStateInfo->CursorSize <= 50) + { + newRadioValue = IDD_CURSOR_MEDIUM; + } + else + { + newRadioValue = IDD_CURSOR_LARGE; + } + CheckRadioButton(hOptionsDlg, IDD_CURSOR_SMALL, IDD_CURSOR_ADVANCED, newRadioValue); +} + bool OptionsCommandCallback(HWND hDlg, const unsigned int Item, const unsigned int Notification, HWND hControlWindow) { UINT Value; @@ -147,6 +171,9 @@ INT_PTR WINAPI SettingsDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lPara switch (wMsg) { case WM_INITDIALOG: + // Initialize the global handle to this dialog + g_hOptionsDlg = hDlg; + CheckDlgButton(hDlg, IDD_HISTORY_NODUP, gpStateInfo->HistoryNoDup); CheckDlgButton(hDlg, IDD_QUICKEDIT, gpStateInfo->QuickEdit); CheckDlgButton(hDlg, IDD_INSERT, gpStateInfo->InsertMode); @@ -167,19 +194,7 @@ INT_PTR WINAPI SettingsDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lPara CreateAndAssociateToolTipToControl(IDD_INTERCEPT_COPY_PASTE, hDlg, IDS_TOOLTIP_INTERCEPT_COPY_PASTE); // initialize cursor radio buttons - if (gpStateInfo->CursorSize <= 25) - { - Item = IDD_CURSOR_SMALL; - } - else if (gpStateInfo->CursorSize <= 50) - { - Item = IDD_CURSOR_MEDIUM; - } - else - { - Item = IDD_CURSOR_LARGE; - } - CheckRadioButton(hDlg, IDD_CURSOR_SMALL, IDD_CURSOR_LARGE, Item); + InitializeCursorSize(hDlg); SetDlgItemInt(hDlg, IDD_HISTORY_SIZE, gpStateInfo->HistoryBufferSize, FALSE); SendDlgItemMessage(hDlg, IDD_HISTORY_SIZE, EM_LIMITTEXT, 3, 0); diff --git a/src/propsheet/OptionsPage.h b/src/propsheet/OptionsPage.h index 21ab26cbe63..d09f1003711 100644 --- a/src/propsheet/OptionsPage.h +++ b/src/propsheet/OptionsPage.h @@ -6,7 +6,7 @@ Module Name: - OptionsPage.h Abstract: -- This module contains the definitions for console options dialog. +- This module contains the definitions for console options dialog. Author(s): Mike Griese (migrie) Oct-2016 @@ -16,3 +16,4 @@ Author(s): void ToggleV2OptionsControls(__in const HWND hDlg); INT_PTR WINAPI SettingsDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam); +void InitializeCursorSize(const HWND hOptionsDlg); diff --git a/src/propsheet/TerminalPage.cpp b/src/propsheet/TerminalPage.cpp index f7e5b0a9dbc..5f645399494 100644 --- a/src/propsheet/TerminalPage.cpp +++ b/src/propsheet/TerminalPage.cpp @@ -3,6 +3,7 @@ #include "precomp.h" #include "TerminalPage.h" +#include "OptionsPage.h" // For InitializeCursorSize #include "ColorControl.h" #include @@ -323,10 +324,22 @@ bool TerminalDlgCommand(const HWND hDlg, const WORD item, const WORD command) no case IDD_TERMINAL_UNDERSCORE: case IDD_TERMINAL_EMPTYBOX: case IDD_TERMINAL_SOLIDBOX: + { gpStateInfo->CursorType = item - IDD_TERMINAL_LEGACY_CURSOR; UpdateApplyButton(hDlg); + + // See GH#1219 - When the cursor state is something other than legacy, + // we need to manually check the "IDD_CURSOR_ADVANCED" radio button on + // the Options page. This will prevent the Options page from manually + // resetting the cursor to legacy. + if (g_hOptionsDlg != INVALID_HANDLE_VALUE) + { + InitializeCursorSize(g_hOptionsDlg); + } + handled = true; break; + } case IDD_DISABLE_SCROLLFORWARD: gpStateInfo->TerminalScrolling = IsDlgButtonChecked(hDlg, IDD_DISABLE_SCROLLFORWARD); UpdateApplyButton(hDlg); diff --git a/src/propsheet/console.rc b/src/propsheet/console.rc index 8ab505a6ede..dd7bc9df01e 100644 --- a/src/propsheet/console.rc +++ b/src/propsheet/console.rc @@ -42,6 +42,8 @@ BEGIN WS_TABSTOP | WS_GROUP AUTORADIOBUTTON "&Medium", IDD_CURSOR_MEDIUM, 14, 33, 84, 10, AUTORADIOBUTTON "&Large", IDD_CURSOR_LARGE, 14, 43, 84, 10, + // IDD_CURSOR_ADVANCED is a hidden control, see GH#1219 + AUTORADIOBUTTON "", IDD_CURSOR_ADVANCED, 14, 53, 0, 0, GROUPBOX "Command History", -1, 115, 11, 120, 56, WS_GROUP LTEXT "&Buffer Size:", -1, 119, 25, 78, 9 @@ -106,6 +108,8 @@ BEGIN WS_TABSTOP | WS_GROUP AUTORADIOBUTTON "&Medium", IDD_CURSOR_MEDIUM, 14, 33, 84, 10, AUTORADIOBUTTON "&Large", IDD_CURSOR_LARGE, 14, 43, 84, 10, + // IDD_CURSOR_ADVANCED is a hidden control, see GH#1219 + AUTORADIOBUTTON "", IDD_CURSOR_ADVANCED, 14, 53, 0, 0, GROUPBOX "Command History", -1, 115, 11, 120, 56, WS_GROUP LTEXT "&Buffer Size:", -1, 119, 25, 78, 9 diff --git a/src/propsheet/dialogs.h b/src/propsheet/dialogs.h index 763aff876ce..952e2ff9a08 100644 --- a/src/propsheet/dialogs.h +++ b/src/propsheet/dialogs.h @@ -43,6 +43,7 @@ Revision History: #define IDD_LANGUAGE_GROUPBOX 116 #define DID_SETTINGS_COMCTL5 117 #define DID_SETTINGS2_COMCTL5 118 +#define IDD_CURSOR_ADVANCED 119 #define DID_FONTDLG 200 #define IDD_STATIC 201 diff --git a/src/propsheet/globals.cpp b/src/propsheet/globals.cpp index a47b0d4bf24..b7c59171cc9 100644 --- a/src/propsheet/globals.cpp +++ b/src/propsheet/globals.cpp @@ -55,3 +55,4 @@ COLORREF g_fakeBackgroundColor = RGB(12, 12, 12); // Default black COLORREF g_fakeCursorColor = RGB(242, 242, 242); // Default bright white HWND g_hTerminalDlg = static_cast(INVALID_HANDLE_VALUE); +HWND g_hOptionsDlg = static_cast(INVALID_HANDLE_VALUE); diff --git a/src/propsheet/globals.h b/src/propsheet/globals.h index 8a75fb1acc1..0d1b2adfc84 100644 --- a/src/propsheet/globals.h +++ b/src/propsheet/globals.h @@ -53,3 +53,4 @@ extern COLORREF g_fakeBackgroundColor; extern COLORREF g_fakeCursorColor; extern HWND g_hTerminalDlg; +extern HWND g_hOptionsDlg; diff --git a/src/propslib/RegistrySerialization.hpp b/src/propslib/RegistrySerialization.hpp index 0f36c74fc20..80cb219eb4a 100644 --- a/src/propslib/RegistrySerialization.hpp +++ b/src/propslib/RegistrySerialization.hpp @@ -98,10 +98,10 @@ class RegistrySerialization } RegPropertyMap; static const RegPropertyMap s_PropertyMappings[]; - static const size_t RegistrySerialization::s_PropertyMappingsSize; + static const size_t s_PropertyMappingsSize; static const RegPropertyMap s_GlobalPropMappings[]; - static const size_t RegistrySerialization::s_GlobalPropMappingsSize; + static const size_t s_GlobalPropMappingsSize; [[nodiscard]] static NTSTATUS s_LoadRegDword(const HKEY hKey, const _RegPropertyMap* const pPropMap, _In_ Settings* const pSettings); [[nodiscard]] static NTSTATUS s_LoadRegString(const HKEY hKey, const _RegPropertyMap* const pPropMap, _In_ Settings* const pSettings); diff --git a/src/renderer/base/lib/base.vcxproj b/src/renderer/base/lib/base.vcxproj index 7ba34072f2a..03f238e9ad6 100644 --- a/src/renderer/base/lib/base.vcxproj +++ b/src/renderer/base/lib/base.vcxproj @@ -22,6 +22,7 @@ + diff --git a/src/renderer/base/lib/base.vcxproj.filters b/src/renderer/base/lib/base.vcxproj.filters index 00f0bb1e6dd..300c6dfc5d4 100644 --- a/src/renderer/base/lib/base.vcxproj.filters +++ b/src/renderer/base/lib/base.vcxproj.filters @@ -80,6 +80,9 @@ Header Files\inc + + Header Files\inc + diff --git a/src/renderer/dx/CustomTextLayout.cpp b/src/renderer/dx/CustomTextLayout.cpp index 94cf4c8e47a..de883e84215 100644 --- a/src/renderer/dx/CustomTextLayout.cpp +++ b/src/renderer/dx/CustomTextLayout.cpp @@ -20,16 +20,16 @@ using namespace Microsoft::Console::Render; // - font - The DirectWrite font face to use while calculating layout (by default, will fallback if necessary) // - clusters - From the backing buffer, the text to be displayed clustered by the columns it should consume. // - width - The count of pixels available per column (the expected pixel width of every column) -CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, - IDWriteTextAnalyzer1* const analyzer, - IDWriteTextFormat* const format, - IDWriteFontFace1* const font, +CustomTextLayout::CustomTextLayout(gsl::not_null const factory, + gsl::not_null const analyzer, + gsl::not_null const format, + gsl::not_null const font, std::basic_string_view const clusters, size_t const width) : - _factory{ factory }, - _analyzer{ analyzer }, - _format{ format }, - _font{ font }, + _factory{ factory.get() }, + _analyzer{ analyzer.get() }, + _format{ format.get() }, + _font{ font.get() }, _localeName{}, _numberSubstitution{}, _readingDirection{ DWRITE_READING_DIRECTION_LEFT_TO_RIGHT }, @@ -39,7 +39,7 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, _width{ width } { // Fetch the locale name out once now from the format - _localeName.resize(format->GetLocaleNameLength() + 1); // +1 for null + _localeName.resize(gsl::narrow_cast(format->GetLocaleNameLength()) + 1); // +1 for null THROW_IF_FAILED(format->GetLocaleName(_localeName.data(), gsl::narrow(_localeName.size()))); for (const auto& cluster : clusters) @@ -58,6 +58,7 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, // - S_OK or suitable DirectX/DirectWrite/Direct2D result code. [[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::GetColumns(_Out_ UINT32* columns) { + RETURN_HR_IF_NULL(E_INVALIDARG, columns); *columns = 0; RETURN_IF_FAILED(_AnalyzeRuns()); @@ -88,7 +89,7 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, [[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::Draw(_In_opt_ void* clientDrawingContext, _In_ IDWriteTextRenderer* renderer, FLOAT originX, - FLOAT originY) + FLOAT originY) noexcept { RETURN_IF_FAILED(_AnalyzeRuns()); RETURN_IF_FAILED(_ShapeGlyphRuns()); @@ -146,7 +147,7 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, } // Resequence the resulting runs in order before returning to caller. - size_t totalRuns = _runs.size(); + const size_t totalRuns = _runs.size(); std::vector runs; runs.resize(totalRuns); @@ -178,7 +179,7 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, const auto textLength = gsl::narrow(_text.size()); // Estimate the maximum number of glyph indices needed to hold a string. - UINT32 estimatedGlyphCount = _EstimateGlyphCount(textLength); + const UINT32 estimatedGlyphCount = _EstimateGlyphCount(textLength); _glyphIndices.resize(estimatedGlyphCount); _glyphOffsets.resize(estimatedGlyphCount); @@ -230,9 +231,9 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, // will shape as if the line is not broken. Run& run = _runs.at(runIndex); - UINT32 textStart = run.textStart; - UINT32 textLength = run.textLength; - UINT32 maxGlyphCount = static_cast(_glyphIndices.size() - glyphStart); + const UINT32 textStart = run.textStart; + const UINT32 textLength = run.textLength; + UINT32 maxGlyphCount = gsl::narrow(_glyphIndices.size() - glyphStart); UINT32 actualGlyphCount = 0; run.glyphStart = glyphStart; @@ -268,7 +269,7 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, do { hr = _analyzer->GetGlyphs( - &_text[textStart], + &_text.at(textStart), textLength, run.fontFace.Get(), run.isSideways, // isSideways, @@ -280,10 +281,10 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, nullptr, // featureLengths 0, // featureCount maxGlyphCount, // maxGlyphCount - &_glyphClusters[textStart], - &textProps[0], - &_glyphIndices[glyphStart], - &glyphProps[0], + &_glyphClusters.at(textStart), + &textProps.at(0), + &_glyphIndices.at(glyphStart), + &glyphProps.at(0), &actualGlyphCount); tries++; @@ -291,7 +292,7 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, { // Try again using a larger buffer. maxGlyphCount = _EstimateGlyphCount(maxGlyphCount); - UINT32 totalGlyphsArrayCount = glyphStart + maxGlyphCount; + const UINT32 totalGlyphsArrayCount = glyphStart + maxGlyphCount; glyphProps.resize(maxGlyphCount); _glyphIndices.resize(totalGlyphsArrayCount); @@ -306,19 +307,19 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, // Get the placement of the all the glyphs. - _glyphAdvances.resize(std::max(static_cast(glyphStart + actualGlyphCount), _glyphAdvances.size())); - _glyphOffsets.resize(std::max(static_cast(glyphStart + actualGlyphCount), _glyphOffsets.size())); + _glyphAdvances.resize(std::max(gsl::narrow_cast(glyphStart) + gsl::narrow_cast(actualGlyphCount), _glyphAdvances.size())); + _glyphOffsets.resize(std::max(gsl::narrow_cast(glyphStart) + gsl::narrow_cast(actualGlyphCount), _glyphOffsets.size())); const auto fontSizeFormat = _format->GetFontSize(); const auto fontSize = fontSizeFormat * run.fontScale; hr = _analyzer->GetGlyphPlacements( - &_text[textStart], - &_glyphClusters[textStart], - &textProps[0], + &_text.at(textStart), + &_glyphClusters.at(textStart), + &textProps.at(0), textLength, - &_glyphIndices[glyphStart], - &glyphProps[0], + &_glyphIndices.at(glyphStart), + &glyphProps.at(0), actualGlyphCount, run.fontFace.Get(), fontSize, @@ -329,8 +330,8 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, NULL, // features NULL, // featureRangeLengths 0, // featureRanges - &_glyphAdvances[glyphStart], - &_glyphOffsets[glyphStart]); + &_glyphAdvances.at(glyphStart), + &_glyphOffsets.at(glyphStart)); RETURN_IF_FAILED(hr); @@ -391,13 +392,13 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, for (auto i = run.glyphStart; i < (run.glyphStart + run.glyphCount); i++) { // Advance is how wide in pixels the glyph is - auto& advance = _glyphAdvances[i]; + auto& advance = _glyphAdvances.at(i); // Offsets is how far to move the origin (in pixels) from where it is - auto& offset = _glyphOffsets[i]; + auto& offset = _glyphOffsets.at(i); // Get how many columns we expected the glyph to have and mutiply into pixels. - const auto columns = _textClusterColumns[i]; + 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. @@ -419,7 +420,7 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, // We need to retrieve the design information for this specific glyph so we can figure out the appropriate // height proportional to the width that we desire. INT32 advanceInDesignUnits; - RETURN_IF_FAILED(run.fontFace->GetDesignGlyphAdvances(1, &_glyphIndices[i], &advanceInDesignUnits)); + RETURN_IF_FAILED(run.fontFace->GetDesignGlyphAdvances(1, &_glyphIndices.at(i), &advanceInDesignUnits)); // When things are drawn, we want the font size (as specified in the base font in the original format) // to be scaled by some factor. @@ -467,6 +468,8 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, IDWriteTextRenderer* renderer, const D2D_POINT_2F origin) noexcept { + RETURN_HR_IF_NULL(E_INVALIDARG, renderer); + try { // We're going to start from the origin given and walk to the right for each @@ -477,21 +480,21 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, for (UINT32 runIndex = 0; runIndex < _runs.size(); ++runIndex) { // Get the run - Run& run = _runs.at(runIndex); + const Run& run = _runs.at(runIndex); // Prepare the glyph run and description objects by converting our // internal storage representation into something that matches DWrite's structures. - DWRITE_GLYPH_RUN glyphRun = { 0 }; + DWRITE_GLYPH_RUN glyphRun; glyphRun.bidiLevel = run.bidiLevel; glyphRun.fontEmSize = _format->GetFontSize() * run.fontScale; glyphRun.fontFace = run.fontFace.Get(); - glyphRun.glyphAdvances = _glyphAdvances.data() + run.glyphStart; + glyphRun.glyphAdvances = &_glyphAdvances.at(run.glyphStart); glyphRun.glyphCount = run.glyphCount; - glyphRun.glyphIndices = _glyphIndices.data() + run.glyphStart; - glyphRun.glyphOffsets = _glyphOffsets.data() + run.glyphStart; + glyphRun.glyphIndices = &_glyphIndices.at(run.glyphStart); + glyphRun.glyphOffsets = &_glyphOffsets.at(run.glyphStart); glyphRun.isSideways = false; - DWRITE_GLYPH_RUN_DESCRIPTION glyphRunDescription = { 0 }; + DWRITE_GLYPH_RUN_DESCRIPTION glyphRunDescription; glyphRunDescription.clusterMap = _glyphClusters.data(); glyphRunDescription.localeName = _localeName.data(); glyphRunDescription.string = _text.data(); @@ -539,7 +542,7 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, // Return Value: // - An estimate of how many glyph spaces may be required in the shaping arrays // to hold the data from a string of the given length. -[[nodiscard]] UINT32 CustomTextLayout::_EstimateGlyphCount(const UINT32 textLength) noexcept +[[nodiscard]] constexpr UINT32 CustomTextLayout::_EstimateGlyphCount(const UINT32 textLength) noexcept { // This formula is from https://docs.microsoft.com/en-us/windows/desktop/api/dwrite/nf-dwrite-idwritetextanalyzer-getglyphs // and is the recommended formula for estimating buffer size for glyph count. @@ -561,12 +564,15 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, _Outptr_result_buffer_(*textLength) WCHAR const** textString, _Out_ UINT32* textLength) { + RETURN_HR_IF_NULL(E_INVALIDARG, textString); + RETURN_HR_IF_NULL(E_INVALIDARG, textLength); + *textString = nullptr; *textLength = 0; if (textPosition < _text.size()) { - *textString = _text.data() + textPosition; + *textString = &_text.at(textPosition); *textLength = gsl::narrow(_text.size()) - textPosition; } @@ -585,8 +591,11 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, // - S_OK or appropriate STL/GSL failure code. [[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::GetTextBeforePosition(UINT32 textPosition, _Outptr_result_buffer_(*textLength) WCHAR const** textString, - _Out_ UINT32* textLength) + _Out_ UINT32* textLength) noexcept { + RETURN_HR_IF_NULL(E_INVALIDARG, textString); + RETURN_HR_IF_NULL(E_INVALIDARG, textLength); + *textString = nullptr; *textLength = 0; @@ -606,7 +615,7 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, // - // Return Value: // - The reading direction held for this layout from construction -[[nodiscard]] DWRITE_READING_DIRECTION STDMETHODCALLTYPE CustomTextLayout::GetParagraphReadingDirection() +[[nodiscard]] DWRITE_READING_DIRECTION STDMETHODCALLTYPE CustomTextLayout::GetParagraphReadingDirection() noexcept { return _readingDirection; } @@ -622,8 +631,11 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, // - S_OK or appropriate STL/GSL failure code. [[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::GetLocaleName(UINT32 textPosition, _Out_ UINT32* textLength, - _Outptr_result_z_ WCHAR const** localeName) + _Outptr_result_z_ WCHAR const** localeName) noexcept { + RETURN_HR_IF_NULL(E_INVALIDARG, textLength); + RETURN_HR_IF_NULL(E_INVALIDARG, localeName); + *localeName = _localeName.data(); *textLength = gsl::narrow(_text.size()) - textPosition; @@ -641,8 +653,11 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, // - S_OK or appropriate STL/GSL failure code. [[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::GetNumberSubstitution(UINT32 textPosition, _Out_ UINT32* textLength, - _COM_Outptr_ IDWriteNumberSubstitution** numberSubstitution) + _COM_Outptr_ IDWriteNumberSubstitution** numberSubstitution) noexcept { + RETURN_HR_IF_NULL(E_INVALIDARG, textLength); + RETURN_HR_IF_NULL(E_INVALIDARG, numberSubstitution); + *numberSubstitution = nullptr; *textLength = gsl::narrow(_text.size()) - textPosition; @@ -799,7 +814,7 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, RETURN_IF_FAILED(format1->GetFontCollection(&collection)); std::wstring familyName; - familyName.resize(format1->GetFontFamilyNameLength() + 1); + familyName.resize(gsl::narrow_cast(format1->GetFontFamilyNameLength()) + 1); RETURN_IF_FAILED(format1->GetFontFamilyName(familyName.data(), gsl::narrow(familyName.size()))); const auto weight = format1->GetFontWeight(); @@ -912,7 +927,7 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, if (textLength < runTextLength) { runTextLength = textLength; // Limit to what's actually left. - UINT32 runTextStart = run.textStart; + const UINT32 runTextStart = run.textStart; _SplitCurrentRun(runTextStart + runTextLength); } @@ -940,12 +955,12 @@ CustomTextLayout::CustomTextLayout(IDWriteFactory1* const factory, // - - Updates internal state void CustomTextLayout::_SetCurrentRun(const UINT32 textPosition) { - if (_runIndex < _runs.size() && _runs[_runIndex].ContainsTextPosition(textPosition)) + if (_runIndex < _runs.size() && _runs.at(_runIndex).ContainsTextPosition(textPosition)) { return; } - _runIndex = static_cast( + _runIndex = gsl::narrow( std::find(_runs.begin(), _runs.end(), textPosition) - _runs.begin()); } @@ -957,13 +972,13 @@ void CustomTextLayout::_SetCurrentRun(const UINT32 textPosition) // - - Updates internal state, the back half will be selected after running void CustomTextLayout::_SplitCurrentRun(const UINT32 splitPosition) { - UINT32 runTextStart = _runs.at(_runIndex).textStart; + const UINT32 runTextStart = _runs.at(_runIndex).textStart; if (splitPosition <= runTextStart) return; // no change // Grow runs by one. - size_t totalRuns = _runs.size(); + const size_t totalRuns = _runs.size(); try { _runs.resize(totalRuns + 1); @@ -974,16 +989,16 @@ void CustomTextLayout::_SplitCurrentRun(const UINT32 splitPosition) } // Copy the old run to the end. - LinkedRun& frontHalf = _runs[_runIndex]; + LinkedRun& frontHalf = _runs.at(_runIndex); LinkedRun& backHalf = _runs.back(); backHalf = frontHalf; // Adjust runs' text positions and lengths. - UINT32 splitPoint = splitPosition - runTextStart; + const UINT32 splitPoint = splitPosition - runTextStart; backHalf.textStart += splitPoint; backHalf.textLength -= splitPoint; frontHalf.textLength = splitPoint; - frontHalf.nextRunIndex = static_cast(totalRuns); - _runIndex = static_cast(totalRuns); + frontHalf.nextRunIndex = gsl::narrow(totalRuns); + _runIndex = gsl::narrow(totalRuns); } #pragma endregion diff --git a/src/renderer/dx/CustomTextLayout.h b/src/renderer/dx/CustomTextLayout.h index 6e57dc5c986..598431b8b74 100644 --- a/src/renderer/dx/CustomTextLayout.h +++ b/src/renderer/dx/CustomTextLayout.h @@ -19,10 +19,10 @@ namespace Microsoft::Console::Render public: // Based on the Windows 7 SDK sample at https://github.com/pauldotknopf/WindowsSDK7-Samples/tree/master/multimedia/DirectWrite/CustomLayout - CustomTextLayout(IDWriteFactory1* const factory, - IDWriteTextAnalyzer1* const analyzer, - IDWriteTextFormat* const format, - IDWriteFontFace1* const font, + CustomTextLayout(gsl::not_null const factory, + gsl::not_null const analyzer, + gsl::not_null const format, + gsl::not_null const font, const std::basic_string_view<::Microsoft::Console::Render::Cluster> clusters, size_t const width); @@ -32,43 +32,43 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT STDMETHODCALLTYPE Draw(_In_opt_ void* clientDrawingContext, _In_ IDWriteTextRenderer* renderer, FLOAT originX, - FLOAT originY); + FLOAT originY) noexcept; // IDWriteTextAnalysisSource methods - [[nodiscard]] virtual HRESULT STDMETHODCALLTYPE GetTextAtPosition(UINT32 textPosition, - _Outptr_result_buffer_(*textLength) WCHAR const** textString, - _Out_ UINT32* textLength) override; - [[nodiscard]] virtual HRESULT STDMETHODCALLTYPE GetTextBeforePosition(UINT32 textPosition, - _Outptr_result_buffer_(*textLength) WCHAR const** textString, - _Out_ UINT32* textLength) override; - [[nodiscard]] virtual DWRITE_READING_DIRECTION STDMETHODCALLTYPE GetParagraphReadingDirection() override; - [[nodiscard]] virtual HRESULT STDMETHODCALLTYPE GetLocaleName(UINT32 textPosition, + [[nodiscard]] HRESULT STDMETHODCALLTYPE GetTextAtPosition(UINT32 textPosition, + _Outptr_result_buffer_(*textLength) WCHAR const** textString, + _Out_ UINT32* textLength) override; + [[nodiscard]] HRESULT STDMETHODCALLTYPE GetTextBeforePosition(UINT32 textPosition, + _Outptr_result_buffer_(*textLength) WCHAR const** textString, + _Out_ UINT32* textLength) noexcept override; + [[nodiscard]] DWRITE_READING_DIRECTION STDMETHODCALLTYPE GetParagraphReadingDirection() noexcept override; + [[nodiscard]] HRESULT STDMETHODCALLTYPE GetLocaleName(UINT32 textPosition, + _Out_ UINT32* textLength, + _Outptr_result_z_ WCHAR const** localeName) noexcept override; + [[nodiscard]] HRESULT STDMETHODCALLTYPE GetNumberSubstitution(UINT32 textPosition, _Out_ UINT32* textLength, - _Outptr_result_z_ WCHAR const** localeName) override; - [[nodiscard]] virtual HRESULT STDMETHODCALLTYPE GetNumberSubstitution(UINT32 textPosition, - _Out_ UINT32* textLength, - _COM_Outptr_ IDWriteNumberSubstitution** numberSubstitution) override; + _COM_Outptr_ IDWriteNumberSubstitution** numberSubstitution) noexcept override; // IDWriteTextAnalysisSink methods - [[nodiscard]] virtual HRESULT STDMETHODCALLTYPE SetScriptAnalysis(UINT32 textPosition, - UINT32 textLength, - _In_ DWRITE_SCRIPT_ANALYSIS const* scriptAnalysis) override; - [[nodiscard]] virtual HRESULT STDMETHODCALLTYPE SetLineBreakpoints(UINT32 textPosition, - UINT32 textLength, - _In_reads_(textLength) DWRITE_LINE_BREAKPOINT const* lineBreakpoints) override; - [[nodiscard]] virtual HRESULT STDMETHODCALLTYPE SetBidiLevel(UINT32 textPosition, - UINT32 textLength, - UINT8 explicitLevel, - UINT8 resolvedLevel) override; - [[nodiscard]] virtual HRESULT STDMETHODCALLTYPE SetNumberSubstitution(UINT32 textPosition, - UINT32 textLength, - _In_ IDWriteNumberSubstitution* numberSubstitution) override; + [[nodiscard]] HRESULT STDMETHODCALLTYPE SetScriptAnalysis(UINT32 textPosition, + UINT32 textLength, + _In_ DWRITE_SCRIPT_ANALYSIS const* scriptAnalysis) override; + [[nodiscard]] HRESULT STDMETHODCALLTYPE SetLineBreakpoints(UINT32 textPosition, + UINT32 textLength, + _In_reads_(textLength) DWRITE_LINE_BREAKPOINT const* lineBreakpoints) override; + [[nodiscard]] HRESULT STDMETHODCALLTYPE SetBidiLevel(UINT32 textPosition, + UINT32 textLength, + UINT8 explicitLevel, + UINT8 resolvedLevel) override; + [[nodiscard]] HRESULT STDMETHODCALLTYPE SetNumberSubstitution(UINT32 textPosition, + UINT32 textLength, + _In_ IDWriteNumberSubstitution* numberSubstitution) override; protected: // A single contiguous run of characters containing the same analysis results. struct Run { - Run() : + Run() noexcept : textStart(), textLength(), glyphStart(), @@ -93,12 +93,12 @@ namespace Microsoft::Console::Render ::Microsoft::WRL::ComPtr fontFace; FLOAT fontScale; - inline bool ContainsTextPosition(UINT32 desiredTextPosition) const + inline bool ContainsTextPosition(UINT32 desiredTextPosition) const noexcept { return desiredTextPosition >= textStart && desiredTextPosition < textStart + textLength; } - inline bool operator==(UINT32 desiredTextPosition) const + inline bool operator==(UINT32 desiredTextPosition) const noexcept { // Search by text position using std::find return ContainsTextPosition(desiredTextPosition); @@ -108,7 +108,7 @@ namespace Microsoft::Console::Render // Single text analysis run, which points to the next run. struct LinkedRun : Run { - LinkedRun() : + LinkedRun() noexcept : nextRunIndex(0) { } @@ -132,7 +132,7 @@ namespace Microsoft::Console::Render IDWriteTextRenderer* renderer, const D2D_POINT_2F origin) noexcept; - [[nodiscard]] static UINT32 _EstimateGlyphCount(const UINT32 textLength) noexcept; + [[nodiscard]] static constexpr UINT32 _EstimateGlyphCount(const UINT32 textLength) noexcept; private: const ::Microsoft::WRL::ComPtr _factory; diff --git a/src/renderer/dx/CustomTextRenderer.cpp b/src/renderer/dx/CustomTextRenderer.cpp index b99c263a635..c0e545faf20 100644 --- a/src/renderer/dx/CustomTextRenderer.cpp +++ b/src/renderer/dx/CustomTextRenderer.cpp @@ -21,8 +21,10 @@ using namespace Microsoft::Console::Render; // Return Value: // - S_OK [[nodiscard]] HRESULT CustomTextRenderer::IsPixelSnappingDisabled(void* /*clientDrawingContext*/, - _Out_ BOOL* isDisabled) + _Out_ BOOL* isDisabled) noexcept { + RETURN_HR_IF_NULL(E_INVALIDARG, isDisabled); + *isDisabled = false; return S_OK; } @@ -38,9 +40,12 @@ using namespace Microsoft::Console::Render; // Return Value: // - S_OK [[nodiscard]] HRESULT CustomTextRenderer::GetPixelsPerDip(void* clientDrawingContext, - _Out_ FLOAT* pixelsPerDip) + _Out_ FLOAT* pixelsPerDip) noexcept { - DrawingContext* drawingContext = static_cast(clientDrawingContext); + RETURN_HR_IF_NULL(E_INVALIDARG, pixelsPerDip); + + const DrawingContext* drawingContext = static_cast(clientDrawingContext); + RETURN_HR_IF_NULL(E_INVALIDARG, drawingContext); float dpiX, dpiY; drawingContext->renderTarget->GetDpi(&dpiX, &dpiY); @@ -58,12 +63,24 @@ using namespace Microsoft::Console::Render; // Return Value: // - S_OK [[nodiscard]] HRESULT CustomTextRenderer::GetCurrentTransform(void* clientDrawingContext, - DWRITE_MATRIX* transform) + DWRITE_MATRIX* transform) noexcept { - DrawingContext* drawingContext = static_cast(clientDrawingContext); + RETURN_HR_IF_NULL(E_INVALIDARG, transform); + + const DrawingContext* drawingContext = static_cast(clientDrawingContext); + RETURN_HR_IF_NULL(E_INVALIDARG, drawingContext); + + // Retrieve as D2D1 matrix then copy into DWRITE matrix. + D2D1_MATRIX_3X2_F d2d1Matrix{ 0 }; + drawingContext->renderTarget->GetTransform(&d2d1Matrix); + + transform->dx = d2d1Matrix.dx; + transform->dy = d2d1Matrix.dy; + transform->m11 = d2d1Matrix.m11; + transform->m12 = d2d1Matrix.m12; + transform->m21 = d2d1Matrix.m21; + transform->m22 = d2d1Matrix.m22; - // Matrix structures are defined identically - drawingContext->renderTarget->GetTransform((D2D1_MATRIX_3X2_F*)transform); return S_OK; } #pragma endregion @@ -88,17 +105,16 @@ using namespace Microsoft::Console::Render; FLOAT baselineOriginX, FLOAT baselineOriginY, _In_ const DWRITE_UNDERLINE* underline, - IUnknown* clientDrawingEffect) + IUnknown* clientDrawingEffect) noexcept { - _FillRectangle(clientDrawingContext, - clientDrawingEffect, - baselineOriginX, - baselineOriginY + underline->offset, - underline->width, - underline->thickness, - underline->readingDirection, - underline->flowDirection); - return S_OK; + return _FillRectangle(clientDrawingContext, + clientDrawingEffect, + baselineOriginX, + baselineOriginY + underline->offset, + underline->width, + underline->thickness, + underline->readingDirection, + underline->flowDirection); } // Routine Description: @@ -120,17 +136,16 @@ using namespace Microsoft::Console::Render; FLOAT baselineOriginX, FLOAT baselineOriginY, _In_ const DWRITE_STRIKETHROUGH* strikethrough, - IUnknown* clientDrawingEffect) + IUnknown* clientDrawingEffect) noexcept { - _FillRectangle(clientDrawingContext, - clientDrawingEffect, - baselineOriginX, - baselineOriginY + strikethrough->offset, - strikethrough->width, - strikethrough->thickness, - strikethrough->readingDirection, - strikethrough->flowDirection); - return S_OK; + return _FillRectangle(clientDrawingContext, + clientDrawingEffect, + baselineOriginX, + baselineOriginY + strikethrough->offset, + strikethrough->width, + strikethrough->thickness, + strikethrough->readingDirection, + strikethrough->flowDirection); } // Routine Description: @@ -146,16 +161,17 @@ using namespace Microsoft::Console::Render; // - flowDirection - textual flow information that could affect the rectangle // Return Value: // - S_OK -void CustomTextRenderer::_FillRectangle(void* clientDrawingContext, - IUnknown* clientDrawingEffect, - float x, - float y, - float width, - float thickness, - DWRITE_READING_DIRECTION /*readingDirection*/, - DWRITE_FLOW_DIRECTION /*flowDirection*/) +[[nodiscard]] HRESULT CustomTextRenderer::_FillRectangle(void* clientDrawingContext, + IUnknown* clientDrawingEffect, + float x, + float y, + float width, + float thickness, + DWRITE_READING_DIRECTION /*readingDirection*/, + DWRITE_FLOW_DIRECTION /*flowDirection*/) noexcept { DrawingContext* drawingContext = static_cast(clientDrawingContext); + RETURN_HR_IF_NULL(E_INVALIDARG, drawingContext); // Get brush ID2D1Brush* brush = drawingContext->foregroundBrush; @@ -165,8 +181,10 @@ void CustomTextRenderer::_FillRectangle(void* clientDrawingContext, brush = static_cast(clientDrawingEffect); } - D2D1_RECT_F rect = D2D1::RectF(x, y, x + width, y + thickness); + const D2D1_RECT_F rect = D2D1::RectF(x, y, x + width, y + thickness); drawingContext->renderTarget->FillRectangle(&rect, brush); + + return S_OK; } // Routine Description: @@ -189,8 +207,10 @@ void CustomTextRenderer::_FillRectangle(void* clientDrawingContext, IDWriteInlineObject* inlineObject, BOOL isSideways, BOOL isRightToLeft, - IUnknown* clientDrawingEffect) + IUnknown* clientDrawingEffect) noexcept { + RETURN_HR_IF_NULL(E_INVALIDARG, inlineObject); + return inlineObject->Draw(clientDrawingContext, this, originX, @@ -233,12 +253,11 @@ void CustomTextRenderer::_FillRectangle(void* clientDrawingContext, // Since we've delegated the drawing of the background of the text into this function, the origin passed in isn't actually the baseline. // It's the top left corner. Save that off first. - D2D1_POINT_2F origin = D2D1::Point2F(baselineOriginX, baselineOriginY); + const D2D1_POINT_2F origin = D2D1::Point2F(baselineOriginX, baselineOriginY); // Then make a copy for the baseline origin (which is part way down the left side of the text, not the top or bottom). // We'll use this baseline Origin for drawing the actual text. - D2D1_POINT_2F baselineOrigin = origin; - baselineOrigin.y += drawingContext->spacing.baseline; + const D2D1_POINT_2F baselineOrigin{ origin.x, origin.y + drawingContext->spacing.baseline }; ::Microsoft::WRL::ComPtr d2dContext; RETURN_IF_FAILED(drawingContext->renderTarget->QueryInterface(d2dContext.GetAddressOf())); @@ -249,11 +268,9 @@ void CustomTextRenderer::_FillRectangle(void* clientDrawingContext, rect.bottom = rect.top + drawingContext->cellSize.height; rect.left = origin.x; rect.right = rect.left; + const auto advancesSpan = gsl::make_span(glyphRun->glyphAdvances, glyphRun->glyphCount); - for (UINT32 i = 0; i < glyphRun->glyphCount; i++) - { - rect.right += glyphRun->glyphAdvances[i]; - } + rect.right = std::accumulate(advancesSpan.cbegin(), advancesSpan.cend(), rect.right); d2dContext->FillRectangle(rect, drawingContext->backgroundBrush); @@ -270,7 +287,7 @@ void CustomTextRenderer::_FillRectangle(void* clientDrawingContext, RETURN_IF_FAILED(drawingContext->dwriteFactory->QueryInterface(dwriteFactory4.GetAddressOf())); // The list of glyph image formats this renderer is prepared to support. - DWRITE_GLYPH_IMAGE_FORMATS supportedFormats = + const DWRITE_GLYPH_IMAGE_FORMATS supportedFormats = DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE | DWRITE_GLYPH_IMAGE_FORMATS_CFF | DWRITE_GLYPH_IMAGE_FORMATS_COLR | @@ -283,14 +300,14 @@ void CustomTextRenderer::_FillRectangle(void* clientDrawingContext, // Determine whether there are any color glyph runs within glyphRun. If // there are, glyphRunEnumerator can be used to iterate through them. ::Microsoft::WRL::ComPtr glyphRunEnumerator; - HRESULT hr = dwriteFactory4->TranslateColorGlyphRun(baselineOrigin, - glyphRun, - glyphRunDescription, - supportedFormats, - measuringMode, - nullptr, - 0, - &glyphRunEnumerator); + const HRESULT hr = dwriteFactory4->TranslateColorGlyphRun(baselineOrigin, + glyphRun, + glyphRunDescription, + supportedFormats, + measuringMode, + nullptr, + 0, + &glyphRunEnumerator); // If the analysis found no color glyphs in the run, just draw normally. if (hr == DWRITE_E_NOCOLOR) @@ -320,7 +337,7 @@ void CustomTextRenderer::_FillRectangle(void* clientDrawingContext, DWRITE_COLOR_GLYPH_RUN1 const* colorRun; RETURN_IF_FAILED(glyphRunEnumerator->GetCurrentRun(&colorRun)); - D2D1_POINT_2F currentBaselineOrigin = D2D1::Point2F(colorRun->baselineOriginX, colorRun->baselineOriginY); + const D2D1_POINT_2F currentBaselineOrigin = D2D1::Point2F(colorRun->baselineOriginX, colorRun->baselineOriginY); switch (colorRun->glyphImageFormat) { @@ -357,7 +374,7 @@ void CustomTextRenderer::_FillRectangle(void* clientDrawingContext, // This run is solid-color outlines, either from non-color // glyphs or from COLR glyph layers. Use Direct2D to draw them. - ID2D1Brush* layerBrush; + ID2D1Brush* layerBrush{ nullptr }; // The rule is "if 0xffff, use current brush." See: // https://docs.microsoft.com/en-us/windows/desktop/api/dwrite_2/ns-dwrite_2-dwrite_color_glyph_run if (colorRun->paletteIndex == 0xFFFF) @@ -414,6 +431,11 @@ void CustomTextRenderer::_FillRectangle(void* clientDrawingContext, _In_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription, ID2D1Brush* brush) { + RETURN_HR_IF_NULL(E_INVALIDARG, clientDrawingContext); + RETURN_HR_IF_NULL(E_INVALIDARG, glyphRun); + RETURN_HR_IF_NULL(E_INVALIDARG, glyphRunDescription); + RETURN_HR_IF_NULL(E_INVALIDARG, brush); + ::Microsoft::WRL::ComPtr d2dContext; RETURN_IF_FAILED(clientDrawingContext->renderTarget->QueryInterface(d2dContext.GetAddressOf())); @@ -433,8 +455,11 @@ void CustomTextRenderer::_FillRectangle(void* clientDrawingContext, D2D1_POINT_2F baselineOrigin, DWRITE_MEASURING_MODE /*measuringMode*/, _In_ const DWRITE_GLYPH_RUN* glyphRun, - _In_ const DWRITE_GLYPH_RUN_DESCRIPTION* /*glyphRunDescription*/) + _In_ const DWRITE_GLYPH_RUN_DESCRIPTION* /*glyphRunDescription*/) noexcept { + RETURN_HR_IF_NULL(E_INVALIDARG, clientDrawingContext); + RETURN_HR_IF_NULL(E_INVALIDARG, glyphRun); + // This is regular text but manually ::Microsoft::WRL::ComPtr d2dFactory; clientDrawingContext->renderTarget->GetFactory(d2dFactory.GetAddressOf()); @@ -473,8 +498,11 @@ void CustomTextRenderer::_FillRectangle(void* clientDrawingContext, D2D1_POINT_2F baselineOrigin, DWRITE_MEASURING_MODE /*measuringMode*/, _In_ const DWRITE_GLYPH_RUN* glyphRun, - _In_ const DWRITE_GLYPH_RUN_DESCRIPTION* /*glyphRunDescription*/) + _In_ const DWRITE_GLYPH_RUN_DESCRIPTION* /*glyphRunDescription*/) noexcept { + RETURN_HR_IF_NULL(E_INVALIDARG, clientDrawingContext); + RETURN_HR_IF_NULL(E_INVALIDARG, glyphRun); + // This is glow text manually ::Microsoft::WRL::ComPtr d2dFactory; clientDrawingContext->renderTarget->GetFactory(d2dFactory.GetAddressOf()); diff --git a/src/renderer/dx/CustomTextRenderer.h b/src/renderer/dx/CustomTextRenderer.h index f64623c11e8..c3e2fc5219b 100644 --- a/src/renderer/dx/CustomTextRenderer.h +++ b/src/renderer/dx/CustomTextRenderer.h @@ -15,7 +15,7 @@ namespace Microsoft::Console::Render IDWriteFactory* dwriteFactory, const DWRITE_LINE_SPACING spacing, const D2D_SIZE_F cellSize, - const D2D1_DRAW_TEXT_OPTIONS options = D2D1_DRAW_TEXT_OPTIONS_NONE) + const D2D1_DRAW_TEXT_OPTIONS options = D2D1_DRAW_TEXT_OPTIONS_NONE) noexcept { this->renderTarget = renderTarget; this->foregroundBrush = foregroundBrush; @@ -42,53 +42,53 @@ namespace Microsoft::Console::Render // https://docs.microsoft.com/en-us/windows/desktop/DirectWrite/how-to-implement-a-custom-text-renderer // IDWritePixelSnapping methods - [[nodiscard]] virtual HRESULT STDMETHODCALLTYPE IsPixelSnappingDisabled(void* clientDrawingContext, - _Out_ BOOL* isDisabled) override; + [[nodiscard]] HRESULT STDMETHODCALLTYPE IsPixelSnappingDisabled(void* clientDrawingContext, + _Out_ BOOL* isDisabled) noexcept override; - [[nodiscard]] virtual HRESULT STDMETHODCALLTYPE GetPixelsPerDip(void* clientDrawingContext, - _Out_ FLOAT* pixelsPerDip) override; + [[nodiscard]] HRESULT STDMETHODCALLTYPE GetPixelsPerDip(void* clientDrawingContext, + _Out_ FLOAT* pixelsPerDip) noexcept override; - [[nodiscard]] virtual HRESULT STDMETHODCALLTYPE GetCurrentTransform(void* clientDrawingContext, - _Out_ DWRITE_MATRIX* transform) override; + [[nodiscard]] HRESULT STDMETHODCALLTYPE GetCurrentTransform(void* clientDrawingContext, + _Out_ DWRITE_MATRIX* transform) noexcept override; // IDWriteTextRenderer methods - [[nodiscard]] virtual HRESULT STDMETHODCALLTYPE DrawGlyphRun(void* clientDrawingContext, - FLOAT baselineOriginX, - FLOAT baselineOriginY, - DWRITE_MEASURING_MODE measuringMode, - _In_ const DWRITE_GLYPH_RUN* glyphRun, - _In_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription, - IUnknown* clientDrawingEffect) override; - - [[nodiscard]] virtual HRESULT STDMETHODCALLTYPE DrawUnderline(void* clientDrawingContext, - FLOAT baselineOriginX, - FLOAT baselineOriginY, - _In_ const DWRITE_UNDERLINE* underline, - IUnknown* clientDrawingEffect) override; - - [[nodiscard]] virtual HRESULT STDMETHODCALLTYPE DrawStrikethrough(void* clientDrawingContext, - FLOAT baselineOriginX, - FLOAT baselineOriginY, - _In_ const DWRITE_STRIKETHROUGH* strikethrough, - IUnknown* clientDrawingEffect) override; - - [[nodiscard]] virtual HRESULT STDMETHODCALLTYPE DrawInlineObject(void* clientDrawingContext, - FLOAT originX, - FLOAT originY, - IDWriteInlineObject* inlineObject, - BOOL isSideways, - BOOL isRightToLeft, - IUnknown* clientDrawingEffect) override; + [[nodiscard]] HRESULT STDMETHODCALLTYPE DrawGlyphRun(void* clientDrawingContext, + FLOAT baselineOriginX, + FLOAT baselineOriginY, + DWRITE_MEASURING_MODE measuringMode, + _In_ const DWRITE_GLYPH_RUN* glyphRun, + _In_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription, + IUnknown* clientDrawingEffect) override; + + [[nodiscard]] HRESULT STDMETHODCALLTYPE DrawUnderline(void* clientDrawingContext, + FLOAT baselineOriginX, + FLOAT baselineOriginY, + _In_ const DWRITE_UNDERLINE* underline, + IUnknown* clientDrawingEffect) noexcept override; + + [[nodiscard]] HRESULT STDMETHODCALLTYPE DrawStrikethrough(void* clientDrawingContext, + FLOAT baselineOriginX, + FLOAT baselineOriginY, + _In_ const DWRITE_STRIKETHROUGH* strikethrough, + IUnknown* clientDrawingEffect) noexcept override; + + [[nodiscard]] HRESULT STDMETHODCALLTYPE DrawInlineObject(void* clientDrawingContext, + FLOAT originX, + FLOAT originY, + IDWriteInlineObject* inlineObject, + BOOL isSideways, + BOOL isRightToLeft, + IUnknown* clientDrawingEffect) noexcept override; private: - void _FillRectangle(void* clientDrawingContext, - IUnknown* clientDrawingEffect, - float x, - float y, - float width, - float thickness, - DWRITE_READING_DIRECTION readingDirection, - DWRITE_FLOW_DIRECTION flowDirection); + [[nodiscard]] HRESULT _FillRectangle(void* clientDrawingContext, + IUnknown* clientDrawingEffect, + float x, + float y, + float width, + float thickness, + DWRITE_READING_DIRECTION readingDirection, + DWRITE_FLOW_DIRECTION flowDirection) noexcept; [[nodiscard]] HRESULT _DrawBasicGlyphRun(DrawingContext* clientDrawingContext, D2D1_POINT_2F baselineOrigin, @@ -101,12 +101,12 @@ namespace Microsoft::Console::Render D2D1_POINT_2F baselineOrigin, DWRITE_MEASURING_MODE measuringMode, _In_ const DWRITE_GLYPH_RUN* glyphRun, - _In_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription); + _In_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription) noexcept; [[nodiscard]] HRESULT _DrawGlowGlyphRun(DrawingContext* clientDrawingContext, D2D1_POINT_2F baselineOrigin, DWRITE_MEASURING_MODE measuringMode, _In_ const DWRITE_GLYPH_RUN* glyphRun, - _In_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription); + _In_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription) noexcept; }; } diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp index f59f02c9603..e8cb5589de6 100644 --- a/src/renderer/dx/DxRenderer.cpp +++ b/src/renderer/dx/DxRenderer.cpp @@ -15,6 +15,8 @@ #pragma hdrstop static constexpr float POINTS_PER_INCH = 72.0f; +static constexpr std::wstring_view FALLBACK_FONT_FACE = L"Consolas"; +static constexpr std::wstring_view FALLBACK_LOCALE = L"en-us"; using namespace Microsoft::Console::Render; using namespace Microsoft::Console::Types; @@ -22,6 +24,8 @@ using namespace Microsoft::Console::Types; // Routine Description: // - Constructs a DirectX-based renderer for console text // which primarily uses DirectWrite on a Direct2D surface +#pragma warning(suppress : 26455) +// TODO GH 2683: The default constructor should not throw. DxEngine::DxEngine() : RenderEngineBase(), _isInvalidUsed{ false }, @@ -127,57 +131,55 @@ DxEngine::~DxEngine() _ReleaseDeviceResources(); } - auto freeOnFail = wil::scope_exit([&] { _ReleaseDeviceResources(); }); + auto freeOnFail = wil::scope_exit([&]() noexcept { _ReleaseDeviceResources(); }); RETURN_IF_FAILED(CreateDXGIFactory1(IID_PPV_ARGS(&_dxgiFactory2))); const DWORD DeviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT | // clang-format off - // This causes problems for folks who do not have the whole DirectX SDK installed - // when they try to run the rest of the project in debug mode. - // As such, I'm leaving this flag here for people doing DX-specific work to toggle it - // only when they need it and shutting it off otherwise. - // Find out more about the debug layer here: - // https://docs.microsoft.com/en-us/windows/desktop/direct3d11/overviews-direct3d-11-devices-layers - // You can find out how to install it here: - // https://docs.microsoft.com/en-us/windows/uwp/gaming/use-the-directx-runtime-and-visual-studio-graphics-diagnostic-features +// This causes problems for folks who do not have the whole DirectX SDK installed +// when they try to run the rest of the project in debug mode. +// As such, I'm leaving this flag here for people doing DX-specific work to toggle it +// only when they need it and shutting it off otherwise. +// Find out more about the debug layer here: +// https://docs.microsoft.com/en-us/windows/desktop/direct3d11/overviews-direct3d-11-devices-layers +// You can find out how to install it here: +// https://docs.microsoft.com/en-us/windows/uwp/gaming/use-the-directx-runtime-and-visual-studio-graphics-diagnostic-features // clang-format on // D3D11_CREATE_DEVICE_DEBUG | D3D11_CREATE_DEVICE_SINGLETHREADED; - D3D_FEATURE_LEVEL FeatureLevels[] = { - D3D_FEATURE_LEVEL_11_1, - D3D_FEATURE_LEVEL_11_0, - D3D_FEATURE_LEVEL_10_1, - D3D_FEATURE_LEVEL_10_0, - D3D_FEATURE_LEVEL_9_1, - }; + const std::array FeatureLevels{ D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, + D3D_FEATURE_LEVEL_9_1 }; // Trying hardware first for maximum performance, then trying WARP (software) renderer second // in case we're running inside a downlevel VM where hardware passthrough isn't enabled like // for Windows 7 in a VM. - const auto hardwareResult = D3D11CreateDevice(NULL, + const auto hardwareResult = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, - NULL, + nullptr, DeviceFlags, - FeatureLevels, - ARRAYSIZE(FeatureLevels), + FeatureLevels.data(), + gsl::narrow_cast(FeatureLevels.size()), D3D11_SDK_VERSION, &_d3dDevice, - NULL, + nullptr, &_d3dDeviceContext); if (FAILED(hardwareResult)) { - RETURN_IF_FAILED(D3D11CreateDevice(NULL, + RETURN_IF_FAILED(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_WARP, - NULL, + nullptr, DeviceFlags, - FeatureLevels, - ARRAYSIZE(FeatureLevels), + FeatureLevels.data(), + gsl::narrow_cast(FeatureLevels.size()), D3D11_SDK_VERSION, &_d3dDevice, - NULL, + nullptr, &_d3dDeviceContext)); } @@ -194,58 +196,62 @@ DxEngine::~DxEngine() SwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; SwapChainDesc.Scaling = DXGI_SCALING_NONE; - switch (_chainMode) + try { - case SwapChainMode::ForHwnd: - { - // use the HWND's dimensions for the swap chain dimensions. - RECT rect = { 0 }; - RETURN_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &rect)); - - SwapChainDesc.Width = rect.right - rect.left; - SwapChainDesc.Height = rect.bottom - rect.top; - - // We can't do alpha for HWNDs. Set to ignore. It will fail otherwise. - SwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; - const auto createSwapChainResult = _dxgiFactory2->CreateSwapChainForHwnd(_d3dDevice.Get(), - _hwndTarget, - &SwapChainDesc, - nullptr, - nullptr, - &_dxgiSwapChain); - if (FAILED(createSwapChainResult)) + switch (_chainMode) { - SwapChainDesc.Scaling = DXGI_SCALING_STRETCH; - RETURN_IF_FAILED(_dxgiFactory2->CreateSwapChainForHwnd(_d3dDevice.Get(), - _hwndTarget, - &SwapChainDesc, - nullptr, - nullptr, - &_dxgiSwapChain)); + case SwapChainMode::ForHwnd: + { + // use the HWND's dimensions for the swap chain dimensions. + RECT rect = { 0 }; + RETURN_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &rect)); + + SwapChainDesc.Width = rect.right - rect.left; + SwapChainDesc.Height = rect.bottom - rect.top; + + // We can't do alpha for HWNDs. Set to ignore. It will fail otherwise. + SwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; + const auto createSwapChainResult = _dxgiFactory2->CreateSwapChainForHwnd(_d3dDevice.Get(), + _hwndTarget, + &SwapChainDesc, + nullptr, + nullptr, + &_dxgiSwapChain); + if (FAILED(createSwapChainResult)) + { + SwapChainDesc.Scaling = DXGI_SCALING_STRETCH; + RETURN_IF_FAILED(_dxgiFactory2->CreateSwapChainForHwnd(_d3dDevice.Get(), + _hwndTarget, + &SwapChainDesc, + nullptr, + nullptr, + &_dxgiSwapChain)); + } + + break; } + case SwapChainMode::ForComposition: + { + // Use the given target size for compositions. + SwapChainDesc.Width = _displaySizePixels.cx; + SwapChainDesc.Height = _displaySizePixels.cy; - break; - } - case SwapChainMode::ForComposition: - { - // Use the given target size for compositions. - SwapChainDesc.Width = _displaySizePixels.cx; - SwapChainDesc.Height = _displaySizePixels.cy; - - // We're doing advanced composition pretty much for the purpose of pretty alpha, so turn it on. - SwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED; - // It's 100% required to use scaling mode stretch for composition. There is no other choice. - SwapChainDesc.Scaling = DXGI_SCALING_STRETCH; - - RETURN_IF_FAILED(_dxgiFactory2->CreateSwapChainForComposition(_d3dDevice.Get(), - &SwapChainDesc, - nullptr, - &_dxgiSwapChain)); - break; - } - default: - THROW_HR(E_NOTIMPL); + // We're doing advanced composition pretty much for the purpose of pretty alpha, so turn it on. + SwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED; + // It's 100% required to use scaling mode stretch for composition. There is no other choice. + SwapChainDesc.Scaling = DXGI_SCALING_STRETCH; + + RETURN_IF_FAILED(_dxgiFactory2->CreateSwapChainForComposition(_d3dDevice.Get(), + &SwapChainDesc, + nullptr, + &_dxgiSwapChain)); + break; + } + default: + THROW_HR(E_NOTIMPL); + } } + CATCH_RETURN(); // With a new swap chain, mark the entire thing as invalid. RETURN_IF_FAILED(InvalidateAll()); @@ -263,9 +269,14 @@ DxEngine::~DxEngine() freeOnFail.release(); // don't need to release if we made it to the bottom and everything was good. // Notify that swap chain changed. + if (_pfn) { - _pfn(); + try + { + _pfn(); + } + CATCH_LOG(); // A failure in the notification function isn't a failure to prepare, so just log it and go on. } return S_OK; @@ -273,43 +284,57 @@ DxEngine::~DxEngine() [[nodiscard]] HRESULT DxEngine::_PrepareRenderTarget() noexcept { - RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(&_dxgiSurface))); - - D2D1_RENDER_TARGET_PROPERTIES props = - D2D1::RenderTargetProperties( - D2D1_RENDER_TARGET_TYPE_DEFAULT, - D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED), - 0.0f, - 0.0f); - - RETURN_IF_FAILED(_d2dFactory->CreateDxgiSurfaceRenderTarget(_dxgiSurface.Get(), - &props, - &_d2dRenderTarget)); - - _d2dRenderTarget->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE); - RETURN_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::DarkRed), - &_d2dBrushBackground)); - - RETURN_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), - &_d2dBrushForeground)); - - // If in composition mode, apply scaling factor matrix - if (_chainMode == SwapChainMode::ForComposition) + try { - const auto fdpi = static_cast(_dpi); - _d2dRenderTarget->SetDpi(fdpi, fdpi); + RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(&_dxgiSurface))); + + const D2D1_RENDER_TARGET_PROPERTIES props = + D2D1::RenderTargetProperties( + D2D1_RENDER_TARGET_TYPE_DEFAULT, + D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED), + 0.0f, + 0.0f); + + RETURN_IF_FAILED(_d2dFactory->CreateDxgiSurfaceRenderTarget(_dxgiSurface.Get(), + &props, + &_d2dRenderTarget)); + + _d2dRenderTarget->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE); + RETURN_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::DarkRed), + &_d2dBrushBackground)); + + RETURN_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), + &_d2dBrushForeground)); + + const D2D1_STROKE_STYLE_PROPERTIES strokeStyleProperties{ + D2D1_CAP_STYLE_SQUARE, // startCap + D2D1_CAP_STYLE_SQUARE, // endCap + D2D1_CAP_STYLE_SQUARE, // dashCap + D2D1_LINE_JOIN_MITER, // lineJoin + 0.f, // miterLimit + D2D1_DASH_STYLE_SOLID, // dashStyle + 0.f, // dashOffset + }; + RETURN_IF_FAILED(_d2dFactory->CreateStrokeStyle(&strokeStyleProperties, nullptr, 0, &_strokeStyle)); + + // If in composition mode, apply scaling factor matrix + if (_chainMode == SwapChainMode::ForComposition) + { + const auto fdpi = static_cast(_dpi); + _d2dRenderTarget->SetDpi(fdpi, fdpi); - DXGI_MATRIX_3X2_F inverseScale = { 0 }; - inverseScale._11 = 1.0f / _scale; - inverseScale._22 = inverseScale._11; + DXGI_MATRIX_3X2_F inverseScale = { 0 }; + inverseScale._11 = 1.0f / _scale; + inverseScale._22 = inverseScale._11; - ::Microsoft::WRL::ComPtr sc2; - RETURN_IF_FAILED(_dxgiSwapChain.As(&sc2)); + ::Microsoft::WRL::ComPtr sc2; + RETURN_IF_FAILED(_dxgiSwapChain.As(&sc2)); - RETURN_IF_FAILED(sc2->SetMatrixTransform(&inverseScale)); + RETURN_IF_FAILED(sc2->SetMatrixTransform(&inverseScale)); + } + return S_OK; } - - return S_OK; + CATCH_RETURN(); } // Routine Description: @@ -320,31 +345,35 @@ DxEngine::~DxEngine() // - void DxEngine::_ReleaseDeviceResources() noexcept { - _haveDeviceResources = false; - _d2dBrushForeground.Reset(); - _d2dBrushBackground.Reset(); - - if (nullptr != _d2dRenderTarget.Get() && _isPainting) + try { - _d2dRenderTarget->EndDraw(); - } + _haveDeviceResources = false; + _d2dBrushForeground.Reset(); + _d2dBrushBackground.Reset(); - _d2dRenderTarget.Reset(); + if (nullptr != _d2dRenderTarget.Get() && _isPainting) + { + _d2dRenderTarget->EndDraw(); + } - _dxgiSurface.Reset(); - _dxgiSwapChain.Reset(); + _d2dRenderTarget.Reset(); - if (nullptr != _d3dDeviceContext.Get()) - { - // To ensure the swap chain goes away we must unbind any views from the - // D3D pipeline - _d3dDeviceContext->OMSetRenderTargets(0, nullptr, nullptr); - } - _d3dDeviceContext.Reset(); + _dxgiSurface.Reset(); + _dxgiSwapChain.Reset(); + + if (nullptr != _d3dDeviceContext.Get()) + { + // To ensure the swap chain goes away we must unbind any views from the + // D3D pipeline + _d3dDeviceContext->OMSetRenderTargets(0, nullptr, nullptr); + } + _d3dDeviceContext.Reset(); - _d3dDevice.Reset(); + _d3dDevice.Reset(); - _dxgiFactory2.Reset(); + _dxgiFactory2.Reset(); + } + CATCH_LOG(); } // Routine Description: @@ -362,10 +391,10 @@ void DxEngine::_ReleaseDeviceResources() noexcept _Out_ IDWriteTextLayout** ppTextLayout) noexcept { return _dwriteFactory->CreateTextLayout(string, - static_cast(stringLength), + gsl::narrow(stringLength), _dwriteTextFormat.Get(), - (float)_displaySizePixels.cx, - _glyphCell.cy != 0 ? _glyphCell.cy : (float)_displaySizePixels.cy, + gsl::narrow(_displaySizePixels.cx), + _glyphCell.cy != 0 ? _glyphCell.cy : gsl::narrow(_displaySizePixels.cy), ppTextLayout); } @@ -397,7 +426,7 @@ void DxEngine::SetCallback(std::function pfn) _pfn = pfn; } -Microsoft::WRL::ComPtr DxEngine::GetSwapChain() noexcept +Microsoft::WRL::ComPtr DxEngine::GetSwapChain() { if (_dxgiSwapChain.Get() == nullptr) { @@ -415,6 +444,8 @@ Microsoft::WRL::ComPtr DxEngine::GetSwapChain() noexcept // - S_OK [[nodiscard]] HRESULT DxEngine::Invalidate(const SMALL_RECT* const psrRegion) noexcept { + RETURN_HR_IF_NULL(E_INVALIDARG, psrRegion); + _InvalidOr(*psrRegion); return S_OK; } @@ -427,7 +458,9 @@ Microsoft::WRL::ComPtr DxEngine::GetSwapChain() noexcept // - S_OK [[nodiscard]] HRESULT DxEngine::InvalidateCursor(const COORD* const pcoordCursor) noexcept { - SMALL_RECT sr = Microsoft::Console::Types::Viewport::FromCoord(*pcoordCursor).ToInclusive(); + RETURN_HR_IF_NULL(E_INVALIDARG, pcoordCursor); + + const SMALL_RECT sr = Microsoft::Console::Types::Viewport::FromCoord(*pcoordCursor).ToInclusive(); return Invalidate(&sr); } @@ -439,6 +472,8 @@ Microsoft::WRL::ComPtr DxEngine::GetSwapChain() noexcept // - S_OK [[nodiscard]] HRESULT DxEngine::InvalidateSystem(const RECT* const prcDirtyClient) noexcept { + RETURN_HR_IF_NULL(E_INVALIDARG, prcDirtyClient); + _InvalidOr(*prcDirtyClient); return S_OK; @@ -471,39 +506,43 @@ Microsoft::WRL::ComPtr DxEngine::GetSwapChain() noexcept { if (pcoordDelta->X != 0 || pcoordDelta->Y != 0) { - POINT delta = { 0 }; - delta.x = pcoordDelta->X * _glyphCell.cx; - delta.y = pcoordDelta->Y * _glyphCell.cy; + try + { + POINT delta = { 0 }; + delta.x = pcoordDelta->X * _glyphCell.cx; + delta.y = pcoordDelta->Y * _glyphCell.cy; - _InvalidOffset(delta); + _InvalidOffset(delta); - _invalidScroll.cx += delta.x; - _invalidScroll.cy += delta.y; + _invalidScroll.cx += delta.x; + _invalidScroll.cy += delta.y; - // Add the revealed portion of the screen from the scroll to the invalid area. - const RECT display = _GetDisplayRect(); - RECT reveal = display; + // Add the revealed portion of the screen from the scroll to the invalid area. + const RECT display = _GetDisplayRect(); + RECT reveal = display; - // X delta first - OffsetRect(&reveal, delta.x, 0); - IntersectRect(&reveal, &reveal, &display); - SubtractRect(&reveal, &display, &reveal); + // X delta first + OffsetRect(&reveal, delta.x, 0); + IntersectRect(&reveal, &reveal, &display); + SubtractRect(&reveal, &display, &reveal); - if (!IsRectEmpty(&reveal)) - { - _InvalidOr(reveal); - } + if (!IsRectEmpty(&reveal)) + { + _InvalidOr(reveal); + } - // Y delta second (subtract rect won't work if you move both) - reveal = display; - OffsetRect(&reveal, 0, delta.y); - IntersectRect(&reveal, &reveal, &display); - SubtractRect(&reveal, &display, &reveal); + // Y delta second (subtract rect won't work if you move both) + reveal = display; + OffsetRect(&reveal, 0, delta.y); + IntersectRect(&reveal, &reveal, &display); + SubtractRect(&reveal, &display, &reveal); - if (!IsRectEmpty(&reveal)) - { - _InvalidOr(reveal); + if (!IsRectEmpty(&reveal)) + { + _InvalidOr(reveal); + } } + CATCH_RETURN(); } return S_OK; @@ -531,6 +570,8 @@ Microsoft::WRL::ComPtr DxEngine::GetSwapChain() noexcept // - S_FALSE because we don't use this. [[nodiscard]] HRESULT DxEngine::InvalidateCircling(_Out_ bool* const pForcePaint) noexcept { + RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint); + *pForcePaint = false; return S_FALSE; } @@ -564,7 +605,7 @@ Microsoft::WRL::ComPtr DxEngine::GetSwapChain() noexcept return size; } default: - THROW_HR(E_NOTIMPL); + FAIL_FAST_HR(E_NOTIMPL); } } @@ -604,7 +645,7 @@ void _ScaleByFont(RECT& cellsToPixels, SIZE fontSize) noexcept // - -Y is up, Y is down, -X is left, X is right. // Return Value: // - -void DxEngine::_InvalidOffset(POINT delta) noexcept +void DxEngine::_InvalidOffset(POINT delta) { if (_isInvalidUsed) { @@ -658,7 +699,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept { UnionRect(&_invalidRect, &_invalidRect, &rc); - RECT rcScreen = _GetDisplayRect(); + const RECT rcScreen = _GetDisplayRect(); IntersectRect(&_invalidRect, &_invalidRect, &rcScreen); } else @@ -676,6 +717,8 @@ void DxEngine::_InvalidOr(RECT rc) noexcept // - S_FALSE because this is unused. [[nodiscard]] HRESULT DxEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept { + RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint); + *pForcePaint = false; return S_FALSE; } @@ -693,37 +736,41 @@ void DxEngine::_InvalidOr(RECT rc) noexcept if (_isEnabled) { - const auto clientSize = _GetClientSize(); - if (!_haveDeviceResources) + try { - RETURN_IF_FAILED(_CreateDeviceResources(true)); - } - else if (_displaySizePixels.cy != clientSize.cy || - _displaySizePixels.cx != clientSize.cx) - { - // OK, we're going to play a dangerous game here for the sake of optimizing resize - // First, set up a complete clear of all device resources if something goes terribly wrong. - auto resetDeviceResourcesOnFailure = wil::scope_exit([&] { - _ReleaseDeviceResources(); - }); + const auto clientSize = _GetClientSize(); + if (!_haveDeviceResources) + { + RETURN_IF_FAILED(_CreateDeviceResources(true)); + } + else if (_displaySizePixels.cy != clientSize.cy || + _displaySizePixels.cx != clientSize.cx) + { + // OK, we're going to play a dangerous game here for the sake of optimizing resize + // First, set up a complete clear of all device resources if something goes terribly wrong. + auto resetDeviceResourcesOnFailure = wil::scope_exit([&]() noexcept { + _ReleaseDeviceResources(); + }); - // Now let go of a few of the device resources that get in the way of resizing buffers in the swap chain - _dxgiSurface.Reset(); - _d2dRenderTarget.Reset(); + // Now let go of a few of the device resources that get in the way of resizing buffers in the swap chain + _dxgiSurface.Reset(); + _d2dRenderTarget.Reset(); - // Change the buffer size and recreate the render target (and surface) - RETURN_IF_FAILED(_dxgiSwapChain->ResizeBuffers(2, clientSize.cx, clientSize.cy, DXGI_FORMAT_B8G8R8A8_UNORM, 0)); - RETURN_IF_FAILED(_PrepareRenderTarget()); + // Change the buffer size and recreate the render target (and surface) + RETURN_IF_FAILED(_dxgiSwapChain->ResizeBuffers(2, clientSize.cx, clientSize.cy, DXGI_FORMAT_B8G8R8A8_UNORM, 0)); + RETURN_IF_FAILED(_PrepareRenderTarget()); - // OK we made it past the parts that can cause errors. We can release our failure handler. - resetDeviceResourcesOnFailure.release(); + // OK we made it past the parts that can cause errors. We can release our failure handler. + resetDeviceResourcesOnFailure.release(); - // And persist the new size. - _displaySizePixels = clientSize; - } + // And persist the new size. + _displaySizePixels = clientSize; + } - _d2dRenderTarget->BeginDraw(); - _isPainting = true; + _d2dRenderTarget->BeginDraw(); + _isPainting = true; + } + CATCH_RETURN(); } return S_OK; @@ -753,7 +800,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept { _presentDirty = _invalidRect; - RECT display = _GetDisplayRect(); + const RECT display = _GetDisplayRect(); SubtractRect(&_presentScroll, &display, &_presentDirty); _presentOffset.x = _invalidScroll.cx; _presentOffset.y = _invalidScroll.cy; @@ -798,13 +845,17 @@ void DxEngine::_InvalidOr(RECT rc) noexcept // - Any DirectX error, a memory error, etc. [[nodiscard]] HRESULT DxEngine::_CopyFrontToBack() noexcept { - Microsoft::WRL::ComPtr backBuffer; - Microsoft::WRL::ComPtr frontBuffer; + try + { + Microsoft::WRL::ComPtr backBuffer; + Microsoft::WRL::ComPtr frontBuffer; - RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer))); - RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(1, IID_PPV_ARGS(&frontBuffer))); + RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer))); + RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(1, IID_PPV_ARGS(&frontBuffer))); - _d3dDeviceContext->CopyResource(backBuffer.Get(), frontBuffer.Get()); + _d3dDeviceContext->CopyResource(backBuffer.Get(), frontBuffer.Get()); + } + CATCH_RETURN(); return S_OK; } @@ -820,16 +871,20 @@ void DxEngine::_InvalidOr(RECT rc) noexcept { if (_presentReady) { - FAIL_FAST_IF_FAILED(_dxgiSwapChain->Present(1, 0)); - /*FAIL_FAST_IF_FAILED(_dxgiSwapChain->Present1(1, 0, &_presentParams));*/ + try + { + FAIL_FAST_IF_FAILED(_dxgiSwapChain->Present(1, 0)); + /*FAIL_FAST_IF_FAILED(_dxgiSwapChain->Present1(1, 0, &_presentParams));*/ - RETURN_IF_FAILED(_CopyFrontToBack()); - _presentReady = false; + RETURN_IF_FAILED(_CopyFrontToBack()); + _presentReady = false; - _presentDirty = { 0 }; - _presentOffset = { 0 }; - _presentScroll = { 0 }; - _presentParams = { 0 }; + _presentDirty = { 0 }; + _presentOffset = { 0 }; + _presentScroll = { 0 }; + _presentParams = { 0 }; + } + CATCH_RETURN(); } return S_OK; @@ -932,9 +987,9 @@ void DxEngine::_InvalidOr(RECT rc) noexcept COORD const coordTarget) noexcept { const auto existingColor = _d2dBrushForeground->GetColor(); - const auto restoreBrushOnExit = wil::scope_exit([&] { _d2dBrushForeground->SetColor(existingColor); }); + const auto restoreBrushOnExit = wil::scope_exit([&]() noexcept { _d2dBrushForeground->SetColor(existingColor); }); - _d2dBrushForeground->SetColor(D2D1::ColorF(color)); + _d2dBrushForeground->SetColor(_ColorFFromColorRef(color)); const auto font = _GetFontSize(); D2D_POINT_2F target; @@ -946,14 +1001,15 @@ void DxEngine::_InvalidOr(RECT rc) noexcept for (size_t i = 0; i < cchLine; i++) { - start = target; + // 0.5 pixel offset for crisp lines + start = { target.x + 0.5f, target.y + 0.5f }; if (lines & GridLines::Top) { end = start; end.x += font.X; - _d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get()); + _d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get(), 1.0f, _strokeStyle.Get()); } if (lines & GridLines::Left) @@ -961,7 +1017,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept end = start; end.y += font.Y; - _d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get()); + _d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get(), 1.0f, _strokeStyle.Get()); } // NOTE: Watch out for inclusive/exclusive rectangles here. @@ -971,26 +1027,25 @@ void DxEngine::_InvalidOr(RECT rc) noexcept // The bottom left corner inclusive is at 0,15 which is Y (0) + Font Height (16) - 1 = 15. // The top right corner inclusive is at 7,0 which is X (0) + Font Height (8) - 1 = 7. - start = target; - start.y += font.Y - 1; + // 0.5 pixel offset for crisp lines; -0.5 on the Y to fit _in_ the cell, not outside it. + start = { target.x + 0.5f, target.y + font.Y - 0.5f }; if (lines & GridLines::Bottom) { end = start; - end.x += font.X; + end.x += font.X - 1.f; - _d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get()); + _d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get(), 1.0f, _strokeStyle.Get()); } - start = target; - start.x += font.X - 1; + start = { target.x + font.X - 0.5f, target.y + 0.5f }; if (lines & GridLines::Right) { end = start; - end.y += font.Y; + end.y += font.Y - 1.f; - _d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get()); + _d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get(), 1.0f, _strokeStyle.Get()); } // Move to the next character in this run. @@ -1015,7 +1070,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept 0.5f); _d2dBrushForeground->SetColor(selectionColor); - const auto resetColorOnExit = wil::scope_exit([&] { _d2dBrushForeground->SetColor(existingColor); }); + const auto resetColorOnExit = wil::scope_exit([&]() noexcept { _d2dBrushForeground->SetColor(existingColor); }); RECT pixels; pixels.left = rect.Left * _glyphCell.cx; @@ -1076,7 +1131,8 @@ enum class CursorPaintType { // Enforce min/max cursor height ULONG ulHeight = std::clamp(options.ulCursorHeightPercent, s_ulMinCursorHeightPercent, s_ulMaxCursorHeightPercent); - ulHeight = (ULONG)((_glyphCell.cy * ulHeight) / 100); + + ulHeight = gsl::narrow((_glyphCell.cy * ulHeight) / 100); rect.top = rect.bottom - ulHeight; break; } @@ -1123,6 +1179,13 @@ enum class CursorPaintType } case CursorPaintType::Outline: { + // DrawRectangle in straddles physical pixels in an attempt to draw a line + // between them. To avoid this, bump the rectangle around by half the stroke width. + rect.top += 0.5f; + rect.left += 0.5f; + rect.bottom -= 0.5f; + rect.right -= 0.5f; + _d2dRenderTarget->DrawRectangle(rect, brush.Get()); break; } @@ -1182,25 +1245,29 @@ enum class CursorPaintType // - S_OK or relevant DirectX error [[nodiscard]] HRESULT DxEngine::UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo) noexcept { - const auto hr = _GetProposedFont(pfiFontInfoDesired, - fiFontInfo, - _dpi, - _dwriteTextFormat, - _dwriteTextAnalyzer, - _dwriteFontFace); + RETURN_IF_FAILED(_GetProposedFont(pfiFontInfoDesired, + fiFontInfo, + _dpi, + _dwriteTextFormat, + _dwriteTextAnalyzer, + _dwriteFontFace)); - const auto size = fiFontInfo.GetSize(); + try + { + const auto size = fiFontInfo.GetSize(); - _glyphCell.cx = size.X; - _glyphCell.cy = size.Y; + _glyphCell.cx = size.X; + _glyphCell.cy = size.Y; + } + CATCH_RETURN(); - return hr; + return S_OK; } [[nodiscard]] Viewport DxEngine::GetViewportInCharacters(const Viewport& viewInPixels) noexcept { - short widthInChars = static_cast(viewInPixels.Width() / _glyphCell.cx); - short heightInChars = static_cast(viewInPixels.Height() / _glyphCell.cy); + const short widthInChars = gsl::narrow_cast(viewInPixels.Width() / _glyphCell.cx); + const short heightInChars = gsl::narrow_cast(viewInPixels.Height() / _glyphCell.cy); return Viewport::FromDimensions(viewInPixels.Origin(), { widthInChars, heightInChars }); } @@ -1277,13 +1344,13 @@ float DxEngine::GetScaling() const noexcept // - // Return Value: // - Rectangle describing dirty area in characters. -[[nodiscard]] SMALL_RECT DxEngine::GetDirtyRectInChars() noexcept +[[nodiscard]] SMALL_RECT DxEngine::GetDirtyRectInChars() { SMALL_RECT r; - r.Top = (SHORT)(floor(_invalidRect.top / _glyphCell.cy)); - r.Left = (SHORT)(floor(_invalidRect.left / _glyphCell.cx)); - r.Bottom = (SHORT)(floor(_invalidRect.bottom / _glyphCell.cy)); - r.Right = (SHORT)(floor(_invalidRect.right / _glyphCell.cx)); + r.Top = gsl::narrow(floor(_invalidRect.top / _glyphCell.cy)); + r.Left = gsl::narrow(floor(_invalidRect.left / _glyphCell.cx)); + r.Bottom = gsl::narrow(floor(_invalidRect.bottom / _glyphCell.cy)); + r.Right = gsl::narrow(floor(_invalidRect.right / _glyphCell.cx)); // Exclusive to inclusive r.Bottom--; @@ -1301,7 +1368,7 @@ float DxEngine::GetScaling() const noexcept // - Nearest integer short x and y values for each cell. [[nodiscard]] COORD DxEngine::_GetFontSize() const noexcept { - return { (SHORT)(_glyphCell.cx), (SHORT)(_glyphCell.cy) }; + return { gsl::narrow(_glyphCell.cx), gsl::narrow(_glyphCell.cy) }; } // Routine Description: @@ -1325,20 +1392,26 @@ float DxEngine::GetScaling() const noexcept // - S_OK or relevant DirectWrite error. [[nodiscard]] HRESULT DxEngine::IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept { - Cluster cluster(glyph, 0); // columns don't matter, we're doing analysis not layout. + RETURN_HR_IF_NULL(E_INVALIDARG, pResult); - // Create the text layout - CustomTextLayout layout(_dwriteFactory.Get(), - _dwriteTextAnalyzer.Get(), - _dwriteTextFormat.Get(), - _dwriteFontFace.Get(), - { &cluster, 1 }, - _glyphCell.cx); + try + { + const Cluster cluster(glyph, 0); // columns don't matter, we're doing analysis not layout. - UINT32 columns = 0; - RETURN_IF_FAILED(layout.GetColumns(&columns)); + // Create the text layout + CustomTextLayout layout(_dwriteFactory.Get(), + _dwriteTextAnalyzer.Get(), + _dwriteTextFormat.Get(), + _dwriteFontFace.Get(), + { &cluster, 1 }, + _glyphCell.cx); - *pResult = columns != 1; + UINT32 columns = 0; + RETURN_IF_FAILED(layout.GetColumns(&columns)); + + *pResult = columns != 1; + } + CATCH_RETURN(); return S_OK; } @@ -1351,7 +1424,47 @@ float DxEngine::GetScaling() const noexcept // - S_OK [[nodiscard]] HRESULT DxEngine::_DoUpdateTitle(_In_ const std::wstring& /*newTitle*/) noexcept { - return PostMessageW(_hwndTarget, CM_UPDATE_TITLE, 0, (LPARAM) nullptr) ? S_OK : E_FAIL; + return PostMessageW(_hwndTarget, CM_UPDATE_TITLE, 0, 0) ? S_OK : E_FAIL; +} + +// Routine Description: +// - Attempts to locate the font given, but then begins falling back if we cannot find it. +// - We'll try to fall back to Consolas with the given weight/stretch/style first, +// then try Consolas again with normal weight/stretch/style, +// and if nothing works, then we'll throw an error. +// Arguments: +// - familyName - The font name we should be looking for +// - weight - The weight (bold, light, etc.) +// - stretch - The stretch of the font is the spacing between each letter +// - style - Normal, italic, etc. +// Return Value: +// - Smart pointer holding interface reference for queryable font data. +[[nodiscard]] Microsoft::WRL::ComPtr DxEngine::_ResolveFontFaceWithFallback(std::wstring& familyName, + DWRITE_FONT_WEIGHT& weight, + DWRITE_FONT_STRETCH& stretch, + DWRITE_FONT_STYLE& style, + std::wstring& localeName) const +{ + auto face = _FindFontFace(familyName, weight, stretch, style, localeName); + + if (!face) + { + familyName = FALLBACK_FONT_FACE; + face = _FindFontFace(familyName, weight, stretch, style, localeName); + } + + if (!face) + { + familyName = FALLBACK_FONT_FACE; + weight = DWRITE_FONT_WEIGHT_NORMAL; + stretch = DWRITE_FONT_STRETCH_NORMAL; + style = DWRITE_FONT_STYLE_NORMAL; + face = _FindFontFace(familyName, weight, stretch, style, localeName); + } + + THROW_IF_NULL_ALLOC(face); + + return face; } // Routine Description: @@ -1363,10 +1476,11 @@ float DxEngine::GetScaling() const noexcept // - style - Normal, italic, etc. // Return Value: // - Smart pointer holding interface reference for queryable font data. -[[nodiscard]] Microsoft::WRL::ComPtr DxEngine::_FindFontFace(const std::wstring& familyName, - DWRITE_FONT_WEIGHT weight, - DWRITE_FONT_STRETCH stretch, - DWRITE_FONT_STYLE style) const +[[nodiscard]] Microsoft::WRL::ComPtr DxEngine::_FindFontFace(std::wstring& familyName, + DWRITE_FONT_WEIGHT& weight, + DWRITE_FONT_STRETCH& stretch, + DWRITE_FONT_STYLE& style, + std::wstring& localeName) const { Microsoft::WRL::ComPtr fontFace; @@ -1375,7 +1489,7 @@ float DxEngine::GetScaling() const noexcept UINT32 familyIndex; BOOL familyExists; - THROW_IF_FAILED(fontCollection->FindFamilyName(familyName.c_str(), &familyIndex, &familyExists)); + THROW_IF_FAILED(fontCollection->FindFamilyName(familyName.data(), &familyIndex, &familyExists)); if (familyExists) { @@ -1389,11 +1503,112 @@ float DxEngine::GetScaling() const noexcept THROW_IF_FAILED(font->CreateFontFace(&fontFace0)); THROW_IF_FAILED(fontFace0.As(&fontFace)); + + // Retrieve metrics in case the font we created was different than what was requested. + weight = font->GetWeight(); + stretch = font->GetStretch(); + style = font->GetStyle(); + + // Dig the family name out at the end to return it. + familyName = _GetFontFamilyName(fontFamily.Get(), localeName); } return fontFace; } +// Routine Description: +// - Helper to retrieve the user's locale preference or fallback to the default. +// Arguments: +// - +// Return Value: +// - A locale that can be used on construction of assorted DX objects that want to know one. +[[nodiscard]] std::wstring DxEngine::_GetLocaleName() const +{ + std::array localeName; + + const auto returnCode = GetUserDefaultLocaleName(localeName.data(), gsl::narrow(localeName.size())); + if (returnCode) + { + return { localeName.data() }; + } + else + { + return { FALLBACK_LOCALE.data(), FALLBACK_LOCALE.size() }; + } +} + +// Routine Description: +// - Retrieves the font family name out of the given object in the given locale. +// - If we can't find a valid name for the given locale, we'll fallback and report it back. +// Arguments: +// - fontFamily - DirectWrite font family object +// - localeName - The locale in which the name should be retrieved. +// - If fallback occurred, this is updated to what we retrieved instead. +// Return Value: +// - Localized string name of the font family +[[nodiscard]] std::wstring DxEngine::_GetFontFamilyName(gsl::not_null const fontFamily, + std::wstring& localeName) const +{ + // See: https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontcollection + Microsoft::WRL::ComPtr familyNames; + THROW_IF_FAILED(fontFamily->GetFamilyNames(&familyNames)); + + // First we have to find the right family name for the locale. We're going to bias toward what the caller + // requested, but fallback if we need to and reply with the locale we ended up choosing. + UINT32 index = 0; + BOOL exists = false; + + // This returns S_OK whether or not it finds a locale name. Check exists field instead. + // If it returns an error, it's a real problem, not an absence of this locale name. + // https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-findlocalename + THROW_IF_FAILED(familyNames->FindLocaleName(localeName.data(), &index, &exists)); + + // If we tried and it still doesn't exist, try with the fallback locale. + if (!exists) + { + localeName = FALLBACK_LOCALE; + THROW_IF_FAILED(familyNames->FindLocaleName(localeName.data(), &index, &exists)); + } + + // If it still doesn't exist, we're going to try index 0. + if (!exists) + { + index = 0; + + // Get the locale name out so at least the caller knows what locale this name goes with. + UINT32 length = 0; + THROW_IF_FAILED(familyNames->GetLocaleNameLength(index, &length)); + + // https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-getlocalenamelength + // https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-getlocalename + // GetLocaleNameLength does not include space for null terminator, but GetLocaleName needs it so add one. + length++; + + localeName.resize(length); + + THROW_IF_FAILED(familyNames->GetLocaleName(index, localeName.data(), length)); + } + + // OK, now that we've decided which family name and the locale that it's in... let's go get it. + UINT32 length = 0; + THROW_IF_FAILED(familyNames->GetStringLength(index, &length)); + + // https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-getstringlength + // https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-getstring + // Once again, GetStringLength is without the null, but GetString needs the null. So add one. + length++; + + // Make our output buffer and resize it so it is allocated. + std::wstring retVal; + retVal.resize(length); + + // FINALLY, go fetch the string name. + THROW_IF_FAILED(familyNames->GetString(index, retVal.data(), length)); + + // and return it. + return retVal; +} + // Routine Description: // - Updates the font used for drawing // Arguments: @@ -1411,13 +1626,13 @@ float DxEngine::GetScaling() const noexcept { try { - const std::wstring fontName(desired.GetFaceName()); - const DWRITE_FONT_WEIGHT weight = DWRITE_FONT_WEIGHT_NORMAL; - const DWRITE_FONT_STYLE style = DWRITE_FONT_STYLE_NORMAL; - const DWRITE_FONT_STRETCH stretch = DWRITE_FONT_STRETCH_NORMAL; + std::wstring fontName(desired.GetFaceName()); + DWRITE_FONT_WEIGHT weight = DWRITE_FONT_WEIGHT_NORMAL; + DWRITE_FONT_STYLE style = DWRITE_FONT_STYLE_NORMAL; + DWRITE_FONT_STRETCH stretch = DWRITE_FONT_STRETCH_NORMAL; + std::wstring localeName = _GetLocaleName(); - const auto face = _FindFontFace(fontName, weight, stretch, style); - THROW_IF_NULL_ALLOC_MSG(face, "Failed to find the requested font"); + const auto face = _ResolveFontFaceWithFallback(fontName, weight, stretch, style, localeName); DWRITE_FONT_METRICS1 fontMetrics; face->GetMetrics(&fontMetrics); @@ -1508,7 +1723,7 @@ float DxEngine::GetScaling() const noexcept style, stretch, fontSize, - L"", + localeName.data(), &format)); THROW_IF_FAILED(format.As(&textFormat)); @@ -1529,20 +1744,16 @@ float DxEngine::GetScaling() const noexcept coordSize.X = gsl::narrow(widthExact); coordSize.Y = gsl::narrow(lineSpacing.height); - const auto familyNameLength = textFormat->GetFontFamilyNameLength() + 1; // 1 for space for null - const auto familyNameBuffer = std::make_unique(familyNameLength); - THROW_IF_FAILED(textFormat->GetFontFamilyName(familyNameBuffer.get(), familyNameLength)); - const DWORD weightDword = static_cast(textFormat->GetFontWeight()); // Unscaled is for the purposes of re-communicating this font back to the renderer again later. // As such, we need to give the same original size parameter back here without padding // or rounding or scaling manipulation. - COORD unscaled = desired.GetEngineSize(); + const COORD unscaled = desired.GetEngineSize(); - COORD scaled = coordSize; + const COORD scaled = coordSize; - actual.SetFromEngine(familyNameBuffer.get(), + actual.SetFromEngine(fontName.data(), desired.GetFamily(), weightDword, false, @@ -1580,22 +1791,6 @@ float DxEngine::GetScaling() const noexcept return D2D1::ColorF(rgb, aFloat); } default: - THROW_HR(E_NOTIMPL); + FAIL_FAST_HR(E_NOTIMPL); } } - -// Routine Description: -// - Helps convert a Direct2D ColorF into a DXGI RGBA -// Arguments: -// - color - Direct2D Color F -// Return Value: -// - DXGI RGBA -[[nodiscard]] DXGI_RGBA DxEngine::s_RgbaFromColorF(const D2D1_COLOR_F color) noexcept -{ - DXGI_RGBA rgba; - rgba.a = color.a; - rgba.b = color.b; - rgba.g = color.g; - rgba.r = color.r; - return rgba; -} diff --git a/src/renderer/dx/DxRenderer.hpp b/src/renderer/dx/DxRenderer.hpp index 8a9335401ea..f0960120316 100644 --- a/src/renderer/dx/DxRenderer.hpp +++ b/src/renderer/dx/DxRenderer.hpp @@ -31,7 +31,11 @@ namespace Microsoft::Console::Render { public: DxEngine(); - virtual ~DxEngine() override; + ~DxEngine(); + DxEngine(const DxEngine&) = default; + DxEngine(DxEngine&&) = default; + DxEngine& operator=(const DxEngine&) = default; + DxEngine& operator=(DxEngine&&) = default; // Used to release device resources so that another instance of // conhost can render to the screen (i.e. only one DirectX @@ -45,7 +49,7 @@ namespace Microsoft::Console::Render void SetCallback(std::function pfn); - ::Microsoft::WRL::ComPtr GetSwapChain() noexcept; + ::Microsoft::WRL::ComPtr GetSwapChain(); // IRenderEngine Members [[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override; @@ -84,7 +88,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override; - [[nodiscard]] SMALL_RECT GetDirtyRectInChars() noexcept override; + [[nodiscard]] SMALL_RECT GetDirtyRectInChars() override; [[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override; [[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override; @@ -133,7 +137,7 @@ namespace Microsoft::Console::Render void _InvalidOr(SMALL_RECT sr) noexcept; void _InvalidOr(RECT rc) noexcept; - void _InvalidOffset(POINT pt) noexcept; + void _InvalidOffset(POINT pt); bool _presentReady; RECT _presentDirty; @@ -151,6 +155,7 @@ namespace Microsoft::Console::Render ::Microsoft::WRL::ComPtr _dwriteFontFace; ::Microsoft::WRL::ComPtr _dwriteTextAnalyzer; ::Microsoft::WRL::ComPtr _customRenderer; + ::Microsoft::WRL::ComPtr _strokeStyle; // Device-Dependent Resources bool _haveDeviceResources; @@ -178,10 +183,22 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT _EnableDisplayAccess(const bool outputEnabled) noexcept; - [[nodiscard]] ::Microsoft::WRL::ComPtr _FindFontFace(const std::wstring& familyName, - DWRITE_FONT_WEIGHT weight, - DWRITE_FONT_STRETCH stretch, - DWRITE_FONT_STYLE style) const; + [[nodiscard]] ::Microsoft::WRL::ComPtr _ResolveFontFaceWithFallback(std::wstring& familyName, + DWRITE_FONT_WEIGHT& weight, + DWRITE_FONT_STRETCH& stretch, + DWRITE_FONT_STYLE& style, + std::wstring& localeName) const; + + [[nodiscard]] ::Microsoft::WRL::ComPtr _FindFontFace(std::wstring& familyName, + DWRITE_FONT_WEIGHT& weight, + DWRITE_FONT_STRETCH& stretch, + DWRITE_FONT_STYLE& style, + std::wstring& localeName) const; + + [[nodiscard]] std::wstring _GetLocaleName() const; + + [[nodiscard]] std::wstring _GetFontFamilyName(gsl::not_null const fontFamily, + std::wstring& localeName) const; [[nodiscard]] HRESULT _GetProposedFont(const FontInfoDesired& desired, FontInfo& actual, @@ -196,6 +213,15 @@ namespace Microsoft::Console::Render [[nodiscard]] D2D1_COLOR_F _ColorFFromColorRef(const COLORREF color) noexcept; - [[nodiscard]] static DXGI_RGBA s_RgbaFromColorF(const D2D1_COLOR_F color) noexcept; + // Routine Description: + // - Helps convert a Direct2D ColorF into a DXGI RGBA + // Arguments: + // - color - Direct2D Color F + // Return Value: + // - DXGI RGBA + [[nodiscard]] constexpr DXGI_RGBA s_RgbaFromColorF(const D2D1_COLOR_F color) noexcept + { + return { color.r, color.g, color.b, color.a }; + } }; } diff --git a/src/renderer/inc/IRenderData.hpp b/src/renderer/inc/IRenderData.hpp index 33bbbc947ad..fb7eaf24589 100644 --- a/src/renderer/inc/IRenderData.hpp +++ b/src/renderer/inc/IRenderData.hpp @@ -16,9 +16,8 @@ Author(s): #include "../../host/conimeinfo.h" #include "../../buffer/out/TextAttribute.hpp" -#include "../../types/inc/viewport.hpp" +#include "../../types/IBaseData.h" -class TextBuffer; class Cursor; namespace Microsoft::Console::Render @@ -38,13 +37,10 @@ namespace Microsoft::Console::Render const Microsoft::Console::Types::Viewport region; }; - class IRenderData + class IRenderData : public Microsoft::Console::Types::IBaseData { public: virtual ~IRenderData() = 0; - virtual Microsoft::Console::Types::Viewport GetViewport() noexcept = 0; - virtual const TextBuffer& GetTextBuffer() noexcept = 0; - virtual const FontInfo& GetFontInfo() noexcept = 0; virtual const TextAttribute GetDefaultBrushColors() noexcept = 0; virtual const COLORREF GetForegroundColor(const TextAttribute& attr) const noexcept = 0; @@ -62,13 +58,7 @@ namespace Microsoft::Console::Render virtual const std::vector GetOverlays() const noexcept = 0; virtual const bool IsGridLineDrawingAllowed() noexcept = 0; - - virtual std::vector GetSelectionRects() noexcept = 0; - virtual const std::wstring GetConsoleTitle() const noexcept = 0; - - virtual void LockConsole() noexcept = 0; - virtual void UnlockConsole() noexcept = 0; }; // See docs/virtual-dtors.md for an explanation of why this is weird. diff --git a/src/renderer/inc/IRenderEngine.hpp b/src/renderer/inc/IRenderEngine.hpp index 2399ab6a3f2..2d321381318 100644 --- a/src/renderer/inc/IRenderEngine.hpp +++ b/src/renderer/inc/IRenderEngine.hpp @@ -64,6 +64,14 @@ namespace Microsoft::Console::Render virtual ~IRenderEngine() = 0; + protected: + IRenderEngine() = default; + IRenderEngine(const IRenderEngine&) = default; + IRenderEngine(IRenderEngine&&) = default; + IRenderEngine& operator=(const IRenderEngine&) = default; + IRenderEngine& operator=(IRenderEngine&&) = default; + + public: [[nodiscard]] virtual HRESULT StartPaint() noexcept = 0; [[nodiscard]] virtual HRESULT EndPaint() noexcept = 0; [[nodiscard]] virtual HRESULT Present() noexcept = 0; diff --git a/src/renderer/inc/IRenderTarget.hpp b/src/renderer/inc/IRenderTarget.hpp index 1d1223b12b1..4fbba7fad41 100644 --- a/src/renderer/inc/IRenderTarget.hpp +++ b/src/renderer/inc/IRenderTarget.hpp @@ -25,6 +25,14 @@ namespace Microsoft::Console::Render public: virtual ~IRenderTarget() = 0; + protected: + IRenderTarget() = default; + IRenderTarget(const IRenderTarget&) = default; + IRenderTarget(IRenderTarget&&) = default; + IRenderTarget& operator=(const IRenderTarget&) = default; + IRenderTarget& operator=(IRenderTarget&&) = default; + + public: virtual void TriggerRedraw(const Microsoft::Console::Types::Viewport& region) = 0; virtual void TriggerRedraw(const COORD* const pcoord) = 0; virtual void TriggerRedrawCursor(const COORD* const pcoord) = 0; diff --git a/src/renderer/inc/RenderEngineBase.hpp b/src/renderer/inc/RenderEngineBase.hpp index dad43c5c51a..eae934bc775 100644 --- a/src/renderer/inc/RenderEngineBase.hpp +++ b/src/renderer/inc/RenderEngineBase.hpp @@ -24,9 +24,16 @@ namespace Microsoft::Console::Render class RenderEngineBase : public IRenderEngine { public: + ~RenderEngineBase() = 0; + + protected: RenderEngineBase(); - virtual ~RenderEngineBase() = 0; + RenderEngineBase(const RenderEngineBase&) = default; + RenderEngineBase(RenderEngineBase&&) = default; + RenderEngineBase& operator=(const RenderEngineBase&) = default; + RenderEngineBase& operator=(RenderEngineBase&&) = default; + public: [[nodiscard]] HRESULT InvalidateTitle(const std::wstring& proposedTitle) noexcept override; [[nodiscard]] HRESULT UpdateTitle(const std::wstring& newTitle) noexcept override; diff --git a/src/renderer/vt/invalidate.cpp b/src/renderer/vt/invalidate.cpp index 1272615baff..6d9caea7a63 100644 --- a/src/renderer/vt/invalidate.cpp +++ b/src/renderer/vt/invalidate.cpp @@ -102,11 +102,19 @@ using namespace Microsoft::Console::Render; // - S_OK [[nodiscard]] HRESULT VtEngine::InvalidateCircling(_Out_ bool* const pForcePaint) noexcept { - *pForcePaint = true; + // If we're in the middle of a resize request, don't try to immediately start a frame. + if (_inResizeRequest) + { + *pForcePaint = false; + } + else + { + *pForcePaint = true; - // Keep track of the fact that we circled, we'll need to do some work on - // end paint to specifically handle this. - _circled = true; + // Keep track of the fact that we circled, we'll need to do some work on + // end paint to specifically handle this. + _circled = true; + } return S_OK; } diff --git a/src/renderer/vt/state.cpp b/src/renderer/vt/state.cpp index d2b26bc2f5d..31cca05d4a2 100644 --- a/src/renderer/vt/state.cpp +++ b/src/renderer/vt/state.cpp @@ -54,6 +54,7 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe, _terminalOwner{ nullptr }, _newBottomLine{ false }, _deferredCursorPos{ INVALID_COORDS }, + _inResizeRequest{ false }, _trace{} { #ifndef UNIT_TESTING @@ -417,3 +418,31 @@ HRESULT VtEngine::RequestCursor() noexcept RETURN_IF_FAILED(_Flush()); return S_OK; } + +// Method Description: +// - Tell the vt renderer to begin a resize operation. During a resize +// operation, the vt renderer should _not_ request to be repainted during a +// text buffer circling event. Any callers of this method should make sure to +// call EndResize to make sure the renderer returns to normal behavior. +// See GH#1795 for context on this method. +// Arguments: +// - +// Return Value: +// - +void VtEngine::BeginResizeRequest() +{ + _inResizeRequest = true; +} + +// Method Description: +// - Tell the vt renderer to end a resize operation. +// See BeginResize for more details. +// See GH#1795 for context on this method. +// Arguments: +// - +// Return Value: +// - +void VtEngine::EndResizeRequest() +{ + _inResizeRequest = false; +} diff --git a/src/renderer/vt/vtrenderer.hpp b/src/renderer/vt/vtrenderer.hpp index 77a9c6349c2..7ca3aa37af3 100644 --- a/src/renderer/vt/vtrenderer.hpp +++ b/src/renderer/vt/vtrenderer.hpp @@ -94,6 +94,8 @@ namespace Microsoft::Console::Render [[nodiscard]] virtual HRESULT WriteTerminalW(const std::wstring& str) noexcept = 0; void SetTerminalOwner(Microsoft::Console::ITerminalOwner* const terminalOwner); + void BeginResizeRequest(); + void EndResizeRequest(); protected: wil::unique_hfile _hFile; @@ -132,6 +134,7 @@ namespace Microsoft::Console::Render Microsoft::Console::ITerminalOwner* _terminalOwner; Microsoft::Console::VirtualTerminal::RenderTracing _trace; + bool _inResizeRequest{ false }; [[nodiscard]] HRESULT _Write(std::string_view const str) noexcept; [[nodiscard]] HRESULT _WriteFormattedString(const std::string* const pFormat, ...) noexcept; diff --git a/src/server/Entrypoints.cpp b/src/server/Entrypoints.cpp index cc77e2f3e78..7eb941238b1 100644 --- a/src/server/Entrypoints.cpp +++ b/src/server/Entrypoints.cpp @@ -86,10 +86,14 @@ HostStartupInfo.cb = sizeof(STARTUPINFO); GetStartupInfoW(&HostStartupInfo); - // If we were started with Title is Link Name, then pass the flag and the link name down to the child. + // Pass the title we were started with down to our child process. + // Conhost itself absolutely doesn't care about this value, but the + // child might. + StartupInformation.StartupInfo.lpTitle = HostStartupInfo.lpTitle; + // If we were started with Title is Link Name, then pass the flag + // down to the child. (the link name was already passed down above) if (WI_IsFlagSet(HostStartupInfo.dwFlags, STARTF_TITLEISLINKNAME)) { - StartupInformation.StartupInfo.lpTitle = HostStartupInfo.lpTitle; StartupInformation.StartupInfo.dwFlags |= STARTF_TITLEISLINKNAME; } } diff --git a/src/terminal/adapter/InteractDispatch.cpp b/src/terminal/adapter/InteractDispatch.cpp index afbddc0fdf1..74c918bffe1 100644 --- a/src/terminal/adapter/InteractDispatch.cpp +++ b/src/terminal/adapter/InteractDispatch.cpp @@ -114,6 +114,8 @@ bool InteractDispatch::WindowManipulation(const DispatchTypes::WindowManipulatio } break; case DispatchTypes::WindowManipulationType::ResizeWindowInCharacters: + // TODO:GH#1765 We should introduce a better `ResizeConpty` function to + // the ConGetSet interface, that specifically handles a conpty resize. if (cParams == 2) { fSuccess = DispatchCommon::s_ResizeWindow(*_pConApi, rgusParams[1], rgusParams[0]); diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 9a045213a3c..1e6089ed22d 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -506,7 +506,7 @@ bool AdaptDispatch::_InsertDeleteHelper(_In_ unsigned int const uiCount, const b SHORT sDistance; RETURN_IF_FALSE(SUCCEEDED(UIntToShort(uiCount, &sDistance))); - // get current cursor, viewport + // get current cursor, attributes CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 }; csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); // Make sure to reset the viewport (with MoveToBottom )to where it was @@ -515,11 +515,11 @@ bool AdaptDispatch::_InsertDeleteHelper(_In_ unsigned int const uiCount, const b RETURN_IF_FALSE(_conApi->GetConsoleScreenBufferInfoEx(&csbiex)); const auto cursor = csbiex.dwCursorPosition; - const auto viewport = Viewport::FromExclusive(csbiex.srWindow); - // Rectangle to cut out of the existing buffer + // Rectangle to cut out of the existing buffer. This is inclusive. + // It will be clipped to the buffer boundaries so SHORT_MAX gives us the full buffer width. SMALL_RECT srScroll; srScroll.Left = cursor.X; - srScroll.Right = viewport.RightExclusive(); + srScroll.Right = SHORT_MAX; srScroll.Top = cursor.Y; srScroll.Bottom = srScroll.Top; @@ -541,85 +541,16 @@ bool AdaptDispatch::_InsertDeleteHelper(_In_ unsigned int const uiCount, const b } else { - // for delete, we need to add to the scroll region to move it off toward the right. - fSuccess = SUCCEEDED(ShortAdd(srScroll.Left, sDistance, &srScroll.Left)); + // Delete scrolls the affected region to the left, relying on the clipping rect to actually delete the characters. + fSuccess = SUCCEEDED(ShortSub(coordDestination.X, sDistance, &coordDestination.X)); } if (fSuccess) { - if (srScroll.Left >= viewport.RightExclusive() || - coordDestination.X >= viewport.RightExclusive()) - { - DWORD const nLength = viewport.RightExclusive() - cursor.X; - size_t written = 0; - - // if the select/scroll region is off screen to the right or the destination is off screen to the right, fill instead of scrolling. - fSuccess = !!_conApi->FillConsoleOutputCharacterW(ciFill.Char.UnicodeChar, - nLength, - cursor, - written); - - if (fSuccess) - { - written = 0; - fSuccess = !!_conApi->FillConsoleOutputAttribute(ciFill.Attributes, - nLength, - cursor, - written); - } - } - else - { - // clip inside the viewport. - fSuccess = !!_conApi->ScrollConsoleScreenBufferW(&srScroll, - &csbiex.srWindow, - coordDestination, - &ciFill); - - if (fSuccess && !fIsInsert) - { - // See MSFT:19888564 - // We've now shifted a number of the characters to the left. - // If the number of chars we've shifted doesn't fill the - // entire region we deleted, then artifacts of the - // previous contents of the row can get left behind. - // - // Example: (this is tested by DeleteCharsNearEndOfLineSimpleFirstCase) - // start with the following buffer contents, and the cursor on the "D" - // [ABCDEFG ] - // ^ - // When you DCH(3) here, we are trying to delete the D, E and F. - // We do that by shifting the contents of the line after the deleted - // characters to the left. HOWEVER, there are only 2 chars left to move. - // So (before the fix) the buffer end up like this: - // [ABCG F ] - // ^ - // The G and " " have moved, but the F did not get overwritten. - // - // Fill the remaining space after the characters we - // shifted with spaces (empty cells). - const short scrolledChars = viewport.RightExclusive() - srScroll.Left; - const short shiftedRightPos = cursor.X + scrolledChars; - if (shiftedRightPos < srScroll.Left) - { - size_t written = 0; - const short spacesToFill = viewport.RightInclusive() - (shiftedRightPos); - const COORD fillPos{ shiftedRightPos, cursor.Y }; - fSuccess = !!_conApi->FillConsoleOutputCharacterW(ciFill.Char.UnicodeChar, - spacesToFill, - fillPos, - written); - if (fSuccess) - { - written = 0; - fSuccess = !!_conApi->FillConsoleOutputAttribute(ciFill.Attributes, - spacesToFill, - fillPos, - written); - } - } - } - } + fSuccess = !!_conApi->ScrollConsoleScreenBufferW(&srScroll, + &srScroll, + coordDestination, + &ciFill); } return fSuccess; @@ -1025,7 +956,13 @@ bool AdaptDispatch::_ScrollMovement(const ScrollDirection sdDirection, _In_ unsi if (fSuccess) { - SMALL_RECT srScreen = csbiex.srWindow; + // Rectangle to cut out of the existing buffer. This is inclusive. + // It will be clipped to the buffer boundaries so SHORT_MAX gives us the full buffer width. + SMALL_RECT srScreen; + srScreen.Left = 0; + srScreen.Right = SHORT_MAX; + srScreen.Top = csbiex.srWindow.Top; + srScreen.Bottom = csbiex.srWindow.Bottom - 1; // srWindow is exclusive, hence the - 1 // Paste coordinate for cut text above COORD coordDestination; @@ -1601,17 +1538,18 @@ bool AdaptDispatch::SoftReset() // True if handled successfully. False otherwise. bool AdaptDispatch::HardReset() { + // Sets the SGR state to normal - this must be done before EraseInDisplay + // to ensure that it clears with the default background color. + bool fSuccess = SoftReset(); + // Clears the screen - Needs to be done in two operations. - bool fSuccess = _EraseScrollback(); if (fSuccess) { fSuccess = EraseInDisplay(DispatchTypes::EraseType::All); } - - // Sets the SGR state to normal. if (fSuccess) { - fSuccess = SoftReset(); + fSuccess = _EraseScrollback(); } // Cursor to 1,1 - the Soft Reset guarantees this is absolute diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index 5c584a82998..b2a6f4bb88c 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -47,12 +47,6 @@ enum class CursorDirection : unsigned int PREVLINE = 5 }; -enum class ScrollDirection : unsigned int -{ - UP = 0, - DOWN = 1 -}; - enum class AbsolutePosition : unsigned int { CursorHorizontal = 0, @@ -395,114 +389,10 @@ class TestGetSet final : public ConGetSet return _fPrivateWriteConsoleControlInputResult; } - bool _IsInsideClip(const SMALL_RECT* const pClipRectangle, const SHORT iRow, const SHORT iCol) - { - if (pClipRectangle == nullptr) - { - return true; - } - else - { - return iRow >= pClipRectangle->Top && iRow < pClipRectangle->Bottom && iCol >= pClipRectangle->Left && iCol < pClipRectangle->Right; - } - } - - BOOL ScrollConsoleScreenBufferW(const SMALL_RECT* pScrollRectangle, _In_opt_ const SMALL_RECT* pClipRectangle, _In_ COORD dwDestinationOrigin, const CHAR_INFO* pFill) override + BOOL ScrollConsoleScreenBufferW(const SMALL_RECT* /*pScrollRectangle*/, _In_opt_ const SMALL_RECT* /*pClipRectangle*/, _In_ COORD /*dwDestinationOrigin*/, const CHAR_INFO* /*pFill*/) override { Log::Comment(L"ScrollConsoleScreenBufferW MOCK called..."); - if (_fScrollConsoleScreenBufferWResult) - { - if (pClipRectangle != nullptr) - { - Log::Comment(NoThrowString().Format( - L"\tScrolling Rectangle (T: %d, B: %d, L: %d, R: %d) " - L"into new top-left coordinate (X: %d, Y:%d) with Fill ('%c', 0x%x) " - L"clipping to (T: %d, B: %d, L: %d, R: %d)...", - pScrollRectangle->Top, - pScrollRectangle->Bottom, - pScrollRectangle->Left, - pScrollRectangle->Right, - dwDestinationOrigin.X, - dwDestinationOrigin.Y, - pFill->Char.UnicodeChar, - pFill->Attributes, - pClipRectangle->Top, - pClipRectangle->Bottom, - pClipRectangle->Left, - pClipRectangle->Right)); - } - else - { - Log::Comment(NoThrowString().Format( - L"\tScrolling Rectangle (T: %d, B: %d, L: %d, R: %d) " - L"into new top-left coordinate (X: %d, Y:%d) with Fill ('%c', 0x%x) ", - pScrollRectangle->Top, - pScrollRectangle->Bottom, - pScrollRectangle->Left, - pScrollRectangle->Right, - dwDestinationOrigin.X, - dwDestinationOrigin.Y, - pFill->Char.UnicodeChar, - pFill->Attributes)); - } - - // allocate buffer space to hold scrolling rectangle - SHORT width = pScrollRectangle->Right - pScrollRectangle->Left; - SHORT height = pScrollRectangle->Bottom - pScrollRectangle->Top + 1; - size_t const cch = width * height; - CHAR_INFO* const ciBuffer = new CHAR_INFO[cch]; - size_t cciFilled = 0; - - Log::Comment(NoThrowString().Format(L"\tCopy buffer size is %zu chars", cch)); - - for (SHORT iCharY = pScrollRectangle->Top; iCharY <= pScrollRectangle->Bottom; iCharY++) - { - // back up space and fill it with the fill. - for (SHORT iCharX = pScrollRectangle->Left; iCharX < pScrollRectangle->Right; iCharX++) - { - COORD coordTarget; - coordTarget.X = (SHORT)iCharX; - coordTarget.Y = iCharY; - - CHAR_INFO* const pciStored = _GetCharAt(coordTarget.Y, coordTarget.X); - - // back up to buffer - ciBuffer[cciFilled] = *pciStored; - cciFilled++; - - // fill with fill - if (_IsInsideClip(pClipRectangle, coordTarget.Y, coordTarget.X)) - { - *pciStored = *pFill; - } - } - } - Log::Comment(NoThrowString().Format(L"\tCopied a total %zu chars", cciFilled)); - Log::Comment(L"\tCopying chars back"); - for (SHORT iCharY = pScrollRectangle->Top; iCharY <= pScrollRectangle->Bottom; iCharY++) - { - // back up space and fill it with the fill. - for (SHORT iCharX = pScrollRectangle->Left; iCharX < pScrollRectangle->Right; iCharX++) - { - COORD coordTarget; - coordTarget.X = dwDestinationOrigin.X + (iCharX - pScrollRectangle->Left); - coordTarget.Y = dwDestinationOrigin.Y + (iCharY - pScrollRectangle->Top); - - CHAR_INFO* const pciStored = _GetCharAt(coordTarget.Y, coordTarget.X); - - if (_IsInsideClip(pClipRectangle, coordTarget.Y, coordTarget.X) && _IsInsideClip(pClipRectangle, iCharY, iCharX)) - { - size_t index = (width) * (iCharY - pScrollRectangle->Top) + (iCharX - pScrollRectangle->Left); - CHAR_INFO charFromBuffer = ciBuffer[index]; - *pciStored = charFromBuffer; - } - } - } - - delete[] ciBuffer; - } - return _fScrollConsoleScreenBufferWResult; } @@ -978,55 +868,6 @@ class TestGetSet final : public ConGetSet } } - void InsertString(COORD coordTarget, PWSTR pwszText, WORD wAttr) - { - Log::Comment(NoThrowString().Format(L"Writing string '%s' to target (X: %d, Y:%d) with color/attr 0x%x", pwszText, coordTarget.X, coordTarget.Y, wAttr)); - - size_t cchModified = 0; - - if (pwszText != nullptr) - { - size_t cch; - if (SUCCEEDED(StringCchLengthW(pwszText, STRSAFE_MAX_LENGTH, &cch))) - { - COORD coordInsertPoint = coordTarget; - - for (size_t i = 0; i < cch; i++) - { - CHAR_INFO* const pci = _GetCharAt(coordInsertPoint.Y, coordInsertPoint.X); - pci->Char.UnicodeChar = pwszText[i]; - pci->Attributes = wAttr; - - _IncrementCoordPos(&coordInsertPoint); - cchModified++; - } - } - } - - Log::Comment(NoThrowString().Format(L"Wrote %zu characters into buffer.", cchModified)); - } - - void FillRectangle(SMALL_RECT srRect, wchar_t wch, WORD wAttr) - { - Log::Comment(NoThrowString().Format(L"Filling area (L: %d, R: %d, T: %d, B: %d) with '%c' in attr 0x%x", srRect.Left, srRect.Right, srRect.Top, srRect.Bottom, wch, wAttr)); - - size_t cchModified = 0; - - for (SHORT iRow = srRect.Top; iRow < srRect.Bottom; iRow++) - { - for (SHORT iCol = srRect.Left; iCol < srRect.Right; iCol++) - { - CHAR_INFO* const pci = _GetCharAt(iRow, iCol); - pci->Char.UnicodeChar = wch; - pci->Attributes = wAttr; - - cchModified++; - } - } - - Log::Comment(NoThrowString().Format(L"Filled %zu characters.", cchModified)); - } - void ValidateInputEvent(_In_ PCWSTR pwszExpectedResponse) { size_t const cchResponse = wcslen(pwszExpectedResponse); @@ -1053,132 +894,6 @@ class TestGetSet final : public ConGetSet } } - bool ValidateString(COORD const coordTarget, PCWSTR pwszText, WORD const wAttr) - { - Log::Comment(NoThrowString().Format(L"Validating that the string %s is written starting at (X: %d, Y: %d) with the color/attr 0x%x", pwszText, coordTarget.X, coordTarget.Y, wAttr)); - - bool fSuccess = true; - - if (pwszText != nullptr) - { - size_t cch; - fSuccess = SUCCEEDED(StringCchLengthW(pwszText, STRSAFE_MAX_LENGTH, &cch)); - - if (fSuccess) - { - COORD coordGetPos = coordTarget; - - for (size_t i = 0; i < cch; i++) - { - const CHAR_INFO* const pci = _GetCharAt(coordGetPos.Y, coordGetPos.X); - - const wchar_t wchActual = pci->Char.UnicodeChar; - const wchar_t wchExpected = pwszText[i]; - - fSuccess = wchExpected == wchActual; - - if (!fSuccess) - { - Log::Comment(NoThrowString().Format(L"ValidateString failed char comparison at (X: %d, Y: %d). Expected: '%c' Actual: '%c'", coordGetPos.X, coordGetPos.Y, wchExpected, wchActual)); - break; - } - - const WORD wAttrActual = pci->Attributes; - const WORD wAttrExpected = wAttr; - - if (!fSuccess) - { - Log::Comment(NoThrowString().Format(L"ValidateString failed attr comparison at (X: %d, Y: %d). Expected: '0x%x' Actual: '0x%x'", coordGetPos.X, coordGetPos.Y, wAttrExpected, wAttrActual)); - break; - } - - _IncrementCoordPos(&coordGetPos); - } - } - } - - return fSuccess; - } - - bool ValidateRectangleContains(SMALL_RECT srRect, wchar_t wchExpected, WORD wAttrExpected) - { - Log::Comment(NoThrowString().Format(L"Validating that the area inside (L: %d, R: %d, T: %d, B: %d) char '%c' and attr 0x%x", srRect.Left, srRect.Right, srRect.Top, srRect.Bottom, wchExpected, wAttrExpected)); - - bool fStateValid = true; - - for (SHORT iRow = srRect.Top; iRow < srRect.Bottom; iRow++) - { - Log::Comment(NoThrowString().Format(L"Validating row(y=) %d", iRow)); - for (SHORT iCol = srRect.Left; iCol < srRect.Right; iCol++) - { - CHAR_INFO* const pci = _GetCharAt(iRow, iCol); - - fStateValid = pci->Char.UnicodeChar == wchExpected; - if (!fStateValid) - { - Log::Comment(NoThrowString().Format(L"Region match failed at (X: %d, Y: %d). Expected: '%c'. Actual: '%c'", iCol, iRow, wchExpected, pci->Char.UnicodeChar)); - break; - } - - fStateValid = pci->Attributes == wAttrExpected; - if (!fStateValid) - { - Log::Comment(NoThrowString().Format(L"Region match failed at (X: %d, Y: %d). Expected Attr: 0x%x. Actual Attr: 0x%x", iCol, iRow, wAttrExpected, pci->Attributes)); - } - } - - if (!fStateValid) - { - break; - } - } - - return fStateValid; - } - - bool ValidateRectangleContains(SMALL_RECT srRect, wchar_t wchExpected, WORD wAttrExpected, SMALL_RECT srExcept) - { - bool fStateValid = true; - - Log::Comment(NoThrowString().Format(L"Validating that the area inside (L: %d, R: %d, T: %d, B: %d) but outside (L: %d, R: %d, T: %d, B: %d) contains char '%c' and attr 0x%x", srRect.Left, srRect.Right, srRect.Top, srRect.Bottom, srExcept.Left, srExcept.Right, srExcept.Top, srExcept.Bottom, wchExpected, wAttrExpected)); - - for (SHORT iRow = srRect.Top; iRow < srRect.Bottom; iRow++) - { - for (SHORT iCol = srRect.Left; iCol < srRect.Right; iCol++) - { - if (iRow >= srExcept.Top && iRow < srExcept.Bottom && iCol >= srExcept.Left && iCol < srExcept.Right) - { - // if in exception range, skip comparison. - continue; - } - else - { - CHAR_INFO* const pci = _GetCharAt(iRow, iCol); - - fStateValid = pci->Char.UnicodeChar == wchExpected; - if (!fStateValid) - { - Log::Comment(NoThrowString().Format(L"Region match failed at (X: %d, Y: %d). Expected: '%c'. Actual: '%c'", iCol, iRow, wchExpected, pci->Char.UnicodeChar)); - break; - } - - fStateValid = pci->Attributes == wAttrExpected; - if (!fStateValid) - { - Log::Comment(NoThrowString().Format(L"Region match failed at (X: %d, Y: %d). Expected Attr: 0x%x. Actual Attr: 0x%x", iCol, iRow, wAttrExpected, pci->Attributes)); - } - } - } - - if (!fStateValid) - { - break; - } - } - - return fStateValid; - } - bool ValidateEraseBufferState(SMALL_RECT* rgsrRegions, size_t cRegions, wchar_t wchExpectedInRegions, WORD wAttrExpectedInRegions) { bool fStateValid = true; @@ -1274,20 +989,6 @@ class TestGetSet final : public ConGetSet return pchar; } - void _PrepForScroll(ScrollDirection const dir, int const distance) - { - _fExpectedWindowAbsolute = FALSE; - _srExpectedConsoleWindow.Top = (SHORT)distance; - _srExpectedConsoleWindow.Bottom = (SHORT)distance; - _srExpectedConsoleWindow.Left = 0; - _srExpectedConsoleWindow.Right = 0; - if (dir == ScrollDirection::UP) - { - _srExpectedConsoleWindow.Top *= -1; - _srExpectedConsoleWindow.Bottom *= -1; - } - } - void _SetMarginsHelper(SMALL_RECT* rect, SHORT top, SHORT bottom) { rect->Top = top; @@ -1939,315 +1640,6 @@ class AdapterTest VERIFY_IS_FALSE(_pDispatch->CursorVisibility(fEnd)); } - TEST_METHOD(InsertCharacterTests) - { - Log::Comment(L"Starting test..."); - - Log::Comment(L"Test 1: The big one. Fill the buffer with Qs. Fill the window with Rs. Write a line of ABCDE at the cursor. Then insert 5 spaces at the cursor. Watch spaces get inserted, ABCDE slide right eating up the Rs in the viewport but not modifying the Qs outside."); - - // place the cursor in the center. - _testGetSet->PrepData(CursorX::XCENTER, CursorY::YCENTER); - - // Save the cursor position. It shouldn't move for the rest of the test. - COORD coordCursorExpected = _testGetSet->_coordCursorPos; - - // Fill the entire buffer with Qs. Blue on Green. - WCHAR const wchOuterBuffer = 'Q'; - WORD const wAttrOuterBuffer = FOREGROUND_BLUE | BACKGROUND_GREEN; - SMALL_RECT srOuterBuffer; - srOuterBuffer.Top = 0; - srOuterBuffer.Left = 0; - srOuterBuffer.Bottom = _testGetSet->_coordBufferSize.Y; - srOuterBuffer.Right = _testGetSet->_coordBufferSize.X; - _testGetSet->FillRectangle(srOuterBuffer, wchOuterBuffer, wAttrOuterBuffer); - - // Fill the viewport with Rs. Red on Blue. - WCHAR const wchViewport = 'R'; - WORD const wAttrViewport = FOREGROUND_RED | BACKGROUND_BLUE; - SMALL_RECT srViewport = _testGetSet->_srViewport; - _testGetSet->FillRectangle(srViewport, wchViewport, wAttrViewport); - - // fill some of the text right of the cursor so we can verify it moved it and didn't overwrite it. - // change the color too so we can make sure that it's fine - - WORD const wAttrTestText = FOREGROUND_GREEN; - PWSTR const pwszTestText = L"ABCDE"; - size_t cchTestText = wcslen(pwszTestText); - SMALL_RECT srTestText; - srTestText.Top = _testGetSet->_coordCursorPos.Y; - srTestText.Bottom = srTestText.Top + 1; - srTestText.Left = _testGetSet->_coordCursorPos.X; - srTestText.Right = srTestText.Left + (SHORT)cchTestText; - _testGetSet->InsertString(_testGetSet->_coordCursorPos, pwszTestText, wAttrTestText); - - WCHAR const wchInsertExpected = L' '; - WORD const wAttrInsertExpected = _testGetSet->_wAttribute; - size_t const cchInsertSize = 5; - SMALL_RECT srInsertExpected; - srInsertExpected.Top = _testGetSet->_coordCursorPos.Y; - srInsertExpected.Bottom = srInsertExpected.Top + 1; - srInsertExpected.Left = _testGetSet->_coordCursorPos.X; - srInsertExpected.Right = srInsertExpected.Left + (SHORT)cchInsertSize; - - // the text we inserted is going to move right by the insert size, so adjust that rectangle right. - srTestText.Left += cchInsertSize; - srTestText.Right += cchInsertSize; - - // insert out 5 spots. this should clear them out with spaces and the default fill from the original cursor position - VERIFY_IS_TRUE(_pDispatch->InsertCharacter(cchInsertSize), L"Verify insert call was sucessful."); - - // the combined area of the letters + the spaces will be 10 characters wide: - SMALL_RECT srModifiedSpace; - srModifiedSpace.Top = _testGetSet->_coordCursorPos.Y; - srModifiedSpace.Bottom = srModifiedSpace.Top + 1; - srModifiedSpace.Left = _testGetSet->_coordCursorPos.X; - srModifiedSpace.Right = srModifiedSpace.Left + (SHORT)cchInsertSize + (SHORT)cchTestText; - - // verify cursor didn't move - VERIFY_ARE_EQUAL(coordCursorExpected, _testGetSet->_coordCursorPos, L"Verify cursor didn't move from insert operation."); - - // e.g. we had this in the buffer: QQQRRRRRRABCDERRRRRRRQQQ with the cursor on the A. - // now we should have this buffer: QQQRRRRRR ABCDERRQQQ with the cursor on the first space. - - // Verify the field of Qs didn't change outside the viewport. - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srOuterBuffer, wchOuterBuffer, wAttrOuterBuffer, srViewport), L"Field of Qs outside viewport should remain unchanged."); - - // Verify the field of Rs within the viewport not including the inserted range and the ABCDE shifted right. (10 characters) - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srViewport, wchViewport, wAttrViewport, srModifiedSpace), L"Field of Rs in the viewport outside modified space should remain unchanged."); - - // Verify the 5 spaces inserted from the cursor. - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srInsertExpected, wchInsertExpected, wAttrInsertExpected), L"Spaces should be inserted with the proper attributes at the cursor."); - - // Verify the ABCDE sequence was shifted right. - COORD coordTestText; - coordTestText.X = srTestText.Left; - coordTestText.Y = srTestText.Top; - VERIFY_IS_TRUE(_testGetSet->ValidateString(coordTestText, pwszTestText, wAttrTestText), L"Inserted string should have moved to the right by the number of spaces inserted, attributes and text preserved."); - - // Test case needed for exact end of line (and full line) insert/delete lengths - Log::Comment(L"Test 2: Inserting at the exact end of the line. Same field of Qs and Rs. Move cursor to right edge of window and insert > 1 space. Only 1 should be inserted, everything else unchanged."); - - _testGetSet->FillRectangle(srOuterBuffer, wchOuterBuffer, wAttrOuterBuffer); - _testGetSet->FillRectangle(srViewport, wchViewport, wAttrViewport); - - // move cursor to right edge - _testGetSet->_coordCursorPos.X = _testGetSet->_srViewport.Right - 1; - coordCursorExpected = _testGetSet->_coordCursorPos; - - // the rectangle where the space should be is exactly the size of the cursor. - srModifiedSpace.Top = _testGetSet->_coordCursorPos.Y; - srModifiedSpace.Bottom = srModifiedSpace.Top + 1; - srModifiedSpace.Left = _testGetSet->_coordCursorPos.X; - srModifiedSpace.Right = srModifiedSpace.Left + 1; - - // insert out 5 spots. this should clear them out with spaces and the default fill from the original cursor position - VERIFY_IS_TRUE(_pDispatch->InsertCharacter(cchInsertSize), L"Verify insert call was sucessful."); - - // cursor didn't move - VERIFY_ARE_EQUAL(coordCursorExpected, _testGetSet->_coordCursorPos, L"Verify cursor didn't move from insert operation."); - - // Qs are the same outside the viewport - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srOuterBuffer, wchOuterBuffer, wAttrOuterBuffer, srViewport), L"Field of Qs outside viewport should remain unchanged."); - - // Entire viewport is Rs except the one space spot - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srViewport, wchViewport, wAttrViewport, srModifiedSpace), L"Field of Rs in the viewport outside modified space should remain unchanged."); - - // The 5 inserted spaces at the right edge resulted in 1 space at the right edge - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srModifiedSpace, wchInsertExpected, wAttrInsertExpected), L"A space was inserted at the cursor position. All extra spaces were discarded as they hit the right boundary."); - - Log::Comment(L"Test 3: Inserting at the exact beginning of the line. Same field of Qs and Rs. Move cursor to left edge of window and insert > screen width of space. The whole row should be spaces but nothing outside the viewport should be changed."); - - _testGetSet->FillRectangle(srOuterBuffer, wchOuterBuffer, wAttrOuterBuffer); - _testGetSet->FillRectangle(srViewport, wchViewport, wAttrViewport); - - // move cursor to left edge - _testGetSet->_coordCursorPos.X = _testGetSet->_srViewport.Left; - coordCursorExpected = _testGetSet->_coordCursorPos; - - // the rectangle of spaces should be the entire line at the cursor. - srModifiedSpace.Top = _testGetSet->_coordCursorPos.Y; - srModifiedSpace.Bottom = srModifiedSpace.Top + 1; - srModifiedSpace.Left = _testGetSet->_srViewport.Left; - srModifiedSpace.Right = _testGetSet->_srViewport.Right; - - // insert greater than the entire viewport (the entire buffer width) at the cursor position - VERIFY_IS_TRUE(_pDispatch->InsertCharacter(_testGetSet->_coordBufferSize.X), L"Verify insert call was successful."); - - // cursor didn't move - VERIFY_ARE_EQUAL(coordCursorExpected, _testGetSet->_coordCursorPos, L"Verify cursor didn't move from insert operation."); - - // Qs are the same outside the viewport - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srOuterBuffer, wchOuterBuffer, wAttrOuterBuffer, srViewport), L"Field of Qs outside viewport should remain unchanged."); - - // Entire viewport is Rs except the one space spot - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srViewport, wchViewport, wAttrViewport, srModifiedSpace), L"Field of Rs in the viewport outside modified space should remain unchanged."); - - // The inserted spaces at the left edge resulted in an entire line of spaces bounded by the viewport - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srModifiedSpace, wchInsertExpected, wAttrInsertExpected), L"A whole line of spaces was inserted at the cursor position. All extra spaces were discarded as they hit the right boundary."); - } - - TEST_METHOD(DeleteCharacterTests) - { - Log::Comment(L"Starting test..."); - - Log::Comment(L"Test 1: The big one. Fill the buffer with Qs. Fill the window with Rs. Write a line of ABCDE at the cursor. Then insert 5 spaces at the cursor. Watch spaces get inserted, ABCDE slide right eating up the Rs in the viewport but not modifying the Qs outside."); - - // place the cursor in the center. - _testGetSet->PrepData(CursorX::XCENTER, CursorY::YCENTER); - - // Save the cursor position. It shouldn't move for the rest of the test. - COORD coordCursorExpected = _testGetSet->_coordCursorPos; - - // Fill the entire buffer with Qs. Blue on Green. - WCHAR const wchOuterBuffer = 'Q'; - WORD const wAttrOuterBuffer = FOREGROUND_BLUE | BACKGROUND_GREEN; - SMALL_RECT srOuterBuffer; - srOuterBuffer.Top = 0; - srOuterBuffer.Left = 0; - srOuterBuffer.Bottom = _testGetSet->_coordBufferSize.Y; - srOuterBuffer.Right = _testGetSet->_coordBufferSize.X; - _testGetSet->FillRectangle(srOuterBuffer, wchOuterBuffer, wAttrOuterBuffer); - - // Fill the viewport with Rs. Red on Blue. - WCHAR const wchViewport = 'R'; - WORD const wAttrViewport = FOREGROUND_RED | BACKGROUND_BLUE; - SMALL_RECT srViewport = _testGetSet->_srViewport; - _testGetSet->FillRectangle(srViewport, wchViewport, wAttrViewport); - - // fill some of the text right of the cursor so we can verify it moved it and wasn't deleted - // change the color too so we can make sure that it's fine - WORD const wAttrTestText = FOREGROUND_GREEN; - PWSTR const pwszTestText = L"ABCDE"; - size_t cchTestText = wcslen(pwszTestText); - SMALL_RECT srTestText; - srTestText.Top = _testGetSet->_coordCursorPos.Y; - srTestText.Bottom = srTestText.Top + 1; - srTestText.Left = _testGetSet->_coordCursorPos.X; - srTestText.Right = srTestText.Left + (SHORT)cchTestText; - _testGetSet->InsertString(_testGetSet->_coordCursorPos, pwszTestText, wAttrTestText); - - // We're going to delete "in" from the right edge, so set up that rectangle. - WCHAR const wchDeleteExpected = L' '; - WORD const wAttrDeleteExpected = _testGetSet->_wAttribute; - size_t const cchDeleteSize = 5; - SMALL_RECT srDeleteExpected; - srDeleteExpected.Top = _testGetSet->_coordCursorPos.Y; - srDeleteExpected.Bottom = srDeleteExpected.Top + 1; - srDeleteExpected.Right = _testGetSet->_srViewport.Right; - srDeleteExpected.Left = srDeleteExpected.Right - cchDeleteSize; - - // We want the ABCDE to shift left when we delete and onto the cursor. So move the cursor left 5 and adjust the srTestText rectangle left 5 to the new - // final destination of where they will be after the delete operation occurs. - _testGetSet->_coordCursorPos.X -= cchDeleteSize; - coordCursorExpected = _testGetSet->_coordCursorPos; - srTestText.Left -= cchDeleteSize; - srTestText.Right -= cchDeleteSize; - - // delete out 5 spots. this should shift the ABCDE text left by 5 and insert 5 spaces at the end of the line - VERIFY_IS_TRUE(_pDispatch->DeleteCharacter(cchDeleteSize), L"Verify delete call was sucessful."); - - // we're going to have ABCDERRRRRRRRRRRRR QQQQQQQ - // since this is a bit more complicated than the insert case, make this the "special" region and exempt it from the bulk "R" check - // we'll check the inside of this rect in 3 pieces, for the ABCDE, then for the inner Rs, then for the 5 spaces after. - SMALL_RECT srSpecialSpace; - srSpecialSpace.Top = _testGetSet->_coordCursorPos.Y; - srSpecialSpace.Bottom = srSpecialSpace.Top + 1; - srSpecialSpace.Left = _testGetSet->_coordCursorPos.X; - srSpecialSpace.Right = _testGetSet->_srViewport.Right; - - SMALL_RECT srGap; // gap space is the Rs between ABCDE and the spaces shifted in from the right - srGap.Left = srTestText.Right; - srGap.Right = srDeleteExpected.Left; - srGap.Top = _testGetSet->_coordCursorPos.Y; - srGap.Bottom = srGap.Top + 1; - - // verify cursor didn't move - VERIFY_ARE_EQUAL(coordCursorExpected, _testGetSet->_coordCursorPos, L"Verify cursor didn't move from insert operation."); - - // e.g. we had this in the buffer: QQQRRRRRR-RRRRABCDERRQQQ with the cursor on the -. - // now we should have this buffer: QQQRRRRRRABCDERR QQQ with the cursor on the A. - - // Verify the field of Qs didn't change outside the viewport. - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srOuterBuffer, wchOuterBuffer, wAttrOuterBuffer, srViewport), L"Field of Qs outside viewport should remain unchanged."); - - // Verify the field of Rs within the viewport not including the special range of the ABCDE, the spaces shifted in from the right, and the Rs between them that went along for the ride. - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srViewport, wchViewport, wAttrViewport, srSpecialSpace), L"Field of Rs in the viewport outside modified space should remain unchanged."); - - // Verify the 5 spaces shifted in from the right edge due to the delete - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srDeleteExpected, wchDeleteExpected, wAttrDeleteExpected), L"Spaces should be inserted with the proper attributes from the right end of this line (viewport edge.)"); - - // Verify the ABCDE sequence was shifted left by 5 toward the cursor. - COORD coordTestText; - coordTestText.X = srTestText.Left; - coordTestText.Y = srTestText.Top; - VERIFY_IS_TRUE(_testGetSet->ValidateString(coordTestText, pwszTestText, wAttrTestText), L"Inserted string should have moved to the left by the number of deletes, attributes and text preserved."); - - // Verify the field of Rs between the ABCDE and the spaces shifted in from the right - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srGap, wchViewport, wAttrViewport), L"Viewport Rs should be preserved/shifted left in between the ABCDE and the spaces that came in from the right edge."); - - // Test case needed for exact end of line (and full line) insert/delete lengths - Log::Comment(L"Test 2: Deleting at the exact end of the line. Same field of Qs and Rs. Move cursor to right edge of window and delete > 1 space. Only 1 should be inserted from the right edge (delete inserts from the right), everything else unchanged."); - - _testGetSet->FillRectangle(srOuterBuffer, wchOuterBuffer, wAttrOuterBuffer); - _testGetSet->FillRectangle(srViewport, wchViewport, wAttrViewport); - - // move cursor to right edge - _testGetSet->_coordCursorPos.X = _testGetSet->_srViewport.Right - 1; - coordCursorExpected = _testGetSet->_coordCursorPos; - - // the rectangle where the space should be is exactly the size of the cursor. - SMALL_RECT srModifiedSpace; - srModifiedSpace.Top = _testGetSet->_coordCursorPos.Y; - srModifiedSpace.Bottom = srModifiedSpace.Top + 1; - srModifiedSpace.Left = _testGetSet->_coordCursorPos.X; - srModifiedSpace.Right = srModifiedSpace.Left + 1; - - // delete out 5 spots. this should clear them out with spaces and the default fill from the original cursor position - VERIFY_IS_TRUE(_pDispatch->DeleteCharacter(cchDeleteSize), L"Verify delete call was sucessful."); - - // cursor didn't move - VERIFY_ARE_EQUAL(coordCursorExpected, _testGetSet->_coordCursorPos, L"Verify cursor didn't move from delete operation."); - - // Qs are the same outside the viewport - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srOuterBuffer, wchOuterBuffer, wAttrOuterBuffer, srViewport), L"Field of Qs outside viewport should remain unchanged."); - - // Entire viewport is Rs except the one space spot - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srViewport, wchViewport, wAttrViewport, srModifiedSpace), L"Field of Rs in the viewport outside modified space should remain unchanged."); - - // The 5 deleted spaces at the right edge resulted in 1 space at the right edge - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srModifiedSpace, wchDeleteExpected, wAttrDeleteExpected), L"A space was inserted at the cursor position. All extra spaces deleted in from the right continued to cover that one space."); - - Log::Comment(L"Test 3: Deleting at the exact beginning of the line. Same field of Qs and Rs. Move cursor to left edge of window and delete > screen width of space. The whole row should be spaces but nothing outside the viewport should be changed."); - - _testGetSet->FillRectangle(srOuterBuffer, wchOuterBuffer, wAttrOuterBuffer); - _testGetSet->FillRectangle(srViewport, wchViewport, wAttrViewport); - - // move cursor to left edge - _testGetSet->_coordCursorPos.X = _testGetSet->_srViewport.Left; - coordCursorExpected = _testGetSet->_coordCursorPos; - - // the rectangle of spaces should be the entire line at the cursor. - srModifiedSpace.Top = _testGetSet->_coordCursorPos.Y; - srModifiedSpace.Bottom = srModifiedSpace.Top + 1; - srModifiedSpace.Left = _testGetSet->_srViewport.Left; - srModifiedSpace.Right = _testGetSet->_srViewport.Right; - - // delete greater than the entire viewport (the entire buffer width) at the cursor position - VERIFY_IS_TRUE(_pDispatch->DeleteCharacter(_testGetSet->_coordBufferSize.X), L"Verify delete call was successful."); - - // cursor didn't move - VERIFY_ARE_EQUAL(coordCursorExpected, _testGetSet->_coordCursorPos, L"Verify cursor didn't move from insert operation."); - - // Qs are the same outside the viewport - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srOuterBuffer, wchOuterBuffer, wAttrOuterBuffer, srViewport), L"Field of Qs outside viewport should remain unchanged."); - - // Entire viewport is Rs except the one space spot - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srViewport, wchViewport, wAttrViewport, srModifiedSpace), L"Field of Rs in the viewport outside modified space should remain unchanged."); - - // The inserted spaces at the left edge resulted in an entire line of spaces bounded by the viewport - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srModifiedSpace, wchDeleteExpected, wAttrDeleteExpected), L"A whole line of spaces was inserted from the right (the cursor position was deleted enough times.) Extra deletes just covered up some of the spaces that were shifted in."); - } - // Ensures that EraseScrollback (^[[3J) deletes any content from the buffer // above the viewport, and moves the contents of the buffer in the // viewport to 0,0. This emulates the xterm behavior of clearing any @@ -2982,129 +2374,6 @@ class AdapterTest VERIFY_IS_FALSE(_pDispatch->DeviceAttributes()); } - TEST_METHOD(ScrollTest) - { - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:uiDirection", L"{0, 1}") // These values align with the ScrollDirection enum class to try all the directions. - TEST_METHOD_PROPERTY(L"Data:uiMagnitude", L"{1, 2, 5}") // These values align with the ScrollDirection enum class to try all the directions. - END_TEST_METHOD_PROPERTIES() - - Log::Comment(L"Starting test..."); - - // Used to switch between the various function options. - typedef bool (AdaptDispatch::*ScrollFunc)(const unsigned int); - ScrollFunc scrollFunc = nullptr; - - // Modify variables based on directionality of this test - ScrollDirection direction; - unsigned int dir; - VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiDirection", dir)); - direction = (ScrollDirection)dir; - unsigned int uiMagnitude; - VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiMagnitude", uiMagnitude)); - SHORT sMagnitude = (SHORT)uiMagnitude; - - switch (direction) - { - case ScrollDirection::UP: - Log::Comment(L"Testing up direction."); - scrollFunc = &AdaptDispatch::ScrollUp; - break; - case ScrollDirection::DOWN: - Log::Comment(L"Testing down direction."); - scrollFunc = &AdaptDispatch::ScrollDown; - break; - } - Log::Comment(NoThrowString().Format(L"Scrolling by %d lines", uiMagnitude)); - if (scrollFunc == nullptr) - { - VERIFY_FAIL(); - return; - } - - // place the cursor in the center. - _testGetSet->PrepData(CursorX::XCENTER, CursorY::YCENTER); - - // Save the cursor position. It shouldn't move for the rest of the test. - COORD coordCursorExpected = _testGetSet->_coordCursorPos; - - // Fill the entire buffer with Qs. Blue on Green. - WCHAR const wchOuterBuffer = 'Q'; - WORD const wAttrOuterBuffer = FOREGROUND_BLUE | BACKGROUND_GREEN; - SMALL_RECT srOuterBuffer; - srOuterBuffer.Top = 0; - srOuterBuffer.Left = 0; - srOuterBuffer.Bottom = _testGetSet->_coordBufferSize.Y; - srOuterBuffer.Right = _testGetSet->_coordBufferSize.X; - _testGetSet->FillRectangle(srOuterBuffer, wchOuterBuffer, wAttrOuterBuffer); - - // Fill the viewport with Rs. Red on Blue. - WCHAR const wchViewport = 'R'; - WORD const wAttrViewport = FOREGROUND_RED | BACKGROUND_BLUE; - SMALL_RECT srViewport = _testGetSet->_srViewport; - _testGetSet->FillRectangle(srViewport, wchViewport, wAttrViewport); - - // Add some characters to see if they moved. - // change the color too so we can make sure that it's fine - - WORD const wAttrTestText = FOREGROUND_GREEN; - PWSTR const pwszTestText = L"ABCDE"; // Text is written at y=34, moves to y=33 - size_t cchTestText = wcslen(pwszTestText); - SMALL_RECT srTestText; - srTestText.Top = _testGetSet->_coordCursorPos.Y; - srTestText.Bottom = srTestText.Top + 1; - srTestText.Left = _testGetSet->_coordCursorPos.X; - srTestText.Right = srTestText.Left + (SHORT)cchTestText; - _testGetSet->InsertString(_testGetSet->_coordCursorPos, pwszTestText, wAttrTestText); - - //Scroll Up one line - VERIFY_IS_TRUE((_pDispatch->*(scrollFunc))(sMagnitude), L"Verify Scroll call was sucessful."); - - // verify cursor didn't move - VERIFY_ARE_EQUAL(coordCursorExpected, _testGetSet->_coordCursorPos, L"Verify cursor didn't move from insert operation."); - - // Verify the field of Qs didn't change outside the viewport. - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srOuterBuffer, wchOuterBuffer, wAttrOuterBuffer, srViewport), - L"Field of Qs outside viewport should remain unchanged."); - - // Okay, this part get confusing. These change depending on the direction of the test. - // direction InViewport Outside - // UP Bottom Line Top minus One - // DOWN Top Line Bottom plus One - const bool fScrollUp = (direction == ScrollDirection::UP); - SMALL_RECT srInViewport = srViewport; - srInViewport.Top = (fScrollUp) ? (srViewport.Bottom - sMagnitude) : (srViewport.Top); - srInViewport.Bottom = srInViewport.Top + sMagnitude; - WCHAR const wchInViewport = ' '; - WORD const wAttrInViewport = _testGetSet->_wAttribute; - - // Verify the bottom line is now empty - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srInViewport, wchInViewport, wAttrInViewport), - L"InViewport line(s) should now be blank, with default buffer attributes"); - - SMALL_RECT srOutside = srViewport; - srOutside.Top = (fScrollUp) ? (srViewport.Top - sMagnitude) : (srViewport.Bottom); - srOutside.Bottom = srOutside.Top + sMagnitude; - WCHAR const wchOutside = wchOuterBuffer; - WORD const wAttrOutside = wAttrOuterBuffer; - - // Verify the line above the viewport is unchanged - VERIFY_IS_TRUE(_testGetSet->ValidateRectangleContains(srOutside, wchOutside, wAttrOutside), - L"Line(s) above the viewport is unchanged"); - - // Verify that the line where the ABCDE is now wchViewport - COORD coordTestText; - PWSTR const pwszNewTestText = L"RRRRR"; - coordTestText.X = srTestText.Left; - coordTestText.Y = srTestText.Top; - VERIFY_IS_TRUE(_testGetSet->ValidateString(coordTestText, pwszNewTestText, wAttrViewport), L"Contents of viewport should have shifted to where the string used to be."); - - // Verify that the line above/below the ABCDE now has the ABCDE - coordTestText.X = srTestText.Left; - coordTestText.Y = (fScrollUp) ? (srTestText.Top - sMagnitude) : (srTestText.Top + sMagnitude); - VERIFY_IS_TRUE(_testGetSet->ValidateString(coordTestText, pwszTestText, wAttrTestText), L"String should have moved up/down by given magnitude."); - } - TEST_METHOD(CursorKeysModeTest) { Log::Comment(L"Starting test..."); @@ -3426,30 +2695,33 @@ class AdapterTest // The cursor will be moved to the same relative location in the new viewport with origin @ 0, 0 const COORD coordRelativeCursor = { _testGetSet->_coordCursorPos.X - _testGetSet->_srViewport.Left, _testGetSet->_coordCursorPos.Y - _testGetSet->_srViewport.Top }; - - // Cursor to 1,1 - _testGetSet->_coordExpectedCursorPos = { 0, 0 }; - _testGetSet->_fSetConsoleCursorPositionResult = true; - _testGetSet->_fPrivateSetLegacyAttributesResult = true; - _testGetSet->_fPrivateSetDefaultAttributesResult = true; - _testGetSet->_fPrivateBoldTextResult = true; - _testGetSet->_fExpectedForeground = true; - _testGetSet->_fExpectedBackground = true; - _testGetSet->_fExpectedMeta = true; - _testGetSet->_fExpectedIsBold = false; - _testGetSet->_expectedShowCursor = true; - _testGetSet->_privateShowCursorResult = true; const COORD coordExpectedCursorPos = { 0, 0 }; - // We're expecting _SetDefaultColorHelper to call - // PrivateSetLegacyAttributes with 0 as the wAttr param. - _testGetSet->_wExpectedAttribute = 0; + auto prepExpectedParameters = [&]() { + // Cursor to 1,1 + _testGetSet->_coordExpectedCursorPos = { 0, 0 }; + _testGetSet->_fSetConsoleCursorPositionResult = true; + _testGetSet->_fPrivateSetLegacyAttributesResult = true; + _testGetSet->_fPrivateSetDefaultAttributesResult = true; + _testGetSet->_fPrivateBoldTextResult = true; + _testGetSet->_fExpectedForeground = true; + _testGetSet->_fExpectedBackground = true; + _testGetSet->_fExpectedMeta = true; + _testGetSet->_fExpectedIsBold = false; + _testGetSet->_expectedShowCursor = true; + _testGetSet->_privateShowCursorResult = true; - // Prepare the results of SoftReset api calls - _testGetSet->_fPrivateSetCursorKeysModeResult = true; - _testGetSet->_fPrivateSetKeypadModeResult = true; - _testGetSet->_fGetConsoleScreenBufferInfoExResult = true; - _testGetSet->_fPrivateSetScrollingRegionResult = true; + // We're expecting _SetDefaultColorHelper to call + // PrivateSetLegacyAttributes with 0 as the wAttr param. + _testGetSet->_wExpectedAttribute = 0; + + // Prepare the results of SoftReset api calls + _testGetSet->_fPrivateSetCursorKeysModeResult = true; + _testGetSet->_fPrivateSetKeypadModeResult = true; + _testGetSet->_fGetConsoleScreenBufferInfoExResult = true; + _testGetSet->_fPrivateSetScrollingRegionResult = true; + }; + prepExpectedParameters(); VERIFY_IS_TRUE(_pDispatch->HardReset()); VERIFY_ARE_EQUAL(_testGetSet->_coordCursorPos, coordExpectedCursorPos); @@ -3457,18 +2729,21 @@ class AdapterTest Log::Comment(L"Test 2: Gracefully fail when getting console information fails."); _testGetSet->PrepData(); + prepExpectedParameters(); _testGetSet->_fGetConsoleScreenBufferInfoExResult = false; VERIFY_IS_FALSE(_pDispatch->HardReset()); Log::Comment(L"Test 3: Gracefully fail when filling the rectangle fails."); _testGetSet->PrepData(); + prepExpectedParameters(); _testGetSet->_fFillConsoleOutputCharacterWResult = false; VERIFY_IS_FALSE(_pDispatch->HardReset()); Log::Comment(L"Test 4: Gracefully fail when setting the window fails."); _testGetSet->PrepData(); + prepExpectedParameters(); _testGetSet->_fSetConsoleWindowInfoResult = false; VERIFY_IS_FALSE(_pDispatch->HardReset()); diff --git a/src/terminal/parser/ft_fuzzer/fuzzing_directed.h b/src/terminal/parser/ft_fuzzer/fuzzing_directed.h index 5e816fbbd7c..9bad935e803 100644 --- a/src/terminal/parser/ft_fuzzer/fuzzing_directed.h +++ b/src/terminal/parser/ft_fuzzer/fuzzing_directed.h @@ -898,7 +898,7 @@ namespace fuzz _Type operator->() const throw() { - return (m_fFuzzed) ? m_t : m_tInit; + return (this->m_fFuzzed) ? this->m_t : m_tInit; } // This operator makes it possible to invoke the fuzzing map @@ -960,7 +960,7 @@ namespace fuzz __inline virtual _Type** operator&() throw() { m_ftEffectiveTraits |= TRAIT_TRANSFER_ALLOCATION; - return (m_fFuzzed) ? &m_t : &m_tInit; + return (this->m_fFuzzed) ? &(this->m_t) : &m_tInit; } private: @@ -978,11 +978,11 @@ namespace fuzz void OnFuzzedValueFromMap() { m_pszFuzzed = nullptr; - m_ftEffectiveTraits = m_traits; - m_pfnOnFuzzedValueFromMap = [&](_Type* psz, std::function dealloc) { + m_ftEffectiveTraits = this->m_traits; + this->m_pfnOnFuzzedValueFromMap = [&](_Type* psz, std::function dealloc) { FreeFuzzedString(); _Type* pszFuzzed = psz; - if (psz && psz != m_tInit) + if (psz && psz != this->m_tInit) { size_t cb = (sizeof(_Type) == sizeof(char)) ? (strlen(reinterpret_cast(psz)) + 1) * sizeof(char) : @@ -1014,8 +1014,8 @@ namespace fuzz // allocation and deallocation responsibilities. if (m_ftEffectiveTraits & TRAIT_TRANSFER_ALLOCATION) { - _Alloc::Free(m_tInit); - m_tInit = nullptr; + _Alloc::Free(this->m_tInit); + this->m_tInit = nullptr; } else { @@ -1070,11 +1070,11 @@ namespace fuzz protected: __inline virtual _Type GetValueFromMap() { - if (!m_fFuzzed) + if (!this->m_fFuzzed) { - m_t = 0; - m_fFuzzed = TRUE; - for (auto& r : m_map) + this->m_t = 0; + this->m_fFuzzed = TRUE; + for (auto& r : this->m_map) { // Generate a new random value during each map entry // and use it to evaluate if each individual fuzz map @@ -1087,12 +1087,12 @@ namespace fuzz int iLow = iHigh - (r.range.iHigh - r.range.iLow); if (iLow <= wRandom && wRandom < iHigh) { - m_t |= CallFuzzMapFunction(r.fte.pfnFuzz, m_tInit, m_tArgs); + this->m_t |= CallFuzzMapFunction(r.fte.pfnFuzz, this->m_tInit, m_tArgs); } } } - return m_t; + return this->m_t; } }; } diff --git a/src/tools/ColorTool/ColorTool/Resources.resx b/src/tools/ColorTool/ColorTool/Resources.resx index ee2a4a96736..091bbf02250 100644 --- a/src/tools/ColorTool/ColorTool/Resources.resx +++ b/src/tools/ColorTool/ColorTool/Resources.resx @@ -162,7 +162,7 @@ Options: -s, --schemes : Displays all available schemes -l, --location : Displays the full path to the schemes directory -v, --version : Display the version number - -o, --output <filename> : output the current color table to an file (in .ini format) + -o, --output <filename> : output the current color table to a file (in .ini format) Available importers: {0} @@ -170,4 +170,4 @@ Available importers: Wrote selected scheme to the defaults. - \ No newline at end of file + diff --git a/src/tsf/ConsoleTSF.cpp b/src/tsf/ConsoleTSF.cpp index a47c63d6b75..17d3a687108 100644 --- a/src/tsf/ConsoleTSF.cpp +++ b/src/tsf/ConsoleTSF.cpp @@ -39,7 +39,7 @@ const GUID GUID_APPLICATION = { 0x626761ad, 0x78d2, 0x44d2, { 0xbe, 0x8b, 0x75, hr = ::CoCreateInstance(CLSID_TF_ThreadMgr, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&_spITfThreadMgr)); Init_CheckResult(); - hr = _spITfThreadMgr->Activate(&_tid); + hr = _spITfThreadMgr->ActivateEx(&_tid, TF_TMAE_CONSOLE); Init_CheckResult(); // Create Cicero document manager and input context. diff --git a/src/types/CodepointWidthDetector.cpp b/src/types/CodepointWidthDetector.cpp index cf113134ced..eb29a8af488 100644 --- a/src/types/CodepointWidthDetector.cpp +++ b/src/types/CodepointWidthDetector.cpp @@ -4,35 +4,344 @@ #include "precomp.h" #include "inc/CodepointWidthDetector.hpp" +namespace +{ + // used to store range data in CodepointWidthDetector's internal map + struct UnicodeRange final + { + unsigned int lowerBound; + unsigned int upperBound; + CodepointWidth width; + }; + + static bool operator<(const UnicodeRange& range, const unsigned int searchTerm) noexcept + { + return range.upperBound < searchTerm; + } + + static constexpr std::array s_wideAndAmbiguousTable{ + // generated from http://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt + // anything not present here is presumed to be Narrow. + UnicodeRange{ 0xa1, 0xa1, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xa4, 0xa4, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xa7, 0xa8, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xaa, 0xaa, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xad, 0xae, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xb0, 0xb4, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xb6, 0xba, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xbc, 0xbf, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xc6, 0xc6, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xd0, 0xd0, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xd7, 0xd8, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xde, 0xe1, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xe6, 0xe6, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xe8, 0xea, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xec, 0xed, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xf0, 0xf0, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xf2, 0xf3, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xf7, 0xfa, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xfc, 0xfc, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xfe, 0xfe, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x101, 0x101, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x111, 0x111, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x113, 0x113, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x11b, 0x11b, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x126, 0x127, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x12b, 0x12b, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x131, 0x133, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x138, 0x138, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x13f, 0x142, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x144, 0x144, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x148, 0x14b, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x14d, 0x14d, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x152, 0x153, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x166, 0x167, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x16b, 0x16b, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x1ce, 0x1ce, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x1d0, 0x1d0, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x1d2, 0x1d2, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x1d4, 0x1d4, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x1d6, 0x1d6, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x1d8, 0x1d8, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x1da, 0x1da, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x1dc, 0x1dc, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x251, 0x251, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x261, 0x261, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2c4, 0x2c4, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2c7, 0x2c7, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2c9, 0x2cb, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2cd, 0x2cd, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2d0, 0x2d0, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2d8, 0x2db, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2dd, 0x2dd, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2df, 0x2df, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x300, 0x36f, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x391, 0x3a1, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x3a3, 0x3a9, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x3b1, 0x3c1, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x3c3, 0x3c9, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x401, 0x401, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x410, 0x44f, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x451, 0x451, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x1100, 0x115f, CodepointWidth::Wide }, + UnicodeRange{ 0x2010, 0x2010, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2013, 0x2016, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2018, 0x2019, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x201c, 0x201d, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2020, 0x2022, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2024, 0x2027, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2030, 0x2030, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2032, 0x2033, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2035, 0x2035, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x203b, 0x203b, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x203e, 0x203e, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2074, 0x2074, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x207f, 0x207f, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2081, 0x2084, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x20ac, 0x20ac, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2103, 0x2103, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2105, 0x2105, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2109, 0x2109, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2113, 0x2113, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2116, 0x2116, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2121, 0x2122, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2126, 0x2126, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x212b, 0x212b, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2153, 0x2154, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x215b, 0x215e, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2160, 0x216b, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2170, 0x2179, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2189, 0x2189, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2190, 0x2199, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x21b8, 0x21b9, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x21d2, 0x21d2, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x21d4, 0x21d4, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x21e7, 0x21e7, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2200, 0x2200, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2202, 0x2203, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2207, 0x2208, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x220b, 0x220b, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x220f, 0x220f, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2211, 0x2211, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2215, 0x2215, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x221a, 0x221a, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x221d, 0x2220, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2223, 0x2223, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2225, 0x2225, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2227, 0x222c, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x222e, 0x222e, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2234, 0x2237, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x223c, 0x223d, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2248, 0x2248, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x224c, 0x224c, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2252, 0x2252, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2260, 0x2261, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2264, 0x2267, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x226a, 0x226b, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x226e, 0x226f, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2282, 0x2283, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2286, 0x2287, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2295, 0x2295, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2299, 0x2299, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x22a5, 0x22a5, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x22bf, 0x22bf, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2312, 0x2312, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x231a, 0x231b, CodepointWidth::Wide }, + UnicodeRange{ 0x2329, 0x232a, CodepointWidth::Wide }, + UnicodeRange{ 0x23e9, 0x23ec, CodepointWidth::Wide }, + UnicodeRange{ 0x23f0, 0x23f0, CodepointWidth::Wide }, + UnicodeRange{ 0x23f3, 0x23f3, CodepointWidth::Wide }, + UnicodeRange{ 0x2460, 0x24e9, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x24eb, 0x254b, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2550, 0x2573, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2580, 0x258f, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2592, 0x2595, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x25a0, 0x25a1, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x25a3, 0x25a9, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x25b2, 0x25b3, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x25b6, 0x25b7, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x25bc, 0x25bd, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x25c0, 0x25c1, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x25c6, 0x25c8, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x25cb, 0x25cb, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x25ce, 0x25d1, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x25e2, 0x25e5, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x25ef, 0x25ef, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x25fd, 0x25fe, CodepointWidth::Wide }, + UnicodeRange{ 0x2605, 0x2606, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2609, 0x2609, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x260e, 0x260f, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2614, 0x2615, CodepointWidth::Wide }, + UnicodeRange{ 0x261c, 0x261c, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x261e, 0x261e, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2640, 0x2640, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2642, 0x2642, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2648, 0x2653, CodepointWidth::Wide }, + UnicodeRange{ 0x2660, 0x2661, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2663, 0x2665, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2667, 0x266a, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x266c, 0x266d, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x266f, 0x266f, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x267f, 0x267f, CodepointWidth::Wide }, + UnicodeRange{ 0x2693, 0x2693, CodepointWidth::Wide }, + UnicodeRange{ 0x269e, 0x269f, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x26a1, 0x26a1, CodepointWidth::Wide }, + UnicodeRange{ 0x26aa, 0x26ab, CodepointWidth::Wide }, + UnicodeRange{ 0x26bd, 0x26be, CodepointWidth::Wide }, + UnicodeRange{ 0x26bf, 0x26bf, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x26c4, 0x26c5, CodepointWidth::Wide }, + UnicodeRange{ 0x26c6, 0x26cd, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x26ce, 0x26ce, CodepointWidth::Wide }, + UnicodeRange{ 0x26cf, 0x26d3, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x26d4, 0x26d4, CodepointWidth::Wide }, + UnicodeRange{ 0x26d5, 0x26e1, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x26e3, 0x26e3, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x26e8, 0x26e9, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x26ea, 0x26ea, CodepointWidth::Wide }, + UnicodeRange{ 0x26eb, 0x26f1, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x26f2, 0x26f3, CodepointWidth::Wide }, + UnicodeRange{ 0x26f4, 0x26f4, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x26f5, 0x26f5, CodepointWidth::Wide }, + UnicodeRange{ 0x26f6, 0x26f9, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x26fa, 0x26fa, CodepointWidth::Wide }, + UnicodeRange{ 0x26fb, 0x26fc, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x26fd, 0x26fd, CodepointWidth::Wide }, + UnicodeRange{ 0x26fe, 0x26ff, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2705, 0x2705, CodepointWidth::Wide }, + UnicodeRange{ 0x270a, 0x270b, CodepointWidth::Wide }, + UnicodeRange{ 0x2728, 0x2728, CodepointWidth::Wide }, + UnicodeRange{ 0x273d, 0x273d, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x274c, 0x274c, CodepointWidth::Wide }, + UnicodeRange{ 0x274e, 0x274e, CodepointWidth::Wide }, + UnicodeRange{ 0x2753, 0x2755, CodepointWidth::Wide }, + UnicodeRange{ 0x2757, 0x2757, CodepointWidth::Wide }, + UnicodeRange{ 0x2776, 0x277f, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2795, 0x2797, CodepointWidth::Wide }, + UnicodeRange{ 0x27b0, 0x27b0, CodepointWidth::Wide }, + UnicodeRange{ 0x27bf, 0x27bf, CodepointWidth::Wide }, + UnicodeRange{ 0x2b1b, 0x2b1c, CodepointWidth::Wide }, + UnicodeRange{ 0x2b50, 0x2b50, CodepointWidth::Wide }, + UnicodeRange{ 0x2b55, 0x2b55, CodepointWidth::Wide }, + UnicodeRange{ 0x2b56, 0x2b59, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x2e80, 0x2e99, CodepointWidth::Wide }, + UnicodeRange{ 0x2e9b, 0x2ef3, CodepointWidth::Wide }, + UnicodeRange{ 0x2f00, 0x2fd5, CodepointWidth::Wide }, + UnicodeRange{ 0x2ff0, 0x2ffb, CodepointWidth::Wide }, + UnicodeRange{ 0x3000, 0x303e, CodepointWidth::Wide }, + UnicodeRange{ 0x3041, 0x3096, CodepointWidth::Wide }, + UnicodeRange{ 0x3099, 0x30ff, CodepointWidth::Wide }, + UnicodeRange{ 0x3105, 0x312e, CodepointWidth::Wide }, + UnicodeRange{ 0x3131, 0x318e, CodepointWidth::Wide }, + UnicodeRange{ 0x3190, 0x31ba, CodepointWidth::Wide }, + UnicodeRange{ 0x31c0, 0x31e3, CodepointWidth::Wide }, + UnicodeRange{ 0x31f0, 0x321e, CodepointWidth::Wide }, + UnicodeRange{ 0x3220, 0x3247, CodepointWidth::Wide }, + UnicodeRange{ 0x3248, 0x324f, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x3250, 0x32fe, CodepointWidth::Wide }, + UnicodeRange{ 0x3300, 0x4dbf, CodepointWidth::Wide }, + UnicodeRange{ 0x4e00, 0xa48c, CodepointWidth::Wide }, + UnicodeRange{ 0xa490, 0xa4c6, CodepointWidth::Wide }, + UnicodeRange{ 0xa960, 0xa97c, CodepointWidth::Wide }, + UnicodeRange{ 0xac00, 0xd7a3, CodepointWidth::Wide }, + UnicodeRange{ 0xe000, 0xf8ff, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xf900, 0xfaff, CodepointWidth::Wide }, + UnicodeRange{ 0xfe00, 0xfe0f, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xfe10, 0xfe19, CodepointWidth::Wide }, + UnicodeRange{ 0xfe30, 0xfe52, CodepointWidth::Wide }, + UnicodeRange{ 0xfe54, 0xfe66, CodepointWidth::Wide }, + UnicodeRange{ 0xfe68, 0xfe6b, CodepointWidth::Wide }, + UnicodeRange{ 0xff01, 0xff60, CodepointWidth::Wide }, + UnicodeRange{ 0xffe0, 0xffe6, CodepointWidth::Wide }, + UnicodeRange{ 0xfffd, 0xfffd, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x16fe0, 0x16fe1, CodepointWidth::Wide }, + UnicodeRange{ 0x17000, 0x187ec, CodepointWidth::Wide }, + UnicodeRange{ 0x18800, 0x18af2, CodepointWidth::Wide }, + UnicodeRange{ 0x1b000, 0x1b11e, CodepointWidth::Wide }, + UnicodeRange{ 0x1b170, 0x1b2fb, CodepointWidth::Wide }, + UnicodeRange{ 0x1f004, 0x1f004, CodepointWidth::Wide }, + UnicodeRange{ 0x1f0cf, 0x1f0cf, CodepointWidth::Wide }, + UnicodeRange{ 0x1f100, 0x1f10a, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x1f110, 0x1f12d, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x1f130, 0x1f169, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x1f170, 0x1f18d, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x1f18e, 0x1f18e, CodepointWidth::Wide }, + UnicodeRange{ 0x1f18f, 0x1f190, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x1f191, 0x1f19a, CodepointWidth::Wide }, + UnicodeRange{ 0x1f19b, 0x1f1ac, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x1f200, 0x1f202, CodepointWidth::Wide }, + UnicodeRange{ 0x1f210, 0x1f23b, CodepointWidth::Wide }, + UnicodeRange{ 0x1f240, 0x1f248, CodepointWidth::Wide }, + UnicodeRange{ 0x1f250, 0x1f251, CodepointWidth::Wide }, + UnicodeRange{ 0x1f260, 0x1f265, CodepointWidth::Wide }, + UnicodeRange{ 0x1f300, 0x1f320, CodepointWidth::Wide }, + UnicodeRange{ 0x1f32d, 0x1f335, CodepointWidth::Wide }, + UnicodeRange{ 0x1f337, 0x1f37c, CodepointWidth::Wide }, + UnicodeRange{ 0x1f37e, 0x1f393, CodepointWidth::Wide }, + UnicodeRange{ 0x1f3a0, 0x1f3ca, CodepointWidth::Wide }, + UnicodeRange{ 0x1f3cf, 0x1f3d3, CodepointWidth::Wide }, + UnicodeRange{ 0x1f3e0, 0x1f3f0, CodepointWidth::Wide }, + UnicodeRange{ 0x1f3f4, 0x1f3f4, CodepointWidth::Wide }, + UnicodeRange{ 0x1f3f8, 0x1f43e, CodepointWidth::Wide }, + UnicodeRange{ 0x1f440, 0x1f440, CodepointWidth::Wide }, + UnicodeRange{ 0x1f442, 0x1f4fc, CodepointWidth::Wide }, + UnicodeRange{ 0x1f4ff, 0x1f53d, CodepointWidth::Wide }, + UnicodeRange{ 0x1f54b, 0x1f54e, CodepointWidth::Wide }, + UnicodeRange{ 0x1f550, 0x1f567, CodepointWidth::Wide }, + UnicodeRange{ 0x1f57a, 0x1f57a, CodepointWidth::Wide }, + UnicodeRange{ 0x1f595, 0x1f596, CodepointWidth::Wide }, + UnicodeRange{ 0x1f5a4, 0x1f5a4, CodepointWidth::Wide }, + UnicodeRange{ 0x1f5fb, 0x1f64f, CodepointWidth::Wide }, + UnicodeRange{ 0x1f680, 0x1f6c5, CodepointWidth::Wide }, + UnicodeRange{ 0x1f6cc, 0x1f6cc, CodepointWidth::Wide }, + UnicodeRange{ 0x1f6d0, 0x1f6d2, CodepointWidth::Wide }, + UnicodeRange{ 0x1f6eb, 0x1f6ec, CodepointWidth::Wide }, + UnicodeRange{ 0x1f6f4, 0x1f6f8, CodepointWidth::Wide }, + UnicodeRange{ 0x1f910, 0x1f93e, CodepointWidth::Wide }, + UnicodeRange{ 0x1f940, 0x1f94c, CodepointWidth::Wide }, + UnicodeRange{ 0x1f950, 0x1f96b, CodepointWidth::Wide }, + UnicodeRange{ 0x1f980, 0x1f997, CodepointWidth::Wide }, + UnicodeRange{ 0x1f9c0, 0x1f9c0, CodepointWidth::Wide }, + UnicodeRange{ 0x1f9d0, 0x1f9e6, CodepointWidth::Wide }, + UnicodeRange{ 0x20000, 0x2fffd, CodepointWidth::Wide }, + UnicodeRange{ 0x30000, 0x3fffd, CodepointWidth::Wide }, + UnicodeRange{ 0xe0100, 0xe01ef, CodepointWidth::Ambiguous }, + UnicodeRange{ 0xf0000, 0xffffd, CodepointWidth::Ambiguous }, + UnicodeRange{ 0x100000, 0x10fffd, CodepointWidth::Ambiguous } + }; +} + +// Routine Description: +// - Constructs an instance of the CodepointWidthDetector class +CodepointWidthDetector::CodepointWidthDetector() noexcept : + _fallbackCache{}, + _pfnFallbackMethod{} +{ +} + // Routine Description: // - returns the width type of codepoint by searching the map generated from the unicode spec // Arguments: // - glyph - the utf16 encoded codepoint to search for // Return Value: // - the width type of the codepoint -CodepointWidth CodepointWidthDetector::GetWidth(const std::wstring_view glyph) const noexcept +CodepointWidth CodepointWidthDetector::GetWidth(const std::wstring_view glyph) const { if (glyph.empty()) { return CodepointWidth::Invalid; } - if (_map.empty()) - { - const_cast(this)->_populateUnicodeSearchMap(); - } - const auto codepoint = _extractCodepoint(glyph); - UnicodeRange search{ codepoint }; - auto it = _map.find(search); - if (it == _map.end()) - { - return CodepointWidth::Invalid; - } - else + const auto it = std::lower_bound(s_wideAndAmbiguousTable.begin(), s_wideAndAmbiguousTable.end(), codepoint); + + // For characters that are not _in_ the table, lower_bound will return the nearest item that is. + // We must check its bounds to make sure that our hit was a true hit. + if (it != s_wideAndAmbiguousTable.end() && codepoint >= it->lowerBound && codepoint <= it->upperBound) { - return it->second; + return it->width; } + + return CodepointWidth::Narrow; } // Routine Description: @@ -75,7 +384,7 @@ bool CodepointWidthDetector::IsWide(const std::wstring_view glyph) const // If not, go to the lookup table. else if (width == CodepointWidth::Ambiguous) { - if (_hasFallback) + if (_pfnFallbackMethod) { return _checkFallbackViaCache(glyph); } @@ -112,7 +421,7 @@ bool CodepointWidthDetector::_lookupIsWide(const std::wstring_view glyph) const // If it's ambiguous, then ask the font if we can. if (width == CodepointWidth::Ambiguous) { - if (_hasFallback) + if (_pfnFallbackMethod) { return _checkFallbackViaCache(glyph); } @@ -142,7 +451,7 @@ bool CodepointWidthDetector::_checkFallbackViaCache(const std::wstring_view glyp const std::wstring findMe{ glyph }; // TODO: Cache needs to be emptied when font changes. - auto it = _fallbackCache.find(findMe); + const auto it = _fallbackCache.find(findMe); if (it == _fallbackCache.end()) { auto result = _pfnFallbackMethod(glyph); @@ -161,7 +470,7 @@ bool CodepointWidthDetector::_checkFallbackViaCache(const std::wstring_view glyp // - glyph - the utf16 encoded codepoint convert // Return Value: // - the codepoint being stored -unsigned int CodepointWidthDetector::_extractCodepoint(const std::wstring_view glyph) const noexcept +unsigned int CodepointWidthDetector::_extractCodepoint(const std::wstring_view glyph) noexcept { if (glyph.size() == 1) { @@ -193,7 +502,6 @@ unsigned int CodepointWidthDetector::_extractCodepoint(const std::wstring_view g void CodepointWidthDetector::SetFallbackMethod(std::function pfnFallback) { _pfnFallbackMethod = pfnFallback; - _hasFallback = true; } // Method Description: @@ -208,1096 +516,3 @@ void CodepointWidthDetector::NotifyFontChanged() const noexcept { _fallbackCache.clear(); } - -void CodepointWidthDetector::_populateUnicodeSearchMap() -{ - // generated from http://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt - _map[UnicodeRange(0, 160)] = CodepointWidth::Narrow; - _map[UnicodeRange(161, 161)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(162, 163)] = CodepointWidth::Narrow; - _map[UnicodeRange(164, 164)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(165, 166)] = CodepointWidth::Narrow; - _map[UnicodeRange(167, 168)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(169, 169)] = CodepointWidth::Narrow; - _map[UnicodeRange(170, 170)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(171, 172)] = CodepointWidth::Narrow; - _map[UnicodeRange(173, 174)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(175, 175)] = CodepointWidth::Narrow; - _map[UnicodeRange(176, 180)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(181, 181)] = CodepointWidth::Narrow; - _map[UnicodeRange(182, 186)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(187, 187)] = CodepointWidth::Narrow; - _map[UnicodeRange(188, 191)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(192, 197)] = CodepointWidth::Narrow; - _map[UnicodeRange(198, 198)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(199, 207)] = CodepointWidth::Narrow; - _map[UnicodeRange(208, 208)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(209, 214)] = CodepointWidth::Narrow; - _map[UnicodeRange(215, 216)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(217, 221)] = CodepointWidth::Narrow; - _map[UnicodeRange(222, 225)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(226, 229)] = CodepointWidth::Narrow; - _map[UnicodeRange(230, 230)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(231, 231)] = CodepointWidth::Narrow; - _map[UnicodeRange(232, 234)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(235, 235)] = CodepointWidth::Narrow; - _map[UnicodeRange(236, 237)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(238, 239)] = CodepointWidth::Narrow; - _map[UnicodeRange(240, 240)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(241, 241)] = CodepointWidth::Narrow; - _map[UnicodeRange(242, 243)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(244, 246)] = CodepointWidth::Narrow; - _map[UnicodeRange(247, 250)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(251, 251)] = CodepointWidth::Narrow; - _map[UnicodeRange(252, 252)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(253, 253)] = CodepointWidth::Narrow; - _map[UnicodeRange(254, 254)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(255, 256)] = CodepointWidth::Narrow; - _map[UnicodeRange(257, 257)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(258, 272)] = CodepointWidth::Narrow; - _map[UnicodeRange(273, 273)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(274, 274)] = CodepointWidth::Narrow; - _map[UnicodeRange(275, 275)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(276, 282)] = CodepointWidth::Narrow; - _map[UnicodeRange(283, 283)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(284, 293)] = CodepointWidth::Narrow; - _map[UnicodeRange(294, 295)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(296, 298)] = CodepointWidth::Narrow; - _map[UnicodeRange(299, 299)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(300, 304)] = CodepointWidth::Narrow; - _map[UnicodeRange(305, 307)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(308, 311)] = CodepointWidth::Narrow; - _map[UnicodeRange(312, 312)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(313, 318)] = CodepointWidth::Narrow; - _map[UnicodeRange(319, 322)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(323, 323)] = CodepointWidth::Narrow; - _map[UnicodeRange(324, 324)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(325, 327)] = CodepointWidth::Narrow; - _map[UnicodeRange(328, 331)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(332, 332)] = CodepointWidth::Narrow; - _map[UnicodeRange(333, 333)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(334, 337)] = CodepointWidth::Narrow; - _map[UnicodeRange(338, 339)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(340, 357)] = CodepointWidth::Narrow; - _map[UnicodeRange(358, 359)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(360, 362)] = CodepointWidth::Narrow; - _map[UnicodeRange(363, 363)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(364, 461)] = CodepointWidth::Narrow; - _map[UnicodeRange(462, 462)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(463, 463)] = CodepointWidth::Narrow; - _map[UnicodeRange(464, 464)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(465, 465)] = CodepointWidth::Narrow; - _map[UnicodeRange(466, 466)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(467, 467)] = CodepointWidth::Narrow; - _map[UnicodeRange(468, 468)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(469, 469)] = CodepointWidth::Narrow; - _map[UnicodeRange(470, 470)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(471, 471)] = CodepointWidth::Narrow; - _map[UnicodeRange(472, 472)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(473, 473)] = CodepointWidth::Narrow; - _map[UnicodeRange(474, 474)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(475, 475)] = CodepointWidth::Narrow; - _map[UnicodeRange(476, 476)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(477, 592)] = CodepointWidth::Narrow; - _map[UnicodeRange(593, 593)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(594, 608)] = CodepointWidth::Narrow; - _map[UnicodeRange(609, 609)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(610, 707)] = CodepointWidth::Narrow; - _map[UnicodeRange(708, 708)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(709, 710)] = CodepointWidth::Narrow; - _map[UnicodeRange(711, 711)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(712, 712)] = CodepointWidth::Narrow; - _map[UnicodeRange(713, 715)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(716, 716)] = CodepointWidth::Narrow; - _map[UnicodeRange(717, 717)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(718, 719)] = CodepointWidth::Narrow; - _map[UnicodeRange(720, 720)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(721, 727)] = CodepointWidth::Narrow; - _map[UnicodeRange(728, 731)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(732, 732)] = CodepointWidth::Narrow; - _map[UnicodeRange(733, 733)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(734, 734)] = CodepointWidth::Narrow; - _map[UnicodeRange(735, 735)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(736, 767)] = CodepointWidth::Narrow; - _map[UnicodeRange(768, 879)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(880, 887)] = CodepointWidth::Narrow; - _map[UnicodeRange(890, 895)] = CodepointWidth::Narrow; - _map[UnicodeRange(900, 906)] = CodepointWidth::Narrow; - _map[UnicodeRange(908, 908)] = CodepointWidth::Narrow; - _map[UnicodeRange(910, 912)] = CodepointWidth::Narrow; - _map[UnicodeRange(913, 929)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(931, 937)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(938, 944)] = CodepointWidth::Narrow; - _map[UnicodeRange(945, 961)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(962, 962)] = CodepointWidth::Narrow; - _map[UnicodeRange(963, 969)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(970, 1024)] = CodepointWidth::Narrow; - _map[UnicodeRange(1025, 1025)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(1026, 1039)] = CodepointWidth::Narrow; - _map[UnicodeRange(1040, 1103)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(1104, 1104)] = CodepointWidth::Narrow; - _map[UnicodeRange(1105, 1105)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(1106, 1327)] = CodepointWidth::Narrow; - _map[UnicodeRange(1329, 1366)] = CodepointWidth::Narrow; - _map[UnicodeRange(1369, 1375)] = CodepointWidth::Narrow; - _map[UnicodeRange(1377, 1415)] = CodepointWidth::Narrow; - _map[UnicodeRange(1417, 1418)] = CodepointWidth::Narrow; - _map[UnicodeRange(1421, 1423)] = CodepointWidth::Narrow; - _map[UnicodeRange(1425, 1479)] = CodepointWidth::Narrow; - _map[UnicodeRange(1488, 1514)] = CodepointWidth::Narrow; - _map[UnicodeRange(1520, 1524)] = CodepointWidth::Narrow; - _map[UnicodeRange(1536, 1564)] = CodepointWidth::Narrow; - _map[UnicodeRange(1566, 1805)] = CodepointWidth::Narrow; - _map[UnicodeRange(1807, 1866)] = CodepointWidth::Narrow; - _map[UnicodeRange(1869, 1969)] = CodepointWidth::Narrow; - _map[UnicodeRange(1984, 2042)] = CodepointWidth::Narrow; - _map[UnicodeRange(2048, 2093)] = CodepointWidth::Narrow; - _map[UnicodeRange(2096, 2110)] = CodepointWidth::Narrow; - _map[UnicodeRange(2112, 2139)] = CodepointWidth::Narrow; - _map[UnicodeRange(2142, 2142)] = CodepointWidth::Narrow; - _map[UnicodeRange(2144, 2154)] = CodepointWidth::Narrow; - _map[UnicodeRange(2208, 2228)] = CodepointWidth::Narrow; - _map[UnicodeRange(2230, 2237)] = CodepointWidth::Narrow; - _map[UnicodeRange(2260, 2435)] = CodepointWidth::Narrow; - _map[UnicodeRange(2437, 2444)] = CodepointWidth::Narrow; - _map[UnicodeRange(2447, 2448)] = CodepointWidth::Narrow; - _map[UnicodeRange(2451, 2472)] = CodepointWidth::Narrow; - _map[UnicodeRange(2474, 2480)] = CodepointWidth::Narrow; - _map[UnicodeRange(2482, 2482)] = CodepointWidth::Narrow; - _map[UnicodeRange(2486, 2489)] = CodepointWidth::Narrow; - _map[UnicodeRange(2492, 2500)] = CodepointWidth::Narrow; - _map[UnicodeRange(2503, 2504)] = CodepointWidth::Narrow; - _map[UnicodeRange(2507, 2510)] = CodepointWidth::Narrow; - _map[UnicodeRange(2519, 2519)] = CodepointWidth::Narrow; - _map[UnicodeRange(2524, 2525)] = CodepointWidth::Narrow; - _map[UnicodeRange(2527, 2531)] = CodepointWidth::Narrow; - _map[UnicodeRange(2534, 2557)] = CodepointWidth::Narrow; - _map[UnicodeRange(2561, 2563)] = CodepointWidth::Narrow; - _map[UnicodeRange(2565, 2570)] = CodepointWidth::Narrow; - _map[UnicodeRange(2575, 2576)] = CodepointWidth::Narrow; - _map[UnicodeRange(2579, 2600)] = CodepointWidth::Narrow; - _map[UnicodeRange(2602, 2608)] = CodepointWidth::Narrow; - _map[UnicodeRange(2610, 2611)] = CodepointWidth::Narrow; - _map[UnicodeRange(2613, 2614)] = CodepointWidth::Narrow; - _map[UnicodeRange(2616, 2617)] = CodepointWidth::Narrow; - _map[UnicodeRange(2620, 2620)] = CodepointWidth::Narrow; - _map[UnicodeRange(2622, 2626)] = CodepointWidth::Narrow; - _map[UnicodeRange(2631, 2632)] = CodepointWidth::Narrow; - _map[UnicodeRange(2635, 2637)] = CodepointWidth::Narrow; - _map[UnicodeRange(2641, 2641)] = CodepointWidth::Narrow; - _map[UnicodeRange(2649, 2652)] = CodepointWidth::Narrow; - _map[UnicodeRange(2654, 2654)] = CodepointWidth::Narrow; - _map[UnicodeRange(2662, 2677)] = CodepointWidth::Narrow; - _map[UnicodeRange(2689, 2691)] = CodepointWidth::Narrow; - _map[UnicodeRange(2693, 2701)] = CodepointWidth::Narrow; - _map[UnicodeRange(2703, 2705)] = CodepointWidth::Narrow; - _map[UnicodeRange(2707, 2728)] = CodepointWidth::Narrow; - _map[UnicodeRange(2730, 2736)] = CodepointWidth::Narrow; - _map[UnicodeRange(2738, 2739)] = CodepointWidth::Narrow; - _map[UnicodeRange(2741, 2745)] = CodepointWidth::Narrow; - _map[UnicodeRange(2748, 2757)] = CodepointWidth::Narrow; - _map[UnicodeRange(2759, 2761)] = CodepointWidth::Narrow; - _map[UnicodeRange(2763, 2765)] = CodepointWidth::Narrow; - _map[UnicodeRange(2768, 2768)] = CodepointWidth::Narrow; - _map[UnicodeRange(2784, 2787)] = CodepointWidth::Narrow; - _map[UnicodeRange(2790, 2801)] = CodepointWidth::Narrow; - _map[UnicodeRange(2809, 2815)] = CodepointWidth::Narrow; - _map[UnicodeRange(2817, 2819)] = CodepointWidth::Narrow; - _map[UnicodeRange(2821, 2828)] = CodepointWidth::Narrow; - _map[UnicodeRange(2831, 2832)] = CodepointWidth::Narrow; - _map[UnicodeRange(2835, 2856)] = CodepointWidth::Narrow; - _map[UnicodeRange(2858, 2864)] = CodepointWidth::Narrow; - _map[UnicodeRange(2866, 2867)] = CodepointWidth::Narrow; - _map[UnicodeRange(2869, 2873)] = CodepointWidth::Narrow; - _map[UnicodeRange(2876, 2884)] = CodepointWidth::Narrow; - _map[UnicodeRange(2887, 2888)] = CodepointWidth::Narrow; - _map[UnicodeRange(2891, 2893)] = CodepointWidth::Narrow; - _map[UnicodeRange(2902, 2903)] = CodepointWidth::Narrow; - _map[UnicodeRange(2908, 2909)] = CodepointWidth::Narrow; - _map[UnicodeRange(2911, 2915)] = CodepointWidth::Narrow; - _map[UnicodeRange(2918, 2935)] = CodepointWidth::Narrow; - _map[UnicodeRange(2946, 2947)] = CodepointWidth::Narrow; - _map[UnicodeRange(2949, 2954)] = CodepointWidth::Narrow; - _map[UnicodeRange(2958, 2960)] = CodepointWidth::Narrow; - _map[UnicodeRange(2962, 2965)] = CodepointWidth::Narrow; - _map[UnicodeRange(2969, 2970)] = CodepointWidth::Narrow; - _map[UnicodeRange(2972, 2972)] = CodepointWidth::Narrow; - _map[UnicodeRange(2974, 2975)] = CodepointWidth::Narrow; - _map[UnicodeRange(2979, 2980)] = CodepointWidth::Narrow; - _map[UnicodeRange(2984, 2986)] = CodepointWidth::Narrow; - _map[UnicodeRange(2990, 3001)] = CodepointWidth::Narrow; - _map[UnicodeRange(3006, 3010)] = CodepointWidth::Narrow; - _map[UnicodeRange(3014, 3016)] = CodepointWidth::Narrow; - _map[UnicodeRange(3018, 3021)] = CodepointWidth::Narrow; - _map[UnicodeRange(3024, 3024)] = CodepointWidth::Narrow; - _map[UnicodeRange(3031, 3031)] = CodepointWidth::Narrow; - _map[UnicodeRange(3046, 3066)] = CodepointWidth::Narrow; - _map[UnicodeRange(3072, 3075)] = CodepointWidth::Narrow; - _map[UnicodeRange(3077, 3084)] = CodepointWidth::Narrow; - _map[UnicodeRange(3086, 3088)] = CodepointWidth::Narrow; - _map[UnicodeRange(3090, 3112)] = CodepointWidth::Narrow; - _map[UnicodeRange(3114, 3129)] = CodepointWidth::Narrow; - _map[UnicodeRange(3133, 3140)] = CodepointWidth::Narrow; - _map[UnicodeRange(3142, 3144)] = CodepointWidth::Narrow; - _map[UnicodeRange(3146, 3149)] = CodepointWidth::Narrow; - _map[UnicodeRange(3157, 3158)] = CodepointWidth::Narrow; - _map[UnicodeRange(3160, 3162)] = CodepointWidth::Narrow; - _map[UnicodeRange(3168, 3171)] = CodepointWidth::Narrow; - _map[UnicodeRange(3174, 3183)] = CodepointWidth::Narrow; - _map[UnicodeRange(3192, 3203)] = CodepointWidth::Narrow; - _map[UnicodeRange(3205, 3212)] = CodepointWidth::Narrow; - _map[UnicodeRange(3214, 3216)] = CodepointWidth::Narrow; - _map[UnicodeRange(3218, 3240)] = CodepointWidth::Narrow; - _map[UnicodeRange(3242, 3251)] = CodepointWidth::Narrow; - _map[UnicodeRange(3253, 3257)] = CodepointWidth::Narrow; - _map[UnicodeRange(3260, 3268)] = CodepointWidth::Narrow; - _map[UnicodeRange(3270, 3272)] = CodepointWidth::Narrow; - _map[UnicodeRange(3274, 3277)] = CodepointWidth::Narrow; - _map[UnicodeRange(3285, 3286)] = CodepointWidth::Narrow; - _map[UnicodeRange(3294, 3294)] = CodepointWidth::Narrow; - _map[UnicodeRange(3296, 3299)] = CodepointWidth::Narrow; - _map[UnicodeRange(3302, 3311)] = CodepointWidth::Narrow; - _map[UnicodeRange(3313, 3314)] = CodepointWidth::Narrow; - _map[UnicodeRange(3328, 3331)] = CodepointWidth::Narrow; - _map[UnicodeRange(3333, 3340)] = CodepointWidth::Narrow; - _map[UnicodeRange(3342, 3344)] = CodepointWidth::Narrow; - _map[UnicodeRange(3346, 3396)] = CodepointWidth::Narrow; - _map[UnicodeRange(3398, 3400)] = CodepointWidth::Narrow; - _map[UnicodeRange(3402, 3407)] = CodepointWidth::Narrow; - _map[UnicodeRange(3412, 3427)] = CodepointWidth::Narrow; - _map[UnicodeRange(3430, 3455)] = CodepointWidth::Narrow; - _map[UnicodeRange(3458, 3459)] = CodepointWidth::Narrow; - _map[UnicodeRange(3461, 3478)] = CodepointWidth::Narrow; - _map[UnicodeRange(3482, 3505)] = CodepointWidth::Narrow; - _map[UnicodeRange(3507, 3515)] = CodepointWidth::Narrow; - _map[UnicodeRange(3517, 3517)] = CodepointWidth::Narrow; - _map[UnicodeRange(3520, 3526)] = CodepointWidth::Narrow; - _map[UnicodeRange(3530, 3530)] = CodepointWidth::Narrow; - _map[UnicodeRange(3535, 3540)] = CodepointWidth::Narrow; - _map[UnicodeRange(3542, 3542)] = CodepointWidth::Narrow; - _map[UnicodeRange(3544, 3551)] = CodepointWidth::Narrow; - _map[UnicodeRange(3558, 3567)] = CodepointWidth::Narrow; - _map[UnicodeRange(3570, 3572)] = CodepointWidth::Narrow; - _map[UnicodeRange(3585, 3642)] = CodepointWidth::Narrow; - _map[UnicodeRange(3647, 3675)] = CodepointWidth::Narrow; - _map[UnicodeRange(3713, 3714)] = CodepointWidth::Narrow; - _map[UnicodeRange(3716, 3716)] = CodepointWidth::Narrow; - _map[UnicodeRange(3719, 3720)] = CodepointWidth::Narrow; - _map[UnicodeRange(3722, 3722)] = CodepointWidth::Narrow; - _map[UnicodeRange(3725, 3725)] = CodepointWidth::Narrow; - _map[UnicodeRange(3732, 3735)] = CodepointWidth::Narrow; - _map[UnicodeRange(3737, 3743)] = CodepointWidth::Narrow; - _map[UnicodeRange(3745, 3747)] = CodepointWidth::Narrow; - _map[UnicodeRange(3749, 3749)] = CodepointWidth::Narrow; - _map[UnicodeRange(3751, 3751)] = CodepointWidth::Narrow; - _map[UnicodeRange(3754, 3755)] = CodepointWidth::Narrow; - _map[UnicodeRange(3757, 3769)] = CodepointWidth::Narrow; - _map[UnicodeRange(3771, 3773)] = CodepointWidth::Narrow; - _map[UnicodeRange(3776, 3780)] = CodepointWidth::Narrow; - _map[UnicodeRange(3782, 3782)] = CodepointWidth::Narrow; - _map[UnicodeRange(3784, 3789)] = CodepointWidth::Narrow; - _map[UnicodeRange(3792, 3801)] = CodepointWidth::Narrow; - _map[UnicodeRange(3804, 3807)] = CodepointWidth::Narrow; - _map[UnicodeRange(3840, 3911)] = CodepointWidth::Narrow; - _map[UnicodeRange(3913, 3948)] = CodepointWidth::Narrow; - _map[UnicodeRange(3953, 3991)] = CodepointWidth::Narrow; - _map[UnicodeRange(3993, 4028)] = CodepointWidth::Narrow; - _map[UnicodeRange(4030, 4044)] = CodepointWidth::Narrow; - _map[UnicodeRange(4046, 4058)] = CodepointWidth::Narrow; - _map[UnicodeRange(4096, 4293)] = CodepointWidth::Narrow; - _map[UnicodeRange(4295, 4295)] = CodepointWidth::Narrow; - _map[UnicodeRange(4301, 4301)] = CodepointWidth::Narrow; - _map[UnicodeRange(4304, 4351)] = CodepointWidth::Narrow; - _map[UnicodeRange(4352, 4447)] = CodepointWidth::Wide; - _map[UnicodeRange(4448, 4680)] = CodepointWidth::Narrow; - _map[UnicodeRange(4682, 4685)] = CodepointWidth::Narrow; - _map[UnicodeRange(4688, 4694)] = CodepointWidth::Narrow; - _map[UnicodeRange(4696, 4696)] = CodepointWidth::Narrow; - _map[UnicodeRange(4698, 4701)] = CodepointWidth::Narrow; - _map[UnicodeRange(4704, 4744)] = CodepointWidth::Narrow; - _map[UnicodeRange(4746, 4749)] = CodepointWidth::Narrow; - _map[UnicodeRange(4752, 4784)] = CodepointWidth::Narrow; - _map[UnicodeRange(4786, 4789)] = CodepointWidth::Narrow; - _map[UnicodeRange(4792, 4798)] = CodepointWidth::Narrow; - _map[UnicodeRange(4800, 4800)] = CodepointWidth::Narrow; - _map[UnicodeRange(4802, 4805)] = CodepointWidth::Narrow; - _map[UnicodeRange(4808, 4822)] = CodepointWidth::Narrow; - _map[UnicodeRange(4824, 4880)] = CodepointWidth::Narrow; - _map[UnicodeRange(4882, 4885)] = CodepointWidth::Narrow; - _map[UnicodeRange(4888, 4954)] = CodepointWidth::Narrow; - _map[UnicodeRange(4957, 4988)] = CodepointWidth::Narrow; - _map[UnicodeRange(4992, 5017)] = CodepointWidth::Narrow; - _map[UnicodeRange(5024, 5109)] = CodepointWidth::Narrow; - _map[UnicodeRange(5112, 5117)] = CodepointWidth::Narrow; - _map[UnicodeRange(5120, 5788)] = CodepointWidth::Narrow; - _map[UnicodeRange(5792, 5880)] = CodepointWidth::Narrow; - _map[UnicodeRange(5888, 5900)] = CodepointWidth::Narrow; - _map[UnicodeRange(5902, 5908)] = CodepointWidth::Narrow; - _map[UnicodeRange(5920, 5942)] = CodepointWidth::Narrow; - _map[UnicodeRange(5952, 5971)] = CodepointWidth::Narrow; - _map[UnicodeRange(5984, 5996)] = CodepointWidth::Narrow; - _map[UnicodeRange(5998, 6000)] = CodepointWidth::Narrow; - _map[UnicodeRange(6002, 6003)] = CodepointWidth::Narrow; - _map[UnicodeRange(6016, 6109)] = CodepointWidth::Narrow; - _map[UnicodeRange(6112, 6121)] = CodepointWidth::Narrow; - _map[UnicodeRange(6128, 6137)] = CodepointWidth::Narrow; - _map[UnicodeRange(6144, 6158)] = CodepointWidth::Narrow; - _map[UnicodeRange(6160, 6169)] = CodepointWidth::Narrow; - _map[UnicodeRange(6176, 6263)] = CodepointWidth::Narrow; - _map[UnicodeRange(6272, 6314)] = CodepointWidth::Narrow; - _map[UnicodeRange(6320, 6389)] = CodepointWidth::Narrow; - _map[UnicodeRange(6400, 6430)] = CodepointWidth::Narrow; - _map[UnicodeRange(6432, 6443)] = CodepointWidth::Narrow; - _map[UnicodeRange(6448, 6459)] = CodepointWidth::Narrow; - _map[UnicodeRange(6464, 6464)] = CodepointWidth::Narrow; - _map[UnicodeRange(6468, 6509)] = CodepointWidth::Narrow; - _map[UnicodeRange(6512, 6516)] = CodepointWidth::Narrow; - _map[UnicodeRange(6528, 6571)] = CodepointWidth::Narrow; - _map[UnicodeRange(6576, 6601)] = CodepointWidth::Narrow; - _map[UnicodeRange(6608, 6618)] = CodepointWidth::Narrow; - _map[UnicodeRange(6622, 6683)] = CodepointWidth::Narrow; - _map[UnicodeRange(6686, 6750)] = CodepointWidth::Narrow; - _map[UnicodeRange(6752, 6780)] = CodepointWidth::Narrow; - _map[UnicodeRange(6783, 6793)] = CodepointWidth::Narrow; - _map[UnicodeRange(6800, 6809)] = CodepointWidth::Narrow; - _map[UnicodeRange(6816, 6829)] = CodepointWidth::Narrow; - _map[UnicodeRange(6832, 6846)] = CodepointWidth::Narrow; - _map[UnicodeRange(6912, 6987)] = CodepointWidth::Narrow; - _map[UnicodeRange(6992, 7036)] = CodepointWidth::Narrow; - _map[UnicodeRange(7040, 7155)] = CodepointWidth::Narrow; - _map[UnicodeRange(7164, 7223)] = CodepointWidth::Narrow; - _map[UnicodeRange(7227, 7241)] = CodepointWidth::Narrow; - _map[UnicodeRange(7245, 7304)] = CodepointWidth::Narrow; - _map[UnicodeRange(7360, 7367)] = CodepointWidth::Narrow; - _map[UnicodeRange(7376, 7417)] = CodepointWidth::Narrow; - _map[UnicodeRange(7424, 7673)] = CodepointWidth::Narrow; - _map[UnicodeRange(7675, 7957)] = CodepointWidth::Narrow; - _map[UnicodeRange(7960, 7965)] = CodepointWidth::Narrow; - _map[UnicodeRange(7968, 8005)] = CodepointWidth::Narrow; - _map[UnicodeRange(8008, 8013)] = CodepointWidth::Narrow; - _map[UnicodeRange(8016, 8023)] = CodepointWidth::Narrow; - _map[UnicodeRange(8025, 8025)] = CodepointWidth::Narrow; - _map[UnicodeRange(8027, 8027)] = CodepointWidth::Narrow; - _map[UnicodeRange(8029, 8029)] = CodepointWidth::Narrow; - _map[UnicodeRange(8031, 8061)] = CodepointWidth::Narrow; - _map[UnicodeRange(8064, 8116)] = CodepointWidth::Narrow; - _map[UnicodeRange(8118, 8132)] = CodepointWidth::Narrow; - _map[UnicodeRange(8134, 8147)] = CodepointWidth::Narrow; - _map[UnicodeRange(8150, 8155)] = CodepointWidth::Narrow; - _map[UnicodeRange(8157, 8175)] = CodepointWidth::Narrow; - _map[UnicodeRange(8178, 8180)] = CodepointWidth::Narrow; - _map[UnicodeRange(8182, 8190)] = CodepointWidth::Narrow; - _map[UnicodeRange(8192, 8207)] = CodepointWidth::Narrow; - _map[UnicodeRange(8208, 8208)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8209, 8210)] = CodepointWidth::Narrow; - _map[UnicodeRange(8211, 8214)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8215, 8215)] = CodepointWidth::Narrow; - _map[UnicodeRange(8216, 8217)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8218, 8219)] = CodepointWidth::Narrow; - _map[UnicodeRange(8220, 8221)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8222, 8223)] = CodepointWidth::Narrow; - _map[UnicodeRange(8224, 8226)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8227, 8227)] = CodepointWidth::Narrow; - _map[UnicodeRange(8228, 8231)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8232, 8239)] = CodepointWidth::Narrow; - _map[UnicodeRange(8240, 8240)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8241, 8241)] = CodepointWidth::Narrow; - _map[UnicodeRange(8242, 8243)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8244, 8244)] = CodepointWidth::Narrow; - _map[UnicodeRange(8245, 8245)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8246, 8250)] = CodepointWidth::Narrow; - _map[UnicodeRange(8251, 8251)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8252, 8253)] = CodepointWidth::Narrow; - _map[UnicodeRange(8254, 8254)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8255, 8292)] = CodepointWidth::Narrow; - _map[UnicodeRange(8294, 8305)] = CodepointWidth::Narrow; - _map[UnicodeRange(8308, 8308)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8309, 8318)] = CodepointWidth::Narrow; - _map[UnicodeRange(8319, 8319)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8320, 8320)] = CodepointWidth::Narrow; - _map[UnicodeRange(8321, 8324)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8325, 8334)] = CodepointWidth::Narrow; - _map[UnicodeRange(8336, 8348)] = CodepointWidth::Narrow; - _map[UnicodeRange(8352, 8363)] = CodepointWidth::Narrow; - _map[UnicodeRange(8364, 8364)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8365, 8383)] = CodepointWidth::Narrow; - _map[UnicodeRange(8400, 8432)] = CodepointWidth::Narrow; - _map[UnicodeRange(8448, 8450)] = CodepointWidth::Narrow; - _map[UnicodeRange(8451, 8451)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8452, 8452)] = CodepointWidth::Narrow; - _map[UnicodeRange(8453, 8453)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8454, 8456)] = CodepointWidth::Narrow; - _map[UnicodeRange(8457, 8457)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8458, 8466)] = CodepointWidth::Narrow; - _map[UnicodeRange(8467, 8467)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8468, 8469)] = CodepointWidth::Narrow; - _map[UnicodeRange(8470, 8470)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8471, 8480)] = CodepointWidth::Narrow; - _map[UnicodeRange(8481, 8482)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8483, 8485)] = CodepointWidth::Narrow; - _map[UnicodeRange(8486, 8486)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8487, 8490)] = CodepointWidth::Narrow; - _map[UnicodeRange(8491, 8491)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8492, 8530)] = CodepointWidth::Narrow; - _map[UnicodeRange(8531, 8532)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8533, 8538)] = CodepointWidth::Narrow; - _map[UnicodeRange(8539, 8542)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8543, 8543)] = CodepointWidth::Narrow; - _map[UnicodeRange(8544, 8555)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8556, 8559)] = CodepointWidth::Narrow; - _map[UnicodeRange(8560, 8569)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8570, 8584)] = CodepointWidth::Narrow; - _map[UnicodeRange(8585, 8585)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8586, 8587)] = CodepointWidth::Narrow; - _map[UnicodeRange(8592, 8601)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8602, 8631)] = CodepointWidth::Narrow; - _map[UnicodeRange(8632, 8633)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8634, 8657)] = CodepointWidth::Narrow; - _map[UnicodeRange(8658, 8658)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8659, 8659)] = CodepointWidth::Narrow; - _map[UnicodeRange(8660, 8660)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8661, 8678)] = CodepointWidth::Narrow; - _map[UnicodeRange(8679, 8679)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8680, 8703)] = CodepointWidth::Narrow; - _map[UnicodeRange(8704, 8704)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8705, 8705)] = CodepointWidth::Narrow; - _map[UnicodeRange(8706, 8707)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8708, 8710)] = CodepointWidth::Narrow; - _map[UnicodeRange(8711, 8712)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8713, 8714)] = CodepointWidth::Narrow; - _map[UnicodeRange(8715, 8715)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8716, 8718)] = CodepointWidth::Narrow; - _map[UnicodeRange(8719, 8719)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8720, 8720)] = CodepointWidth::Narrow; - _map[UnicodeRange(8721, 8721)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8722, 8724)] = CodepointWidth::Narrow; - _map[UnicodeRange(8725, 8725)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8726, 8729)] = CodepointWidth::Narrow; - _map[UnicodeRange(8730, 8730)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8731, 8732)] = CodepointWidth::Narrow; - _map[UnicodeRange(8733, 8736)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8737, 8738)] = CodepointWidth::Narrow; - _map[UnicodeRange(8739, 8739)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8740, 8740)] = CodepointWidth::Narrow; - _map[UnicodeRange(8741, 8741)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8742, 8742)] = CodepointWidth::Narrow; - _map[UnicodeRange(8743, 8748)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8749, 8749)] = CodepointWidth::Narrow; - _map[UnicodeRange(8750, 8750)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8751, 8755)] = CodepointWidth::Narrow; - _map[UnicodeRange(8756, 8759)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8760, 8763)] = CodepointWidth::Narrow; - _map[UnicodeRange(8764, 8765)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8766, 8775)] = CodepointWidth::Narrow; - _map[UnicodeRange(8776, 8776)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8777, 8779)] = CodepointWidth::Narrow; - _map[UnicodeRange(8780, 8780)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8781, 8785)] = CodepointWidth::Narrow; - _map[UnicodeRange(8786, 8786)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8787, 8799)] = CodepointWidth::Narrow; - _map[UnicodeRange(8800, 8801)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8802, 8803)] = CodepointWidth::Narrow; - _map[UnicodeRange(8804, 8807)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8808, 8809)] = CodepointWidth::Narrow; - _map[UnicodeRange(8810, 8811)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8812, 8813)] = CodepointWidth::Narrow; - _map[UnicodeRange(8814, 8815)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8816, 8833)] = CodepointWidth::Narrow; - _map[UnicodeRange(8834, 8835)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8836, 8837)] = CodepointWidth::Narrow; - _map[UnicodeRange(8838, 8839)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8840, 8852)] = CodepointWidth::Narrow; - _map[UnicodeRange(8853, 8853)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8854, 8856)] = CodepointWidth::Narrow; - _map[UnicodeRange(8857, 8857)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8858, 8868)] = CodepointWidth::Narrow; - _map[UnicodeRange(8869, 8869)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8870, 8894)] = CodepointWidth::Narrow; - _map[UnicodeRange(8895, 8895)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8896, 8977)] = CodepointWidth::Narrow; - _map[UnicodeRange(8978, 8978)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(8979, 8985)] = CodepointWidth::Narrow; - _map[UnicodeRange(8986, 8987)] = CodepointWidth::Wide; - _map[UnicodeRange(8988, 9000)] = CodepointWidth::Narrow; - _map[UnicodeRange(9001, 9002)] = CodepointWidth::Wide; - _map[UnicodeRange(9003, 9192)] = CodepointWidth::Narrow; - _map[UnicodeRange(9193, 9196)] = CodepointWidth::Wide; - _map[UnicodeRange(9197, 9199)] = CodepointWidth::Narrow; - _map[UnicodeRange(9200, 9200)] = CodepointWidth::Wide; - _map[UnicodeRange(9201, 9202)] = CodepointWidth::Narrow; - _map[UnicodeRange(9203, 9203)] = CodepointWidth::Wide; - _map[UnicodeRange(9204, 9254)] = CodepointWidth::Narrow; - _map[UnicodeRange(9280, 9290)] = CodepointWidth::Narrow; - _map[UnicodeRange(9312, 9449)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9450, 9450)] = CodepointWidth::Narrow; - _map[UnicodeRange(9451, 9547)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9548, 9551)] = CodepointWidth::Narrow; - _map[UnicodeRange(9552, 9587)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9588, 9599)] = CodepointWidth::Narrow; - _map[UnicodeRange(9600, 9615)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9616, 9617)] = CodepointWidth::Narrow; - _map[UnicodeRange(9618, 9621)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9622, 9631)] = CodepointWidth::Narrow; - _map[UnicodeRange(9632, 9633)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9634, 9634)] = CodepointWidth::Narrow; - _map[UnicodeRange(9635, 9641)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9642, 9649)] = CodepointWidth::Narrow; - _map[UnicodeRange(9650, 9651)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9652, 9653)] = CodepointWidth::Narrow; - _map[UnicodeRange(9654, 9655)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9656, 9659)] = CodepointWidth::Narrow; - _map[UnicodeRange(9660, 9661)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9662, 9663)] = CodepointWidth::Narrow; - _map[UnicodeRange(9664, 9665)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9666, 9669)] = CodepointWidth::Narrow; - _map[UnicodeRange(9670, 9672)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9673, 9674)] = CodepointWidth::Narrow; - _map[UnicodeRange(9675, 9675)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9676, 9677)] = CodepointWidth::Narrow; - _map[UnicodeRange(9678, 9681)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9682, 9697)] = CodepointWidth::Narrow; - _map[UnicodeRange(9698, 9701)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9702, 9710)] = CodepointWidth::Narrow; - _map[UnicodeRange(9711, 9711)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9712, 9724)] = CodepointWidth::Narrow; - _map[UnicodeRange(9725, 9726)] = CodepointWidth::Wide; - _map[UnicodeRange(9727, 9732)] = CodepointWidth::Narrow; - _map[UnicodeRange(9733, 9734)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9735, 9736)] = CodepointWidth::Narrow; - _map[UnicodeRange(9737, 9737)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9738, 9741)] = CodepointWidth::Narrow; - _map[UnicodeRange(9742, 9743)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9744, 9747)] = CodepointWidth::Narrow; - _map[UnicodeRange(9748, 9749)] = CodepointWidth::Wide; - _map[UnicodeRange(9750, 9755)] = CodepointWidth::Narrow; - _map[UnicodeRange(9756, 9756)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9757, 9757)] = CodepointWidth::Narrow; - _map[UnicodeRange(9758, 9758)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9759, 9791)] = CodepointWidth::Narrow; - _map[UnicodeRange(9792, 9792)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9793, 9793)] = CodepointWidth::Narrow; - _map[UnicodeRange(9794, 9794)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9795, 9799)] = CodepointWidth::Narrow; - _map[UnicodeRange(9800, 9811)] = CodepointWidth::Wide; - _map[UnicodeRange(9812, 9823)] = CodepointWidth::Narrow; - _map[UnicodeRange(9824, 9825)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9826, 9826)] = CodepointWidth::Narrow; - _map[UnicodeRange(9827, 9829)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9830, 9830)] = CodepointWidth::Narrow; - _map[UnicodeRange(9831, 9834)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9835, 9835)] = CodepointWidth::Narrow; - _map[UnicodeRange(9836, 9837)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9838, 9838)] = CodepointWidth::Narrow; - _map[UnicodeRange(9839, 9839)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9840, 9854)] = CodepointWidth::Narrow; - _map[UnicodeRange(9855, 9855)] = CodepointWidth::Wide; - _map[UnicodeRange(9856, 9874)] = CodepointWidth::Narrow; - _map[UnicodeRange(9875, 9875)] = CodepointWidth::Wide; - _map[UnicodeRange(9876, 9885)] = CodepointWidth::Narrow; - _map[UnicodeRange(9886, 9887)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9888, 9888)] = CodepointWidth::Narrow; - _map[UnicodeRange(9889, 9889)] = CodepointWidth::Wide; - _map[UnicodeRange(9890, 9897)] = CodepointWidth::Narrow; - _map[UnicodeRange(9898, 9899)] = CodepointWidth::Wide; - _map[UnicodeRange(9900, 9916)] = CodepointWidth::Narrow; - _map[UnicodeRange(9917, 9918)] = CodepointWidth::Wide; - _map[UnicodeRange(9919, 9919)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9920, 9923)] = CodepointWidth::Narrow; - _map[UnicodeRange(9924, 9925)] = CodepointWidth::Wide; - _map[UnicodeRange(9926, 9933)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9934, 9934)] = CodepointWidth::Wide; - _map[UnicodeRange(9935, 9939)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9940, 9940)] = CodepointWidth::Wide; - _map[UnicodeRange(9941, 9953)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9954, 9954)] = CodepointWidth::Narrow; - _map[UnicodeRange(9955, 9955)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9956, 9959)] = CodepointWidth::Narrow; - _map[UnicodeRange(9960, 9961)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9962, 9962)] = CodepointWidth::Wide; - _map[UnicodeRange(9963, 9969)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9970, 9971)] = CodepointWidth::Wide; - _map[UnicodeRange(9972, 9972)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9973, 9973)] = CodepointWidth::Wide; - _map[UnicodeRange(9974, 9977)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9978, 9978)] = CodepointWidth::Wide; - _map[UnicodeRange(9979, 9980)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9981, 9981)] = CodepointWidth::Wide; - _map[UnicodeRange(9982, 9983)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(9984, 9988)] = CodepointWidth::Narrow; - _map[UnicodeRange(9989, 9989)] = CodepointWidth::Wide; - _map[UnicodeRange(9990, 9993)] = CodepointWidth::Narrow; - _map[UnicodeRange(9994, 9995)] = CodepointWidth::Wide; - _map[UnicodeRange(9996, 10023)] = CodepointWidth::Narrow; - _map[UnicodeRange(10024, 10024)] = CodepointWidth::Wide; - _map[UnicodeRange(10025, 10044)] = CodepointWidth::Narrow; - _map[UnicodeRange(10045, 10045)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(10046, 10059)] = CodepointWidth::Narrow; - _map[UnicodeRange(10060, 10060)] = CodepointWidth::Wide; - _map[UnicodeRange(10061, 10061)] = CodepointWidth::Narrow; - _map[UnicodeRange(10062, 10062)] = CodepointWidth::Wide; - _map[UnicodeRange(10063, 10066)] = CodepointWidth::Narrow; - _map[UnicodeRange(10067, 10069)] = CodepointWidth::Wide; - _map[UnicodeRange(10070, 10070)] = CodepointWidth::Narrow; - _map[UnicodeRange(10071, 10071)] = CodepointWidth::Wide; - _map[UnicodeRange(10072, 10101)] = CodepointWidth::Narrow; - _map[UnicodeRange(10102, 10111)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(10112, 10132)] = CodepointWidth::Narrow; - _map[UnicodeRange(10133, 10135)] = CodepointWidth::Wide; - _map[UnicodeRange(10136, 10159)] = CodepointWidth::Narrow; - _map[UnicodeRange(10160, 10160)] = CodepointWidth::Wide; - _map[UnicodeRange(10161, 10174)] = CodepointWidth::Narrow; - _map[UnicodeRange(10175, 10175)] = CodepointWidth::Wide; - _map[UnicodeRange(10176, 11034)] = CodepointWidth::Narrow; - _map[UnicodeRange(11035, 11036)] = CodepointWidth::Wide; - _map[UnicodeRange(11037, 11087)] = CodepointWidth::Narrow; - _map[UnicodeRange(11088, 11088)] = CodepointWidth::Wide; - _map[UnicodeRange(11089, 11092)] = CodepointWidth::Narrow; - _map[UnicodeRange(11093, 11093)] = CodepointWidth::Wide; - _map[UnicodeRange(11094, 11097)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(11098, 11123)] = CodepointWidth::Narrow; - _map[UnicodeRange(11126, 11157)] = CodepointWidth::Narrow; - _map[UnicodeRange(11160, 11193)] = CodepointWidth::Narrow; - _map[UnicodeRange(11197, 11208)] = CodepointWidth::Narrow; - _map[UnicodeRange(11210, 11218)] = CodepointWidth::Narrow; - _map[UnicodeRange(11244, 11247)] = CodepointWidth::Narrow; - _map[UnicodeRange(11264, 11310)] = CodepointWidth::Narrow; - _map[UnicodeRange(11312, 11358)] = CodepointWidth::Narrow; - _map[UnicodeRange(11360, 11507)] = CodepointWidth::Narrow; - _map[UnicodeRange(11513, 11557)] = CodepointWidth::Narrow; - _map[UnicodeRange(11559, 11559)] = CodepointWidth::Narrow; - _map[UnicodeRange(11565, 11565)] = CodepointWidth::Narrow; - _map[UnicodeRange(11568, 11623)] = CodepointWidth::Narrow; - _map[UnicodeRange(11631, 11632)] = CodepointWidth::Narrow; - _map[UnicodeRange(11647, 11670)] = CodepointWidth::Narrow; - _map[UnicodeRange(11680, 11686)] = CodepointWidth::Narrow; - _map[UnicodeRange(11688, 11694)] = CodepointWidth::Narrow; - _map[UnicodeRange(11696, 11702)] = CodepointWidth::Narrow; - _map[UnicodeRange(11704, 11710)] = CodepointWidth::Narrow; - _map[UnicodeRange(11712, 11718)] = CodepointWidth::Narrow; - _map[UnicodeRange(11720, 11726)] = CodepointWidth::Narrow; - _map[UnicodeRange(11728, 11734)] = CodepointWidth::Narrow; - _map[UnicodeRange(11736, 11742)] = CodepointWidth::Narrow; - _map[UnicodeRange(11744, 11849)] = CodepointWidth::Narrow; - _map[UnicodeRange(11904, 11929)] = CodepointWidth::Wide; - _map[UnicodeRange(11931, 12019)] = CodepointWidth::Wide; - _map[UnicodeRange(12032, 12245)] = CodepointWidth::Wide; - _map[UnicodeRange(12272, 12283)] = CodepointWidth::Wide; - _map[UnicodeRange(12288, 12350)] = CodepointWidth::Wide; - _map[UnicodeRange(12351, 12351)] = CodepointWidth::Narrow; - _map[UnicodeRange(12353, 12438)] = CodepointWidth::Wide; - _map[UnicodeRange(12441, 12543)] = CodepointWidth::Wide; - _map[UnicodeRange(12549, 12590)] = CodepointWidth::Wide; - _map[UnicodeRange(12593, 12686)] = CodepointWidth::Wide; - _map[UnicodeRange(12688, 12730)] = CodepointWidth::Wide; - _map[UnicodeRange(12736, 12771)] = CodepointWidth::Wide; - _map[UnicodeRange(12784, 12830)] = CodepointWidth::Wide; - _map[UnicodeRange(12832, 12871)] = CodepointWidth::Wide; - _map[UnicodeRange(12872, 12879)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(12880, 13054)] = CodepointWidth::Wide; - _map[UnicodeRange(13056, 19903)] = CodepointWidth::Wide; - _map[UnicodeRange(19904, 19967)] = CodepointWidth::Narrow; - _map[UnicodeRange(19968, 42124)] = CodepointWidth::Wide; - _map[UnicodeRange(42128, 42182)] = CodepointWidth::Wide; - _map[UnicodeRange(42192, 42539)] = CodepointWidth::Narrow; - _map[UnicodeRange(42560, 42743)] = CodepointWidth::Narrow; - _map[UnicodeRange(42752, 42926)] = CodepointWidth::Narrow; - _map[UnicodeRange(42928, 42935)] = CodepointWidth::Narrow; - _map[UnicodeRange(42999, 43051)] = CodepointWidth::Narrow; - _map[UnicodeRange(43056, 43065)] = CodepointWidth::Narrow; - _map[UnicodeRange(43072, 43127)] = CodepointWidth::Narrow; - _map[UnicodeRange(43136, 43205)] = CodepointWidth::Narrow; - _map[UnicodeRange(43214, 43225)] = CodepointWidth::Narrow; - _map[UnicodeRange(43232, 43261)] = CodepointWidth::Narrow; - _map[UnicodeRange(43264, 43347)] = CodepointWidth::Narrow; - _map[UnicodeRange(43359, 43359)] = CodepointWidth::Narrow; - _map[UnicodeRange(43360, 43388)] = CodepointWidth::Wide; - _map[UnicodeRange(43392, 43469)] = CodepointWidth::Narrow; - _map[UnicodeRange(43471, 43481)] = CodepointWidth::Narrow; - _map[UnicodeRange(43486, 43518)] = CodepointWidth::Narrow; - _map[UnicodeRange(43520, 43574)] = CodepointWidth::Narrow; - _map[UnicodeRange(43584, 43597)] = CodepointWidth::Narrow; - _map[UnicodeRange(43600, 43609)] = CodepointWidth::Narrow; - _map[UnicodeRange(43612, 43714)] = CodepointWidth::Narrow; - _map[UnicodeRange(43739, 43766)] = CodepointWidth::Narrow; - _map[UnicodeRange(43777, 43782)] = CodepointWidth::Narrow; - _map[UnicodeRange(43785, 43790)] = CodepointWidth::Narrow; - _map[UnicodeRange(43793, 43798)] = CodepointWidth::Narrow; - _map[UnicodeRange(43808, 43814)] = CodepointWidth::Narrow; - _map[UnicodeRange(43816, 43822)] = CodepointWidth::Narrow; - _map[UnicodeRange(43824, 43877)] = CodepointWidth::Narrow; - _map[UnicodeRange(43888, 44013)] = CodepointWidth::Narrow; - _map[UnicodeRange(44016, 44025)] = CodepointWidth::Narrow; - _map[UnicodeRange(44032, 55203)] = CodepointWidth::Wide; - _map[UnicodeRange(55216, 55238)] = CodepointWidth::Narrow; - _map[UnicodeRange(55243, 55291)] = CodepointWidth::Narrow; - _map[UnicodeRange(55296, 57343)] = CodepointWidth::Narrow; - _map[UnicodeRange(57344, 63743)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(63744, 64255)] = CodepointWidth::Wide; - _map[UnicodeRange(64256, 64262)] = CodepointWidth::Narrow; - _map[UnicodeRange(64275, 64279)] = CodepointWidth::Narrow; - _map[UnicodeRange(64285, 64310)] = CodepointWidth::Narrow; - _map[UnicodeRange(64312, 64316)] = CodepointWidth::Narrow; - _map[UnicodeRange(64318, 64318)] = CodepointWidth::Narrow; - _map[UnicodeRange(64320, 64321)] = CodepointWidth::Narrow; - _map[UnicodeRange(64323, 64324)] = CodepointWidth::Narrow; - _map[UnicodeRange(64326, 64449)] = CodepointWidth::Narrow; - _map[UnicodeRange(64467, 64831)] = CodepointWidth::Narrow; - _map[UnicodeRange(64848, 64911)] = CodepointWidth::Narrow; - _map[UnicodeRange(64914, 64967)] = CodepointWidth::Narrow; - _map[UnicodeRange(65008, 65021)] = CodepointWidth::Narrow; - _map[UnicodeRange(65024, 65039)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(65040, 65049)] = CodepointWidth::Wide; - _map[UnicodeRange(65056, 65071)] = CodepointWidth::Narrow; - _map[UnicodeRange(65072, 65106)] = CodepointWidth::Wide; - _map[UnicodeRange(65108, 65126)] = CodepointWidth::Wide; - _map[UnicodeRange(65128, 65131)] = CodepointWidth::Wide; - _map[UnicodeRange(65136, 65140)] = CodepointWidth::Narrow; - _map[UnicodeRange(65142, 65276)] = CodepointWidth::Narrow; - _map[UnicodeRange(65279, 65279)] = CodepointWidth::Narrow; - _map[UnicodeRange(65281, 65376)] = CodepointWidth::Wide; - _map[UnicodeRange(65377, 65470)] = CodepointWidth::Narrow; - _map[UnicodeRange(65474, 65479)] = CodepointWidth::Narrow; - _map[UnicodeRange(65482, 65487)] = CodepointWidth::Narrow; - _map[UnicodeRange(65490, 65495)] = CodepointWidth::Narrow; - _map[UnicodeRange(65498, 65500)] = CodepointWidth::Narrow; - _map[UnicodeRange(65504, 65510)] = CodepointWidth::Wide; - _map[UnicodeRange(65512, 65518)] = CodepointWidth::Narrow; - _map[UnicodeRange(65529, 65532)] = CodepointWidth::Narrow; - _map[UnicodeRange(65533, 65533)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(65536, 65547)] = CodepointWidth::Narrow; - _map[UnicodeRange(65549, 65574)] = CodepointWidth::Narrow; - _map[UnicodeRange(65576, 65594)] = CodepointWidth::Narrow; - _map[UnicodeRange(65596, 65597)] = CodepointWidth::Narrow; - _map[UnicodeRange(65599, 65613)] = CodepointWidth::Narrow; - _map[UnicodeRange(65616, 65629)] = CodepointWidth::Narrow; - _map[UnicodeRange(65664, 65786)] = CodepointWidth::Narrow; - _map[UnicodeRange(65792, 65794)] = CodepointWidth::Narrow; - _map[UnicodeRange(65799, 65843)] = CodepointWidth::Narrow; - _map[UnicodeRange(65847, 65934)] = CodepointWidth::Narrow; - _map[UnicodeRange(65936, 65947)] = CodepointWidth::Narrow; - _map[UnicodeRange(65952, 65952)] = CodepointWidth::Narrow; - _map[UnicodeRange(66000, 66045)] = CodepointWidth::Narrow; - _map[UnicodeRange(66176, 66204)] = CodepointWidth::Narrow; - _map[UnicodeRange(66208, 66256)] = CodepointWidth::Narrow; - _map[UnicodeRange(66272, 66299)] = CodepointWidth::Narrow; - _map[UnicodeRange(66304, 66339)] = CodepointWidth::Narrow; - _map[UnicodeRange(66349, 66378)] = CodepointWidth::Narrow; - _map[UnicodeRange(66384, 66426)] = CodepointWidth::Narrow; - _map[UnicodeRange(66432, 66461)] = CodepointWidth::Narrow; - _map[UnicodeRange(66463, 66499)] = CodepointWidth::Narrow; - _map[UnicodeRange(66504, 66517)] = CodepointWidth::Narrow; - _map[UnicodeRange(66560, 66717)] = CodepointWidth::Narrow; - _map[UnicodeRange(66720, 66729)] = CodepointWidth::Narrow; - _map[UnicodeRange(66736, 66771)] = CodepointWidth::Narrow; - _map[UnicodeRange(66776, 66811)] = CodepointWidth::Narrow; - _map[UnicodeRange(66816, 66855)] = CodepointWidth::Narrow; - _map[UnicodeRange(66864, 66915)] = CodepointWidth::Narrow; - _map[UnicodeRange(66927, 66927)] = CodepointWidth::Narrow; - _map[UnicodeRange(67072, 67382)] = CodepointWidth::Narrow; - _map[UnicodeRange(67392, 67413)] = CodepointWidth::Narrow; - _map[UnicodeRange(67424, 67431)] = CodepointWidth::Narrow; - _map[UnicodeRange(67584, 67589)] = CodepointWidth::Narrow; - _map[UnicodeRange(67592, 67592)] = CodepointWidth::Narrow; - _map[UnicodeRange(67594, 67637)] = CodepointWidth::Narrow; - _map[UnicodeRange(67639, 67640)] = CodepointWidth::Narrow; - _map[UnicodeRange(67644, 67644)] = CodepointWidth::Narrow; - _map[UnicodeRange(67647, 67669)] = CodepointWidth::Narrow; - _map[UnicodeRange(67671, 67742)] = CodepointWidth::Narrow; - _map[UnicodeRange(67751, 67759)] = CodepointWidth::Narrow; - _map[UnicodeRange(67808, 67826)] = CodepointWidth::Narrow; - _map[UnicodeRange(67828, 67829)] = CodepointWidth::Narrow; - _map[UnicodeRange(67835, 67867)] = CodepointWidth::Narrow; - _map[UnicodeRange(67871, 67897)] = CodepointWidth::Narrow; - _map[UnicodeRange(67903, 67903)] = CodepointWidth::Narrow; - _map[UnicodeRange(67968, 68023)] = CodepointWidth::Narrow; - _map[UnicodeRange(68028, 68047)] = CodepointWidth::Narrow; - _map[UnicodeRange(68050, 68099)] = CodepointWidth::Narrow; - _map[UnicodeRange(68101, 68102)] = CodepointWidth::Narrow; - _map[UnicodeRange(68108, 68115)] = CodepointWidth::Narrow; - _map[UnicodeRange(68117, 68119)] = CodepointWidth::Narrow; - _map[UnicodeRange(68121, 68147)] = CodepointWidth::Narrow; - _map[UnicodeRange(68152, 68154)] = CodepointWidth::Narrow; - _map[UnicodeRange(68159, 68167)] = CodepointWidth::Narrow; - _map[UnicodeRange(68176, 68184)] = CodepointWidth::Narrow; - _map[UnicodeRange(68192, 68255)] = CodepointWidth::Narrow; - _map[UnicodeRange(68288, 68326)] = CodepointWidth::Narrow; - _map[UnicodeRange(68331, 68342)] = CodepointWidth::Narrow; - _map[UnicodeRange(68352, 68405)] = CodepointWidth::Narrow; - _map[UnicodeRange(68409, 68437)] = CodepointWidth::Narrow; - _map[UnicodeRange(68440, 68466)] = CodepointWidth::Narrow; - _map[UnicodeRange(68472, 68497)] = CodepointWidth::Narrow; - _map[UnicodeRange(68505, 68508)] = CodepointWidth::Narrow; - _map[UnicodeRange(68521, 68527)] = CodepointWidth::Narrow; - _map[UnicodeRange(68608, 68680)] = CodepointWidth::Narrow; - _map[UnicodeRange(68736, 68786)] = CodepointWidth::Narrow; - _map[UnicodeRange(68800, 68850)] = CodepointWidth::Narrow; - _map[UnicodeRange(68858, 68863)] = CodepointWidth::Narrow; - _map[UnicodeRange(69216, 69246)] = CodepointWidth::Narrow; - _map[UnicodeRange(69632, 69709)] = CodepointWidth::Narrow; - _map[UnicodeRange(69714, 69743)] = CodepointWidth::Narrow; - _map[UnicodeRange(69759, 69825)] = CodepointWidth::Narrow; - _map[UnicodeRange(69840, 69864)] = CodepointWidth::Narrow; - _map[UnicodeRange(69872, 69881)] = CodepointWidth::Narrow; - _map[UnicodeRange(69888, 69940)] = CodepointWidth::Narrow; - _map[UnicodeRange(69942, 69955)] = CodepointWidth::Narrow; - _map[UnicodeRange(69968, 70006)] = CodepointWidth::Narrow; - _map[UnicodeRange(70016, 70093)] = CodepointWidth::Narrow; - _map[UnicodeRange(70096, 70111)] = CodepointWidth::Narrow; - _map[UnicodeRange(70113, 70132)] = CodepointWidth::Narrow; - _map[UnicodeRange(70144, 70161)] = CodepointWidth::Narrow; - _map[UnicodeRange(70163, 70206)] = CodepointWidth::Narrow; - _map[UnicodeRange(70272, 70278)] = CodepointWidth::Narrow; - _map[UnicodeRange(70280, 70280)] = CodepointWidth::Narrow; - _map[UnicodeRange(70282, 70285)] = CodepointWidth::Narrow; - _map[UnicodeRange(70287, 70301)] = CodepointWidth::Narrow; - _map[UnicodeRange(70303, 70313)] = CodepointWidth::Narrow; - _map[UnicodeRange(70320, 70378)] = CodepointWidth::Narrow; - _map[UnicodeRange(70384, 70393)] = CodepointWidth::Narrow; - _map[UnicodeRange(70400, 70403)] = CodepointWidth::Narrow; - _map[UnicodeRange(70405, 70412)] = CodepointWidth::Narrow; - _map[UnicodeRange(70415, 70416)] = CodepointWidth::Narrow; - _map[UnicodeRange(70419, 70440)] = CodepointWidth::Narrow; - _map[UnicodeRange(70442, 70448)] = CodepointWidth::Narrow; - _map[UnicodeRange(70450, 70451)] = CodepointWidth::Narrow; - _map[UnicodeRange(70453, 70457)] = CodepointWidth::Narrow; - _map[UnicodeRange(70460, 70468)] = CodepointWidth::Narrow; - _map[UnicodeRange(70471, 70472)] = CodepointWidth::Narrow; - _map[UnicodeRange(70475, 70477)] = CodepointWidth::Narrow; - _map[UnicodeRange(70480, 70480)] = CodepointWidth::Narrow; - _map[UnicodeRange(70487, 70487)] = CodepointWidth::Narrow; - _map[UnicodeRange(70493, 70499)] = CodepointWidth::Narrow; - _map[UnicodeRange(70502, 70508)] = CodepointWidth::Narrow; - _map[UnicodeRange(70512, 70516)] = CodepointWidth::Narrow; - _map[UnicodeRange(70656, 70745)] = CodepointWidth::Narrow; - _map[UnicodeRange(70747, 70747)] = CodepointWidth::Narrow; - _map[UnicodeRange(70749, 70749)] = CodepointWidth::Narrow; - _map[UnicodeRange(70784, 70855)] = CodepointWidth::Narrow; - _map[UnicodeRange(70864, 70873)] = CodepointWidth::Narrow; - _map[UnicodeRange(71040, 71093)] = CodepointWidth::Narrow; - _map[UnicodeRange(71096, 71133)] = CodepointWidth::Narrow; - _map[UnicodeRange(71168, 71236)] = CodepointWidth::Narrow; - _map[UnicodeRange(71248, 71257)] = CodepointWidth::Narrow; - _map[UnicodeRange(71264, 71276)] = CodepointWidth::Narrow; - _map[UnicodeRange(71296, 71351)] = CodepointWidth::Narrow; - _map[UnicodeRange(71360, 71369)] = CodepointWidth::Narrow; - _map[UnicodeRange(71424, 71449)] = CodepointWidth::Narrow; - _map[UnicodeRange(71453, 71467)] = CodepointWidth::Narrow; - _map[UnicodeRange(71472, 71487)] = CodepointWidth::Narrow; - _map[UnicodeRange(71840, 71922)] = CodepointWidth::Narrow; - _map[UnicodeRange(71935, 71935)] = CodepointWidth::Narrow; - _map[UnicodeRange(72192, 72263)] = CodepointWidth::Narrow; - _map[UnicodeRange(72272, 72323)] = CodepointWidth::Narrow; - _map[UnicodeRange(72326, 72348)] = CodepointWidth::Narrow; - _map[UnicodeRange(72350, 72354)] = CodepointWidth::Narrow; - _map[UnicodeRange(72384, 72440)] = CodepointWidth::Narrow; - _map[UnicodeRange(72704, 72712)] = CodepointWidth::Narrow; - _map[UnicodeRange(72714, 72758)] = CodepointWidth::Narrow; - _map[UnicodeRange(72760, 72773)] = CodepointWidth::Narrow; - _map[UnicodeRange(72784, 72812)] = CodepointWidth::Narrow; - _map[UnicodeRange(72816, 72847)] = CodepointWidth::Narrow; - _map[UnicodeRange(72850, 72871)] = CodepointWidth::Narrow; - _map[UnicodeRange(72873, 72886)] = CodepointWidth::Narrow; - _map[UnicodeRange(72960, 72966)] = CodepointWidth::Narrow; - _map[UnicodeRange(72968, 72969)] = CodepointWidth::Narrow; - _map[UnicodeRange(72971, 73014)] = CodepointWidth::Narrow; - _map[UnicodeRange(73018, 73018)] = CodepointWidth::Narrow; - _map[UnicodeRange(73020, 73021)] = CodepointWidth::Narrow; - _map[UnicodeRange(73023, 73031)] = CodepointWidth::Narrow; - _map[UnicodeRange(73040, 73049)] = CodepointWidth::Narrow; - _map[UnicodeRange(73728, 74649)] = CodepointWidth::Narrow; - _map[UnicodeRange(74752, 74862)] = CodepointWidth::Narrow; - _map[UnicodeRange(74864, 74868)] = CodepointWidth::Narrow; - _map[UnicodeRange(74880, 75075)] = CodepointWidth::Narrow; - _map[UnicodeRange(77824, 78894)] = CodepointWidth::Narrow; - _map[UnicodeRange(82944, 83526)] = CodepointWidth::Narrow; - _map[UnicodeRange(92160, 92728)] = CodepointWidth::Narrow; - _map[UnicodeRange(92736, 92766)] = CodepointWidth::Narrow; - _map[UnicodeRange(92768, 92777)] = CodepointWidth::Narrow; - _map[UnicodeRange(92782, 92783)] = CodepointWidth::Narrow; - _map[UnicodeRange(92880, 92909)] = CodepointWidth::Narrow; - _map[UnicodeRange(92912, 92917)] = CodepointWidth::Narrow; - _map[UnicodeRange(92928, 92997)] = CodepointWidth::Narrow; - _map[UnicodeRange(93008, 93017)] = CodepointWidth::Narrow; - _map[UnicodeRange(93019, 93025)] = CodepointWidth::Narrow; - _map[UnicodeRange(93027, 93047)] = CodepointWidth::Narrow; - _map[UnicodeRange(93053, 93071)] = CodepointWidth::Narrow; - _map[UnicodeRange(93952, 94020)] = CodepointWidth::Narrow; - _map[UnicodeRange(94032, 94078)] = CodepointWidth::Narrow; - _map[UnicodeRange(94095, 94111)] = CodepointWidth::Narrow; - _map[UnicodeRange(94176, 94177)] = CodepointWidth::Wide; - _map[UnicodeRange(94208, 100332)] = CodepointWidth::Wide; - _map[UnicodeRange(100352, 101106)] = CodepointWidth::Wide; - _map[UnicodeRange(110592, 110878)] = CodepointWidth::Wide; - _map[UnicodeRange(110960, 111355)] = CodepointWidth::Wide; - _map[UnicodeRange(113664, 113770)] = CodepointWidth::Narrow; - _map[UnicodeRange(113776, 113788)] = CodepointWidth::Narrow; - _map[UnicodeRange(113792, 113800)] = CodepointWidth::Narrow; - _map[UnicodeRange(113808, 113817)] = CodepointWidth::Narrow; - _map[UnicodeRange(113820, 113827)] = CodepointWidth::Narrow; - _map[UnicodeRange(118784, 119029)] = CodepointWidth::Narrow; - _map[UnicodeRange(119040, 119078)] = CodepointWidth::Narrow; - _map[UnicodeRange(119081, 119272)] = CodepointWidth::Narrow; - _map[UnicodeRange(119296, 119365)] = CodepointWidth::Narrow; - _map[UnicodeRange(119552, 119638)] = CodepointWidth::Narrow; - _map[UnicodeRange(119648, 119665)] = CodepointWidth::Narrow; - _map[UnicodeRange(119808, 119892)] = CodepointWidth::Narrow; - _map[UnicodeRange(119894, 119964)] = CodepointWidth::Narrow; - _map[UnicodeRange(119966, 119967)] = CodepointWidth::Narrow; - _map[UnicodeRange(119970, 119970)] = CodepointWidth::Narrow; - _map[UnicodeRange(119973, 119974)] = CodepointWidth::Narrow; - _map[UnicodeRange(119977, 119980)] = CodepointWidth::Narrow; - _map[UnicodeRange(119982, 119993)] = CodepointWidth::Narrow; - _map[UnicodeRange(119995, 119995)] = CodepointWidth::Narrow; - _map[UnicodeRange(119997, 120003)] = CodepointWidth::Narrow; - _map[UnicodeRange(120005, 120069)] = CodepointWidth::Narrow; - _map[UnicodeRange(120071, 120074)] = CodepointWidth::Narrow; - _map[UnicodeRange(120077, 120084)] = CodepointWidth::Narrow; - _map[UnicodeRange(120086, 120092)] = CodepointWidth::Narrow; - _map[UnicodeRange(120094, 120121)] = CodepointWidth::Narrow; - _map[UnicodeRange(120123, 120126)] = CodepointWidth::Narrow; - _map[UnicodeRange(120128, 120132)] = CodepointWidth::Narrow; - _map[UnicodeRange(120134, 120134)] = CodepointWidth::Narrow; - _map[UnicodeRange(120138, 120144)] = CodepointWidth::Narrow; - _map[UnicodeRange(120146, 120485)] = CodepointWidth::Narrow; - _map[UnicodeRange(120488, 120779)] = CodepointWidth::Narrow; - _map[UnicodeRange(120782, 121483)] = CodepointWidth::Narrow; - _map[UnicodeRange(121499, 121503)] = CodepointWidth::Narrow; - _map[UnicodeRange(121505, 121519)] = CodepointWidth::Narrow; - _map[UnicodeRange(122880, 122886)] = CodepointWidth::Narrow; - _map[UnicodeRange(122888, 122904)] = CodepointWidth::Narrow; - _map[UnicodeRange(122907, 122913)] = CodepointWidth::Narrow; - _map[UnicodeRange(122915, 122916)] = CodepointWidth::Narrow; - _map[UnicodeRange(122918, 122922)] = CodepointWidth::Narrow; - _map[UnicodeRange(124928, 125124)] = CodepointWidth::Narrow; - _map[UnicodeRange(125127, 125142)] = CodepointWidth::Narrow; - _map[UnicodeRange(125184, 125258)] = CodepointWidth::Narrow; - _map[UnicodeRange(125264, 125273)] = CodepointWidth::Narrow; - _map[UnicodeRange(125278, 125279)] = CodepointWidth::Narrow; - _map[UnicodeRange(126464, 126467)] = CodepointWidth::Narrow; - _map[UnicodeRange(126469, 126495)] = CodepointWidth::Narrow; - _map[UnicodeRange(126497, 126498)] = CodepointWidth::Narrow; - _map[UnicodeRange(126500, 126500)] = CodepointWidth::Narrow; - _map[UnicodeRange(126503, 126503)] = CodepointWidth::Narrow; - _map[UnicodeRange(126505, 126514)] = CodepointWidth::Narrow; - _map[UnicodeRange(126516, 126519)] = CodepointWidth::Narrow; - _map[UnicodeRange(126521, 126521)] = CodepointWidth::Narrow; - _map[UnicodeRange(126523, 126523)] = CodepointWidth::Narrow; - _map[UnicodeRange(126530, 126530)] = CodepointWidth::Narrow; - _map[UnicodeRange(126535, 126535)] = CodepointWidth::Narrow; - _map[UnicodeRange(126537, 126537)] = CodepointWidth::Narrow; - _map[UnicodeRange(126539, 126539)] = CodepointWidth::Narrow; - _map[UnicodeRange(126541, 126543)] = CodepointWidth::Narrow; - _map[UnicodeRange(126545, 126546)] = CodepointWidth::Narrow; - _map[UnicodeRange(126548, 126548)] = CodepointWidth::Narrow; - _map[UnicodeRange(126551, 126551)] = CodepointWidth::Narrow; - _map[UnicodeRange(126553, 126553)] = CodepointWidth::Narrow; - _map[UnicodeRange(126555, 126555)] = CodepointWidth::Narrow; - _map[UnicodeRange(126557, 126557)] = CodepointWidth::Narrow; - _map[UnicodeRange(126559, 126559)] = CodepointWidth::Narrow; - _map[UnicodeRange(126561, 126562)] = CodepointWidth::Narrow; - _map[UnicodeRange(126564, 126564)] = CodepointWidth::Narrow; - _map[UnicodeRange(126567, 126570)] = CodepointWidth::Narrow; - _map[UnicodeRange(126572, 126578)] = CodepointWidth::Narrow; - _map[UnicodeRange(126580, 126583)] = CodepointWidth::Narrow; - _map[UnicodeRange(126585, 126588)] = CodepointWidth::Narrow; - _map[UnicodeRange(126590, 126590)] = CodepointWidth::Narrow; - _map[UnicodeRange(126592, 126601)] = CodepointWidth::Narrow; - _map[UnicodeRange(126603, 126619)] = CodepointWidth::Narrow; - _map[UnicodeRange(126625, 126627)] = CodepointWidth::Narrow; - _map[UnicodeRange(126629, 126633)] = CodepointWidth::Narrow; - _map[UnicodeRange(126635, 126651)] = CodepointWidth::Narrow; - _map[UnicodeRange(126704, 126705)] = CodepointWidth::Narrow; - _map[UnicodeRange(126976, 126979)] = CodepointWidth::Narrow; - _map[UnicodeRange(126980, 126980)] = CodepointWidth::Wide; - _map[UnicodeRange(126981, 127019)] = CodepointWidth::Narrow; - _map[UnicodeRange(127024, 127123)] = CodepointWidth::Narrow; - _map[UnicodeRange(127136, 127150)] = CodepointWidth::Narrow; - _map[UnicodeRange(127153, 127167)] = CodepointWidth::Narrow; - _map[UnicodeRange(127169, 127182)] = CodepointWidth::Narrow; - _map[UnicodeRange(127183, 127183)] = CodepointWidth::Wide; - _map[UnicodeRange(127185, 127221)] = CodepointWidth::Narrow; - _map[UnicodeRange(127232, 127242)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(127243, 127244)] = CodepointWidth::Narrow; - _map[UnicodeRange(127248, 127277)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(127278, 127278)] = CodepointWidth::Narrow; - _map[UnicodeRange(127280, 127337)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(127338, 127339)] = CodepointWidth::Narrow; - _map[UnicodeRange(127344, 127373)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(127374, 127374)] = CodepointWidth::Wide; - _map[UnicodeRange(127375, 127376)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(127377, 127386)] = CodepointWidth::Wide; - _map[UnicodeRange(127387, 127404)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(127462, 127487)] = CodepointWidth::Narrow; - _map[UnicodeRange(127488, 127490)] = CodepointWidth::Wide; - _map[UnicodeRange(127504, 127547)] = CodepointWidth::Wide; - _map[UnicodeRange(127552, 127560)] = CodepointWidth::Wide; - _map[UnicodeRange(127568, 127569)] = CodepointWidth::Wide; - _map[UnicodeRange(127584, 127589)] = CodepointWidth::Wide; - _map[UnicodeRange(127744, 127776)] = CodepointWidth::Wide; - _map[UnicodeRange(127777, 127788)] = CodepointWidth::Narrow; - _map[UnicodeRange(127789, 127797)] = CodepointWidth::Wide; - _map[UnicodeRange(127798, 127798)] = CodepointWidth::Narrow; - _map[UnicodeRange(127799, 127868)] = CodepointWidth::Wide; - _map[UnicodeRange(127869, 127869)] = CodepointWidth::Narrow; - _map[UnicodeRange(127870, 127891)] = CodepointWidth::Wide; - _map[UnicodeRange(127892, 127903)] = CodepointWidth::Narrow; - _map[UnicodeRange(127904, 127946)] = CodepointWidth::Wide; - _map[UnicodeRange(127947, 127950)] = CodepointWidth::Narrow; - _map[UnicodeRange(127951, 127955)] = CodepointWidth::Wide; - _map[UnicodeRange(127956, 127967)] = CodepointWidth::Narrow; - _map[UnicodeRange(127968, 127984)] = CodepointWidth::Wide; - _map[UnicodeRange(127985, 127987)] = CodepointWidth::Narrow; - _map[UnicodeRange(127988, 127988)] = CodepointWidth::Wide; - _map[UnicodeRange(127989, 127991)] = CodepointWidth::Narrow; - _map[UnicodeRange(127992, 128062)] = CodepointWidth::Wide; - _map[UnicodeRange(128063, 128063)] = CodepointWidth::Narrow; - _map[UnicodeRange(128064, 128064)] = CodepointWidth::Wide; - _map[UnicodeRange(128065, 128065)] = CodepointWidth::Narrow; - _map[UnicodeRange(128066, 128252)] = CodepointWidth::Wide; - _map[UnicodeRange(128253, 128254)] = CodepointWidth::Narrow; - _map[UnicodeRange(128255, 128317)] = CodepointWidth::Wide; - _map[UnicodeRange(128318, 128330)] = CodepointWidth::Narrow; - _map[UnicodeRange(128331, 128334)] = CodepointWidth::Wide; - _map[UnicodeRange(128335, 128335)] = CodepointWidth::Narrow; - _map[UnicodeRange(128336, 128359)] = CodepointWidth::Wide; - _map[UnicodeRange(128360, 128377)] = CodepointWidth::Narrow; - _map[UnicodeRange(128378, 128378)] = CodepointWidth::Wide; - _map[UnicodeRange(128379, 128404)] = CodepointWidth::Narrow; - _map[UnicodeRange(128405, 128406)] = CodepointWidth::Wide; - _map[UnicodeRange(128407, 128419)] = CodepointWidth::Narrow; - _map[UnicodeRange(128420, 128420)] = CodepointWidth::Wide; - _map[UnicodeRange(128421, 128506)] = CodepointWidth::Narrow; - _map[UnicodeRange(128507, 128591)] = CodepointWidth::Wide; - _map[UnicodeRange(128592, 128639)] = CodepointWidth::Narrow; - _map[UnicodeRange(128640, 128709)] = CodepointWidth::Wide; - _map[UnicodeRange(128710, 128715)] = CodepointWidth::Narrow; - _map[UnicodeRange(128716, 128716)] = CodepointWidth::Wide; - _map[UnicodeRange(128717, 128719)] = CodepointWidth::Narrow; - _map[UnicodeRange(128720, 128722)] = CodepointWidth::Wide; - _map[UnicodeRange(128723, 128724)] = CodepointWidth::Narrow; - _map[UnicodeRange(128736, 128746)] = CodepointWidth::Narrow; - _map[UnicodeRange(128747, 128748)] = CodepointWidth::Wide; - _map[UnicodeRange(128752, 128755)] = CodepointWidth::Narrow; - _map[UnicodeRange(128756, 128760)] = CodepointWidth::Wide; - _map[UnicodeRange(128768, 128883)] = CodepointWidth::Narrow; - _map[UnicodeRange(128896, 128980)] = CodepointWidth::Narrow; - _map[UnicodeRange(129024, 129035)] = CodepointWidth::Narrow; - _map[UnicodeRange(129040, 129095)] = CodepointWidth::Narrow; - _map[UnicodeRange(129104, 129113)] = CodepointWidth::Narrow; - _map[UnicodeRange(129120, 129159)] = CodepointWidth::Narrow; - _map[UnicodeRange(129168, 129197)] = CodepointWidth::Narrow; - _map[UnicodeRange(129280, 129291)] = CodepointWidth::Narrow; - _map[UnicodeRange(129296, 129342)] = CodepointWidth::Wide; - _map[UnicodeRange(129344, 129356)] = CodepointWidth::Wide; - _map[UnicodeRange(129360, 129387)] = CodepointWidth::Wide; - _map[UnicodeRange(129408, 129431)] = CodepointWidth::Wide; - _map[UnicodeRange(129472, 129472)] = CodepointWidth::Wide; - _map[UnicodeRange(129488, 129510)] = CodepointWidth::Wide; - _map[UnicodeRange(131072, 196605)] = CodepointWidth::Wide; - _map[UnicodeRange(196608, 262141)] = CodepointWidth::Wide; - _map[UnicodeRange(917505, 917505)] = CodepointWidth::Narrow; - _map[UnicodeRange(917536, 917631)] = CodepointWidth::Narrow; - _map[UnicodeRange(917760, 917999)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(983040, 1048573)] = CodepointWidth::Ambiguous; - _map[UnicodeRange(1048576, 1114109)] = CodepointWidth::Ambiguous; -} diff --git a/src/types/GlyphWidth.cpp b/src/types/GlyphWidth.cpp index b92935c31b6..f90c79f9481 100644 --- a/src/types/GlyphWidth.cpp +++ b/src/types/GlyphWidth.cpp @@ -5,6 +5,8 @@ #include "inc/CodepointWidthDetector.hpp" #include "inc/GlyphWidth.hpp" +#pragma warning(suppress : 26426) +// TODO GH 2676 - remove warning suppression and decide what to do re: singleton instance of CodepointWidthDetector static CodepointWidthDetector widthDetector; // Function Description: @@ -18,7 +20,7 @@ bool IsGlyphFullWidth(const std::wstring_view glyph) // Function Description: // - determines if the glyph represented by the single character should be // wide or not. See CodepointWidthDetector::IsWide -bool IsGlyphFullWidth(const wchar_t wch) +bool IsGlyphFullWidth(const wchar_t wch) noexcept { return widthDetector.IsWide(wch); } @@ -44,7 +46,7 @@ void SetGlyphWidthFallback(std::function pfnFallb // - // Return Value: // - -void NotifyGlyphWidthFontChanged() +void NotifyGlyphWidthFontChanged() noexcept { widthDetector.NotifyFontChanged(); } diff --git a/src/types/IBaseData.h b/src/types/IBaseData.h new file mode 100644 index 00000000000..453dbae7711 --- /dev/null +++ b/src/types/IBaseData.h @@ -0,0 +1,48 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- IUiaData.hpp + +Abstract: +- This serves as the interface defining all information needed for the UI Automation Tree and the Renderer + +Author(s): +- Carlos Zamora (CaZamor) Aug-2019 +--*/ + +#pragma once + +#include "inc/viewport.hpp" + +class TextBuffer; + +namespace Microsoft::Console::Types +{ + class IBaseData + { + public: + virtual ~IBaseData() = 0; + + protected: + IBaseData() = default; + IBaseData(const IBaseData&) = default; + IBaseData(IBaseData&&) = default; + IBaseData& operator=(const IBaseData&) = default; + IBaseData& operator=(IBaseData&&) = default; + + public: + virtual Microsoft::Console::Types::Viewport GetViewport() noexcept = 0; + virtual const TextBuffer& GetTextBuffer() noexcept = 0; + virtual const FontInfo& GetFontInfo() noexcept = 0; + + virtual std::vector GetSelectionRects() noexcept = 0; + + virtual void LockConsole() noexcept = 0; + virtual void UnlockConsole() noexcept = 0; + }; + + // See docs/virtual-dtors.md for an explanation of why this is weird. + inline IBaseData::~IBaseData() {} +} diff --git a/src/interactivity/inc/IConsoleWindow.hpp b/src/types/IConsoleWindow.hpp similarity index 81% rename from src/interactivity/inc/IConsoleWindow.hpp rename to src/types/IConsoleWindow.hpp index d6585e020e1..632010edf08 100644 --- a/src/interactivity/inc/IConsoleWindow.hpp +++ b/src/types/IConsoleWindow.hpp @@ -15,12 +15,14 @@ Author(s): #pragma once +#include "IUiaWindow.h" + // copied typedef from uiautomationcore.h typedef int EVENTID; -namespace Microsoft::Console::Interactivity +namespace Microsoft::Console::Types { - class IConsoleWindow + class IConsoleWindow : public IUiaWindow { public: virtual ~IConsoleWindow() = 0; @@ -36,13 +38,9 @@ namespace Microsoft::Console::Interactivity virtual void SetIsFullscreen(const bool fFullscreenEnabled) = 0; - virtual void ChangeViewport(const SMALL_RECT NewWindow) = 0; - virtual void CaptureMouse() = 0; virtual BOOL ReleaseMouse() = 0; - virtual HWND GetWindowHandle() const = 0; - // Pass null. virtual void SetOwner() = 0; @@ -64,9 +62,6 @@ namespace Microsoft::Console::Interactivity const WORD wAbsoluteChange) = 0; virtual void VerticalScroll(const WORD wScrollCommand, const WORD wAbsoluteChange) = 0; - [[nodiscard]] virtual HRESULT SignalUia(_In_ EVENTID id) = 0; - [[nodiscard]] virtual HRESULT UiaSetTextAreaFocus() = 0; - virtual RECT GetWindowRect() const = 0; }; inline IConsoleWindow::~IConsoleWindow() {} diff --git a/src/types/IInputEvent.cpp b/src/types/IInputEvent.cpp index f7a3a925a2a..5df614a6ca5 100644 --- a/src/types/IInputEvent.cpp +++ b/src/types/IInputEvent.cpp @@ -48,7 +48,7 @@ std::deque> IInputEvent::Create(const std::deque> outEvents; for (size_t i = 0; i < records.size(); ++i) { - std::unique_ptr event = IInputEvent::Create(records[i]); + std::unique_ptr event = IInputEvent::Create(records.at(i)); outEvents.push_back(std::move(event)); } return outEvents; diff --git a/src/types/IUiaData.h b/src/types/IUiaData.h new file mode 100644 index 00000000000..6fbb2f513ef --- /dev/null +++ b/src/types/IUiaData.h @@ -0,0 +1,43 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- IUiaData.hpp + +Abstract: +- This serves as the interface defining all information needed for the UI Automation Tree + +Author(s): +- Carlos Zamora (CaZamor) Aug-2019 +--*/ + +#pragma once + +#include "IBaseData.h" + +struct ITextRangeProvider; + +namespace Microsoft::Console::Types +{ + class IUiaData : public IBaseData + { + public: + ~IUiaData() = 0; + + protected: + IUiaData() = default; + IUiaData(const IUiaData&) = default; + IUiaData(IUiaData&&) = default; + IUiaData& operator=(const IUiaData&) = default; + IUiaData& operator=(IUiaData&&) = default; + + public: + virtual const bool IsSelectionActive() const = 0; + virtual void ClearSelection() = 0; + virtual void SelectNewRegion(const COORD coordStart, const COORD coordEnd) = 0; + }; + + // See docs/virtual-dtors.md for an explanation of why this is weird. + inline IUiaData::~IUiaData() {} +} diff --git a/src/types/IUiaWindow.h b/src/types/IUiaWindow.h new file mode 100644 index 00000000000..ad1a0c1cd20 --- /dev/null +++ b/src/types/IUiaWindow.h @@ -0,0 +1,31 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- IUiaWindow.hpp + +Abstract: +- Defines the methods and properties of what makes a window generate a UIA Tree for accessibility + +Author(s): +- Carlos Zamora (CaZamor) July 2019 +--*/ + +#pragma once + +// copied typedef from uiautomationcore.h +typedef int EVENTID; + +namespace Microsoft::Console::Types +{ + class IUiaWindow + { + public: + virtual void ChangeViewport(const SMALL_RECT NewWindow) = 0; + virtual HWND GetWindowHandle() const = 0; + [[nodiscard]] virtual HRESULT SignalUia(_In_ EVENTID id) = 0; + [[nodiscard]] virtual HRESULT UiaSetTextAreaFocus() = 0; + virtual RECT GetWindowRect() const noexcept = 0; + }; +} diff --git a/src/types/KeyEvent.cpp b/src/types/KeyEvent.cpp index c62a35c908b..869d302d5a4 100644 --- a/src/types/KeyEvent.cpp +++ b/src/types/KeyEvent.cpp @@ -68,7 +68,7 @@ void KeyEvent::ActivateModifierKey(const ModifierKeyState modifierKey) noexcept WI_SetAllFlags(_activeModifierKeys, bitFlag); } -bool KeyEvent::DoActiveModifierKeysMatch(const std::unordered_set& consoleModifiers) const noexcept +bool KeyEvent::DoActiveModifierKeysMatch(const std::unordered_set& consoleModifiers) const { DWORD consoleBits = 0; for (const ModifierKeyState& mod : consoleModifiers) diff --git a/src/types/ScreenInfoUiaProviderBase.cpp b/src/types/ScreenInfoUiaProviderBase.cpp new file mode 100644 index 00000000000..27cf40c14e0 --- /dev/null +++ b/src/types/ScreenInfoUiaProviderBase.cpp @@ -0,0 +1,624 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" + +#include "ScreenInfoUiaProviderBase.h" +#include "WindowUiaProviderBase.hpp" + +using namespace Microsoft::Console::Types; +using namespace Microsoft::Console::Types::ScreenInfoUiaProviderTracing; + +// A helper function to create a SafeArray Version of an int array of a specified length +SAFEARRAY* BuildIntSafeArray(std::basic_string_view data) +{ + SAFEARRAY* psa = SafeArrayCreateVector(VT_I4, 0, gsl::narrow(data.size())); + if (psa != nullptr) + { + for (size_t i = 0; i < data.size(); i++) + { + LONG lIndex = 0; + if (FAILED(SizeTToLong(i, &lIndex) || + FAILED(SafeArrayPutElement(psa, &lIndex, (void*)&(data.at(i)))))) + { + SafeArrayDestroy(psa); + psa = nullptr; + break; + } + } + } + + return psa; +} + +ScreenInfoUiaProviderBase::ScreenInfoUiaProviderBase(_In_ IUiaData* pData) : + _signalFiringMapping{}, + _cRefs(1), + _pData(THROW_HR_IF_NULL(E_INVALIDARG, pData)) +{ + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(nullptr, ApiCall::Constructor, nullptr); +} + +[[nodiscard]] HRESULT ScreenInfoUiaProviderBase::Signal(_In_ EVENTID id) +{ + HRESULT hr = S_OK; + // check to see if we're already firing this particular event + if (_signalFiringMapping.find(id) != _signalFiringMapping.end() && + _signalFiringMapping[id] == true) + { + return hr; + } + + try + { + _signalFiringMapping[id] = true; + } + CATCH_RETURN(); + + IRawElementProviderSimple* pProvider = this; + hr = UiaRaiseAutomationEvent(pProvider, id); + _signalFiringMapping[id] = false; + + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + // tracing + /*ApiMsgSignal apiMsg; + apiMsg.Signal = id; + Tracing::s_TraceUia(this, ApiCall::Signal, &apiMsg);*/ + return hr; +} + +#pragma region IUnknown + +IFACEMETHODIMP_(ULONG) +ScreenInfoUiaProviderBase::AddRef() +{ + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::AddRef, nullptr); + return InterlockedIncrement(&_cRefs); +} + +IFACEMETHODIMP_(ULONG) +ScreenInfoUiaProviderBase::Release() +{ + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::Release, nullptr); + const long val = InterlockedDecrement(&_cRefs); + if (val == 0) + { + delete this; + } + return val; +} + +IFACEMETHODIMP ScreenInfoUiaProviderBase::QueryInterface(_In_ REFIID riid, + _COM_Outptr_result_maybenull_ void** ppInterface) +{ + RETURN_HR_IF_NULL(E_INVALIDARG, ppInterface); + + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::QueryInterface, nullptr); + if (riid == __uuidof(IUnknown) || + riid == __uuidof(IRawElementProviderSimple) || + riid == __uuidof(IRawElementProviderFragment) || + riid == __uuidof(ITextProvider)) + { + *ppInterface = this; + } + else + { + *ppInterface = nullptr; + return E_NOINTERFACE; + } + + AddRef(); + + return S_OK; +} + +#pragma endregion + +#pragma region IRawElementProviderSimple + +// Implementation of IRawElementProviderSimple::get_ProviderOptions. +// Gets UI Automation provider options. +IFACEMETHODIMP ScreenInfoUiaProviderBase::get_ProviderOptions(_Out_ ProviderOptions* pOptions) noexcept +{ + RETURN_HR_IF_NULL(E_INVALIDARG, pOptions); + + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::GetProviderOptions, nullptr); + *pOptions = ProviderOptions_ServerSideProvider; + return S_OK; +} + +// Implementation of IRawElementProviderSimple::get_PatternProvider. +// Gets the object that supports ISelectionPattern. +IFACEMETHODIMP ScreenInfoUiaProviderBase::GetPatternProvider(_In_ PATTERNID patternId, + _COM_Outptr_result_maybenull_ IUnknown** ppInterface) +{ + RETURN_HR_IF(E_INVALIDARG, ppInterface == nullptr); + *ppInterface = nullptr; + + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::GetPatternProvider, nullptr); + + *ppInterface = nullptr; + HRESULT hr = S_OK; + + if (patternId == UIA_TextPatternId) + { + hr = this->QueryInterface(IID_PPV_ARGS(ppInterface)); + if (FAILED(hr)) + { + *ppInterface = nullptr; + } + } + return hr; +} + +// Implementation of IRawElementProviderSimple::get_PropertyValue. +// Gets custom properties. +IFACEMETHODIMP ScreenInfoUiaProviderBase::GetPropertyValue(_In_ PROPERTYID propertyId, + _Out_ VARIANT* pVariant) noexcept +{ + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::GetPropertyValue, nullptr); + + pVariant->vt = VT_EMPTY; + + // Returning the default will leave the property as the default + // so we only really need to touch it for the properties we want to implement + if (propertyId == UIA_ControlTypePropertyId) + { + // This control is the Document control type, implying that it is + // a complex document that supports text pattern + pVariant->vt = VT_I4; + pVariant->lVal = UIA_DocumentControlTypeId; + } + else if (propertyId == UIA_NamePropertyId) + { + // TODO: MSFT: 7960168 - These strings should be localized text in the final UIA work + pVariant->bstrVal = SysAllocString(L"Text Area"); + if (pVariant->bstrVal != nullptr) + { + pVariant->vt = VT_BSTR; + } + } + else if (propertyId == UIA_AutomationIdPropertyId) + { + pVariant->bstrVal = SysAllocString(L"Text Area"); + if (pVariant->bstrVal != nullptr) + { + pVariant->vt = VT_BSTR; + } + } + else if (propertyId == UIA_IsControlElementPropertyId) + { + pVariant->vt = VT_BOOL; + pVariant->boolVal = VARIANT_TRUE; + } + else if (propertyId == UIA_IsContentElementPropertyId) + { + pVariant->vt = VT_BOOL; + pVariant->boolVal = VARIANT_TRUE; + } + else if (propertyId == UIA_IsKeyboardFocusablePropertyId) + { + pVariant->vt = VT_BOOL; + pVariant->boolVal = VARIANT_TRUE; + } + else if (propertyId == UIA_HasKeyboardFocusPropertyId) + { + pVariant->vt = VT_BOOL; + pVariant->boolVal = VARIANT_TRUE; + } + else if (propertyId == UIA_ProviderDescriptionPropertyId) + { + pVariant->bstrVal = SysAllocString(L"Microsoft Console Host: Screen Information Text Area"); + if (pVariant->bstrVal != nullptr) + { + pVariant->vt = VT_BSTR; + } + } + else if (propertyId == UIA_IsEnabledPropertyId) + { + pVariant->vt = VT_BOOL; + pVariant->boolVal = VARIANT_TRUE; + } + + return S_OK; +} + +IFACEMETHODIMP ScreenInfoUiaProviderBase::get_HostRawElementProvider(_COM_Outptr_result_maybenull_ IRawElementProviderSimple** ppProvider) noexcept +{ + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::GetHostRawElementProvider, nullptr); + RETURN_HR_IF(E_INVALIDARG, ppProvider == nullptr); + *ppProvider = nullptr; + + return S_OK; +} +#pragma endregion + +#pragma region IRawElementProviderFragment + +IFACEMETHODIMP ScreenInfoUiaProviderBase::GetRuntimeId(_Outptr_result_maybenull_ SAFEARRAY** ppRuntimeId) +{ + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::GetRuntimeId, nullptr); + + // Root defers this to host, others must implement it... + RETURN_HR_IF(E_INVALIDARG, ppRuntimeId == nullptr); + *ppRuntimeId = nullptr; + + // AppendRuntimeId is a magic Number that tells UIAutomation to Append its own Runtime ID(From the HWND) + const std::array rId{ UiaAppendRuntimeId, -1 }; + + const std::basic_string_view span{ rId.data(), rId.size() }; + // BuildIntSafeArray is a custom function to hide the SafeArray creation + *ppRuntimeId = BuildIntSafeArray(span); + RETURN_IF_NULL_ALLOC(*ppRuntimeId); + + return S_OK; +} + +IFACEMETHODIMP ScreenInfoUiaProviderBase::GetEmbeddedFragmentRoots(_Outptr_result_maybenull_ SAFEARRAY** ppRoots) noexcept +{ + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::GetEmbeddedFragmentRoots, nullptr); + + RETURN_HR_IF(E_INVALIDARG, ppRoots == nullptr); + *ppRoots = nullptr; + return S_OK; +} + +IFACEMETHODIMP ScreenInfoUiaProviderBase::SetFocus() +{ + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::SetFocus, nullptr); + + return Signal(UIA_AutomationFocusChangedEventId); +} + +#pragma endregion + +#pragma region ITextProvider + +IFACEMETHODIMP ScreenInfoUiaProviderBase::GetSelection(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) +{ + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //ApiMsgGetSelection apiMsg; + + _LockConsole(); + auto Unlock = wil::scope_exit([&]() noexcept { + _UnlockConsole(); + }); + + RETURN_HR_IF_NULL(E_INVALIDARG, ppRetVal); + *ppRetVal = nullptr; + HRESULT hr = S_OK; + + if (!_pData->IsSelectionActive()) + { + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //apiMsg.AreaSelected = false; + //apiMsg.SelectionRowCount = 1; + + // return a degenerate range at the cursor position + const Cursor& cursor = _getTextBuffer().GetCursor(); + + // make a safe array + *ppRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, 1); + if (*ppRetVal == nullptr) + { + return E_OUTOFMEMORY; + } + + IRawElementProviderSimple* pProvider; + hr = this->QueryInterface(IID_PPV_ARGS(&pProvider)); + if (pProvider == nullptr) + { + hr = E_POINTER; + } + + if (FAILED(hr)) + { + SafeArrayDestroy(*ppRetVal); + *ppRetVal = nullptr; + return hr; + } + + UiaTextRangeBase* range; + try + { + range = CreateTextRange(pProvider, + cursor); + } + catch (...) + { + range = nullptr; + hr = wil::ResultFromCaughtException(); + } + pProvider->Release(); + if (range == nullptr) + { + SafeArrayDestroy(*ppRetVal); + *ppRetVal = nullptr; + return hr; + } + + LONG currentIndex = 0; + hr = SafeArrayPutElement(*ppRetVal, ¤tIndex, range); + if (FAILED(hr)) + { + SafeArrayDestroy(*ppRetVal); + *ppRetVal = nullptr; + return hr; + } + } + else + { + // get the selection ranges + std::deque ranges; + IRawElementProviderSimple* pProvider; + RETURN_IF_FAILED(QueryInterface(IID_PPV_ARGS(&pProvider))); + RETURN_HR_IF_NULL(E_POINTER, pProvider); + try + { + ranges = GetSelectionRanges(pProvider); + } + catch (...) + { + hr = wil::ResultFromCaughtException(); + } + pProvider->Release(); + RETURN_IF_FAILED(hr); + + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //apiMsg.AreaSelected = true; + //apiMsg.SelectionRowCount = static_cast(ranges.size()); + + // make a safe array + *ppRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, gsl::narrow(ranges.size())); + if (*ppRetVal == nullptr) + { + return E_OUTOFMEMORY; + } + + // fill the safe array + for (LONG i = 0; i < gsl::narrow(ranges.size()); ++i) + { + hr = SafeArrayPutElement(*ppRetVal, &i, ranges.at(i)); + if (FAILED(hr)) + { + SafeArrayDestroy(*ppRetVal); + *ppRetVal = nullptr; + while (!ranges.empty()) + { + UiaTextRangeBase* pRange = ranges.at(0); + ranges.pop_front(); + if (pRange) + { + pRange->Release(); + } + } + return hr; + } + } + } + + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::GetSelection, &apiMsg); + return S_OK; +} + +IFACEMETHODIMP ScreenInfoUiaProviderBase::GetVisibleRanges(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) +{ + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::GetVisibleRanges, nullptr); + + _LockConsole(); + auto Unlock = wil::scope_exit([&]() noexcept { + _UnlockConsole(); + }); + + RETURN_HR_IF_NULL(E_INVALIDARG, ppRetVal); + *ppRetVal = nullptr; + + const auto viewport = _getViewport(); + const COORD screenBufferCoords = _getScreenBufferCoords(); + const int totalLines = screenBufferCoords.Y; + + // make a safe array + const size_t rowCount = viewport.Height(); + *ppRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, gsl::narrow(rowCount)); + if (*ppRetVal == nullptr) + { + return E_OUTOFMEMORY; + } + + // stuff each visible line in the safearray + for (size_t i = 0; i < rowCount; ++i) + { + const int lineNumber = (viewport.Top() + i) % totalLines; + const int start = lineNumber * screenBufferCoords.X; + // - 1 to get the last column in the row + const int end = start + screenBufferCoords.X - 1; + + IRawElementProviderSimple* pProvider; + HRESULT hr = this->QueryInterface(IID_PPV_ARGS(&pProvider)); + if (pProvider == nullptr) + { + hr = E_POINTER; + } + + if (FAILED(hr)) + { + SafeArrayDestroy(*ppRetVal); + *ppRetVal = nullptr; + return hr; + } + + UiaTextRangeBase* range; + try + { + range = CreateTextRange(pProvider, + start, + end, + false); + } + catch (...) + { + range = nullptr; + hr = wil::ResultFromCaughtException(); + } + pProvider->Release(); + + if (range == nullptr) + { + SafeArrayDestroy(*ppRetVal); + *ppRetVal = nullptr; + return hr; + } + + LONG currentIndex = gsl::narrow(i); + hr = SafeArrayPutElement(*ppRetVal, ¤tIndex, range); + if (FAILED(hr)) + { + SafeArrayDestroy(*ppRetVal); + *ppRetVal = nullptr; + return hr; + } + } + return S_OK; +} + +IFACEMETHODIMP ScreenInfoUiaProviderBase::RangeFromChild(_In_ IRawElementProviderSimple* /*childElement*/, + _COM_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) +{ + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::RangeFromChild, nullptr); + + RETURN_HR_IF_NULL(E_INVALIDARG, ppRetVal); + *ppRetVal = nullptr; + + IRawElementProviderSimple* pProvider; + RETURN_IF_FAILED(this->QueryInterface(IID_PPV_ARGS(&pProvider))); + RETURN_HR_IF_NULL(E_POINTER, pProvider); + + HRESULT hr = S_OK; + try + { + *ppRetVal = CreateTextRange(pProvider); + } + catch (...) + { + *ppRetVal = nullptr; + hr = wil::ResultFromCaughtException(); + } + pProvider->Release(); + + return hr; +} + +IFACEMETHODIMP ScreenInfoUiaProviderBase::RangeFromPoint(_In_ UiaPoint point, + _COM_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) +{ + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::RangeFromPoint, nullptr); + + RETURN_HR_IF_NULL(E_INVALIDARG, ppRetVal); + *ppRetVal = nullptr; + + IRawElementProviderSimple* pProvider; + RETURN_IF_FAILED(this->QueryInterface(IID_PPV_ARGS(&pProvider))); + RETURN_HR_IF_NULL(E_POINTER, pProvider); + + HRESULT hr = S_OK; + try + { + *ppRetVal = CreateTextRange(pProvider, + point); + } + catch (...) + { + *ppRetVal = nullptr; + hr = wil::ResultFromCaughtException(); + } + pProvider->Release(); + + return hr; +} + +IFACEMETHODIMP ScreenInfoUiaProviderBase::get_DocumentRange(_COM_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) +{ + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::GetDocumentRange, nullptr); + + RETURN_HR_IF_NULL(E_INVALIDARG, ppRetVal); + *ppRetVal = nullptr; + + IRawElementProviderSimple* pProvider; + RETURN_IF_FAILED(this->QueryInterface(IID_PPV_ARGS(&pProvider))); + RETURN_HR_IF_NULL(E_POINTER, pProvider); + + HRESULT hr = S_OK; + try + { + *ppRetVal = CreateTextRange(pProvider); + } + catch (...) + { + *ppRetVal = nullptr; + hr = wil::ResultFromCaughtException(); + } + pProvider->Release(); + + if (*ppRetVal) + { + (*ppRetVal)->ExpandToEnclosingUnit(TextUnit::TextUnit_Document); + } + + return hr; +} + +IFACEMETHODIMP ScreenInfoUiaProviderBase::get_SupportedTextSelection(_Out_ SupportedTextSelection* pRetVal) noexcept +{ + RETURN_HR_IF_NULL(E_INVALIDARG, pRetVal); + + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::GetSupportedTextSelection, nullptr); + + *pRetVal = SupportedTextSelection::SupportedTextSelection_Single; + return S_OK; +} + +#pragma endregion + +const COORD ScreenInfoUiaProviderBase::_getScreenBufferCoords() const +{ + return _getTextBuffer().GetSize().Dimensions(); +} + +const TextBuffer& ScreenInfoUiaProviderBase::_getTextBuffer() const noexcept +{ + return _pData->GetTextBuffer(); +} + +const Viewport ScreenInfoUiaProviderBase::_getViewport() const noexcept +{ + return _pData->GetViewport(); +} + +void ScreenInfoUiaProviderBase::_LockConsole() noexcept +{ + // TODO GitHub #2141: Lock and Unlock in conhost should decouple Ctrl+C dispatch and use smarter handling + _pData->LockConsole(); +} + +void ScreenInfoUiaProviderBase::_UnlockConsole() noexcept +{ + // TODO GitHub #2141: Lock and Unlock in conhost should decouple Ctrl+C dispatch and use smarter handling + _pData->UnlockConsole(); +} diff --git a/src/types/ScreenInfoUiaProviderBase.h b/src/types/ScreenInfoUiaProviderBase.h new file mode 100644 index 00000000000..5ffe24b5b01 --- /dev/null +++ b/src/types/ScreenInfoUiaProviderBase.h @@ -0,0 +1,178 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- ScreenInfoUiaProviderBase.hpp + +Abstract: +- This module provides UI Automation access to the screen buffer to + support both automation tests and accessibility (screen reading) + applications. +- ConHost and Windows Terminal must use IRenderData to have access to the proper information +- Based on examples, sample code, and guidance from + https://msdn.microsoft.com/en-us/library/windows/desktop/ee671596(v=vs.85).aspx + +Author(s): +- Michael Niksa (MiNiksa) 2017 +- Austin Diviness (AustDi) 2017 +- Carlos Zamora (CaZamor) 2019 +--*/ + +#pragma once + +#include "precomp.h" +#include "../buffer/out/textBuffer.hpp" +#include "UiaTextRangeBase.hpp" +#include "IUiaData.h" + +namespace Microsoft::Console::Types +{ + class WindowUiaProviderBase; + class Viewport; + + class ScreenInfoUiaProviderBase : + public IRawElementProviderSimple, + public IRawElementProviderFragment, + public ITextProvider + { + public: + ScreenInfoUiaProviderBase(_In_ IUiaData* pData); + ScreenInfoUiaProviderBase(const ScreenInfoUiaProviderBase&) = default; + ScreenInfoUiaProviderBase(ScreenInfoUiaProviderBase&&) = default; + ScreenInfoUiaProviderBase& operator=(const ScreenInfoUiaProviderBase&) = default; + ScreenInfoUiaProviderBase& operator=(ScreenInfoUiaProviderBase&&) = default; + virtual ~ScreenInfoUiaProviderBase() = default; + + [[nodiscard]] HRESULT Signal(_In_ EVENTID id); + + // IUnknown methods + IFACEMETHODIMP_(ULONG) + AddRef() override; + IFACEMETHODIMP_(ULONG) + Release() override; + IFACEMETHODIMP QueryInterface(_In_ REFIID riid, + _COM_Outptr_result_maybenull_ void** ppInterface) override; + + // IRawElementProviderSimple methods + IFACEMETHODIMP get_ProviderOptions(_Out_ ProviderOptions* pOptions) noexcept override; + IFACEMETHODIMP GetPatternProvider(_In_ PATTERNID iid, + _COM_Outptr_result_maybenull_ IUnknown** ppInterface) override; + IFACEMETHODIMP GetPropertyValue(_In_ PROPERTYID idProp, + _Out_ VARIANT* pVariant) noexcept override; + IFACEMETHODIMP get_HostRawElementProvider(_COM_Outptr_result_maybenull_ IRawElementProviderSimple** ppProvider) noexcept override; + + // IRawElementProviderFragment methods + virtual IFACEMETHODIMP Navigate(_In_ NavigateDirection direction, + _COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider) = 0; + IFACEMETHODIMP GetRuntimeId(_Outptr_result_maybenull_ SAFEARRAY** ppRuntimeId) override; + virtual IFACEMETHODIMP get_BoundingRectangle(_Out_ UiaRect* pRect) = 0; + IFACEMETHODIMP GetEmbeddedFragmentRoots(_Outptr_result_maybenull_ SAFEARRAY** ppRoots) noexcept override; + IFACEMETHODIMP SetFocus() override; + virtual IFACEMETHODIMP get_FragmentRoot(_COM_Outptr_result_maybenull_ IRawElementProviderFragmentRoot** ppProvider) = 0; + + // ITextProvider + IFACEMETHODIMP GetSelection(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) override; + IFACEMETHODIMP GetVisibleRanges(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) override; + IFACEMETHODIMP RangeFromChild(_In_ IRawElementProviderSimple* childElement, + _COM_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) override; + IFACEMETHODIMP RangeFromPoint(_In_ UiaPoint point, + _COM_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) override; + IFACEMETHODIMP get_DocumentRange(_COM_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) override; + IFACEMETHODIMP get_SupportedTextSelection(_Out_ SupportedTextSelection* pRetVal) noexcept override; + + protected: + virtual std::deque GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider) = 0; + + // degenerate range + virtual UiaTextRangeBase* CreateTextRange(_In_ IRawElementProviderSimple* const pProvider) = 0; + + // degenerate range at cursor position + virtual UiaTextRangeBase* CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, + const Cursor& cursor) = 0; + + // specific endpoint range + virtual UiaTextRangeBase* CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, + const Endpoint start, + const Endpoint end, + const bool degenerate) = 0; + + // range from a UiaPoint + virtual UiaTextRangeBase* CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, + const UiaPoint point) = 0; + + // weak reference to IRenderData + IUiaData* _pData; + + private: + // Ref counter for COM object + ULONG _cRefs; + + // this is used to prevent the object from + // signaling an event while it is already in the + // process of signalling another event. + // This fixes a problem with JAWS where it would + // call a public method that calls + // UiaRaiseAutomationEvent to signal something + // happened, which JAWS then detects the signal + // and calls the same method in response, + // eventually overflowing the stack. + // We aren't using this as a cheap locking + // mechanism for multi-threaded code. + std::map _signalFiringMapping; + + const COORD _getScreenBufferCoords() const; + const TextBuffer& _getTextBuffer() const noexcept; + const Viewport _getViewport() const noexcept; + void _LockConsole() noexcept; + void _UnlockConsole() noexcept; + }; + + namespace ScreenInfoUiaProviderTracing + { + enum class ApiCall + { + Constructor, + Signal, + AddRef, + Release, + QueryInterface, + GetProviderOptions, + GetPatternProvider, + GetPropertyValue, + GetHostRawElementProvider, + Navigate, + GetRuntimeId, + GetBoundingRectangle, + GetEmbeddedFragmentRoots, + SetFocus, + GetFragmentRoot, + GetSelection, + GetVisibleRanges, + RangeFromChild, + RangeFromPoint, + GetDocumentRange, + GetSupportedTextSelection + }; + + struct IApiMsg + { + }; + + struct ApiMsgSignal : public IApiMsg + { + EVENTID Signal; + }; + + struct ApiMsgNavigate : public IApiMsg + { + NavigateDirection Direction; + }; + + struct ApiMsgGetSelection : public IApiMsg + { + bool AreaSelected; + unsigned int SelectionRowCount; + }; + } +} diff --git a/src/types/UTF8OutPipeReader.cpp b/src/types/UTF8OutPipeReader.cpp index 5c646bc2166..1e69046c675 100644 --- a/src/types/UTF8OutPipeReader.cpp +++ b/src/types/UTF8OutPipeReader.cpp @@ -6,8 +6,10 @@ #include #include -UTF8OutPipeReader::UTF8OutPipeReader(HANDLE outPipe) : - _outPipe{ outPipe } +UTF8OutPipeReader::UTF8OutPipeReader(HANDLE outPipe) noexcept : + _outPipe{ outPipe }, + _buffer{ 0 }, + _utf8Partials{ 0 } { } @@ -30,24 +32,24 @@ UTF8OutPipeReader::UTF8OutPipeReader(HANDLE outPipe) : bool fSuccess{}; // in case of early escaping - *_buffer = 0; - strView = std::string_view{ reinterpret_cast(_buffer), 0 }; + _buffer.at(0) = 0; + strView = std::string_view{ _buffer.data(), 0 }; // copy UTF-8 code units that were remaining from the previously read chunk (if any) if (_dwPartialsLen != 0) { - std::move(_utf8Partials, _utf8Partials + _dwPartialsLen, _buffer); + std::move(_utf8Partials.cbegin(), _utf8Partials.cbegin() + _dwPartialsLen, _buffer.begin()); } // try to read data - fSuccess = !!ReadFile(_outPipe, &_buffer[_dwPartialsLen], std::extent::value - _dwPartialsLen, &dwRead, nullptr); + fSuccess = !!ReadFile(_outPipe, &_buffer.at(_dwPartialsLen), gsl::narrow(_buffer.size()) - _dwPartialsLen, &dwRead, nullptr); dwRead += _dwPartialsLen; _dwPartialsLen = 0; if (!fSuccess) // reading failed (we must check this first, because dwRead will also be 0.) { - auto lastError = GetLastError(); + const auto lastError = GetLastError(); if (lastError == ERROR_BROKEN_PIPE) { // This is a successful, but detectable, exit. @@ -65,13 +67,13 @@ UTF8OutPipeReader::UTF8OutPipeReader(HANDLE outPipe) : return S_OK; } - const BYTE* const endPtr{ _buffer + dwRead }; - const BYTE* backIter{ endPtr - 1 }; + const auto endPtr = _buffer.cbegin() + dwRead; + auto backIter = endPtr - 1; // If the last byte in the buffer was a byte belonging to a UTF-8 multi-byte character if ((*backIter & _Utf8BitMasks::MaskAsciiByte) > _Utf8BitMasks::IsAsciiByte) { // Check only up to 3 last bytes, if no Lead Byte was found then the byte before must be the Lead Byte and no partials are in the buffer - for (DWORD dwSequenceLen{ 1UL }, stop{ dwRead < 4UL ? dwRead : 4UL }; dwSequenceLen < stop; ++dwSequenceLen, --backIter) + for (DWORD dwSequenceLen{ 1UL }; dwSequenceLen < std::min(dwRead, 4UL); ++dwSequenceLen, --backIter) { // If Lead Byte found if ((*backIter & _Utf8BitMasks::MaskContinuationByte) > _Utf8BitMasks::IsContinuationByte) @@ -80,9 +82,9 @@ UTF8OutPipeReader::UTF8OutPipeReader(HANDLE outPipe) : // Use the bitmask at index `dwSequenceLen`. Compare the result with the operand having the same index. If they // are not equal then the sequence has to be cached because it is a partial code point. Otherwise the // sequence is a complete UTF-8 code point and the whole buffer is ready for the conversion to hstring. - if ((*backIter & _cmpMasks[dwSequenceLen]) != _cmpOperands[dwSequenceLen]) + if ((*backIter & _cmpMasks.at(dwSequenceLen)) != _cmpOperands.at(dwSequenceLen)) { - std::move(backIter, endPtr, _utf8Partials); + std::move(backIter, endPtr, _utf8Partials.begin()); dwRead -= dwSequenceLen; _dwPartialsLen = dwSequenceLen; } @@ -93,6 +95,6 @@ UTF8OutPipeReader::UTF8OutPipeReader(HANDLE outPipe) : } // give back a view of the part of the buffer that contains complete code points only - strView = std::string_view{ reinterpret_cast(_buffer), dwRead }; + strView = std::string_view{ &_buffer.at(0), dwRead }; return S_OK; } diff --git a/src/interactivity/win32/UiaTextRange.cpp b/src/types/UiaTextRangeBase.cpp similarity index 52% rename from src/interactivity/win32/UiaTextRange.cpp rename to src/types/UiaTextRangeBase.cpp index eedb8305c04..929a8c8e612 100644 --- a/src/interactivity/win32/UiaTextRange.cpp +++ b/src/types/UiaTextRangeBase.cpp @@ -2,59 +2,52 @@ // Licensed under the MIT license. #include "precomp.h" -#include "UiaTextRange.hpp" -#include "../inc/ServiceLocator.hpp" +#include "UiaTextRangeBase.hpp" +#include "ScreenInfoUiaProviderBase.h" -#include "window.hpp" -#include "windowdpiapi.hpp" -#include "../host/tracing.hpp" - -#include "../host/selection.hpp" -#include "../host/search.h" - -using namespace Microsoft::Console::Interactivity::Win32; -using namespace Microsoft::Console::Interactivity::Win32::UiaTextRangeTracing; -using namespace Microsoft::Console::Interactivity; +using namespace Microsoft::Console::Types; +using namespace Microsoft::Console::Types::UiaTextRangeBaseTracing; // toggle these for additional logging in a debug build //#define UIATEXTRANGE_DEBUG_MSGS 1 #undef UIATEXTRANGE_DEBUG_MSGS -IdType UiaTextRange::id = 1; +IdType UiaTextRangeBase::id = 1; -UiaTextRange::MoveState::MoveState(const UiaTextRange& range, - const MovementDirection direction) : - StartScreenInfoRow{ UiaTextRange::_endpointToScreenInfoRow(range.GetStart()) }, - StartColumn{ UiaTextRange::_endpointToColumn(range.GetStart()) }, - EndScreenInfoRow{ UiaTextRange::_endpointToScreenInfoRow(range.GetEnd()) }, - EndColumn{ UiaTextRange::_endpointToColumn(range.GetEnd()) }, +UiaTextRangeBase::MoveState::MoveState(IUiaData* pData, + const UiaTextRangeBase& range, + const MovementDirection direction) : + StartScreenInfoRow{ UiaTextRangeBase::_endpointToScreenInfoRow(pData, range.GetStart()) }, + StartColumn{ UiaTextRangeBase::_endpointToColumn(pData, range.GetStart()) }, + EndScreenInfoRow{ UiaTextRangeBase::_endpointToScreenInfoRow(pData, range.GetEnd()) }, + EndColumn{ UiaTextRangeBase::_endpointToColumn(pData, range.GetEnd()) }, Direction{ direction } { if (direction == MovementDirection::Forward) { - LimitingRow = UiaTextRange::_getLastScreenInfoRowIndex(); - FirstColumnInRow = UiaTextRange::_getFirstColumnIndex(); - LastColumnInRow = UiaTextRange::_getLastColumnIndex(); + LimitingRow = UiaTextRangeBase::_getLastScreenInfoRowIndex(pData); + FirstColumnInRow = UiaTextRangeBase::_getFirstColumnIndex(); + LastColumnInRow = UiaTextRangeBase::_getLastColumnIndex(pData); Increment = MovementIncrement::Forward; } else { - LimitingRow = UiaTextRange::_getFirstScreenInfoRowIndex(); - FirstColumnInRow = UiaTextRange::_getLastColumnIndex(); - LastColumnInRow = UiaTextRange::_getFirstColumnIndex(); + LimitingRow = UiaTextRangeBase::_getFirstScreenInfoRowIndex(); + FirstColumnInRow = UiaTextRangeBase::_getLastColumnIndex(pData); + LastColumnInRow = UiaTextRangeBase::_getFirstColumnIndex(); Increment = MovementIncrement::Backward; } } -UiaTextRange::MoveState::MoveState(const ScreenInfoRow startScreenInfoRow, - const Column startColumn, - const ScreenInfoRow endScreenInfoRow, - const Column endColumn, - const ScreenInfoRow limitingRow, - const Column firstColumnInRow, - const Column lastColumnInRow, - const MovementIncrement increment, - const MovementDirection direction) : +UiaTextRangeBase::MoveState::MoveState(const ScreenInfoRow startScreenInfoRow, + const Column startColumn, + const ScreenInfoRow endScreenInfoRow, + const Column endColumn, + const ScreenInfoRow limitingRow, + const Column firstColumnInRow, + const Column lastColumnInRow, + const MovementIncrement increment, + const MovementDirection direction) noexcept : StartScreenInfoRow{ startScreenInfoRow }, StartColumn{ startColumn }, EndScreenInfoRow{ endScreenInfoRow }, @@ -72,16 +65,16 @@ UiaTextRange::MoveState::MoveState(const ScreenInfoRow startScreenInfoRow, // This is a debugging function that prints out the current // relationship between screen info rows, text buffer rows, and // endpoints. -void UiaTextRange::_outputRowConversions() +void UiaTextRangeBase::_outputRowConversions(IUiaData* pData) { try { - unsigned int totalRows = _getTotalRows(); + unsigned int totalRows = _getTotalRows(pData); OutputDebugString(L"screenBuffer\ttextBuffer\tendpoint\n"); for (unsigned int i = 0; i < totalRows; ++i) { std::wstringstream ss; - ss << i << "\t" << _screenInfoRowToTextBufferRow(i) << "\t" << _screenInfoRowToEndpoint(i) << "\n"; + ss << i << "\t" << _screenInfoRowToTextBufferRow(pData, i) << "\t" << _screenInfoRowToEndpoint(pData, i) << "\n"; std::wstring str = ss.str(); OutputDebugString(str.c_str()); } @@ -93,7 +86,7 @@ void UiaTextRange::_outputRowConversions() } } -void UiaTextRange::_outputObjectState() +void UiaTextRangeBase::_outputObjectState() { std::wstringstream ss; ss << "Object State"; @@ -108,150 +101,32 @@ void UiaTextRange::_outputObjectState() } #endif // _DEBUG -std::deque UiaTextRange::GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider) -{ - std::deque ranges; - - // get the selection rects - const auto rectangles = Selection::Instance().GetSelectionRects(); - - // create a range for each row - for (const auto& rect : rectangles) - { - ScreenInfoRow currentRow = rect.Top; - Endpoint start = _screenInfoRowToEndpoint(currentRow) + rect.Left; - Endpoint end = _screenInfoRowToEndpoint(currentRow) + rect.Right; - UiaTextRange* range = UiaTextRange::Create(pProvider, - start, - end, - false); - if (range == nullptr) - { - // something when wrong, clean up and throw - while (!ranges.empty()) - { - UiaTextRange* temp = ranges[0]; - ranges.pop_front(); - temp->Release(); - } - throw E_INVALIDARG; - } - else - { - ranges.push_back(range); - } - } - return ranges; -} - -UiaTextRange* UiaTextRange::Create(_In_ IRawElementProviderSimple* const pProvider) -{ - UiaTextRange* range = nullptr; - ; - try - { - range = new UiaTextRange(pProvider); - } - catch (...) - { - range = nullptr; - } - - if (range) - { - pProvider->AddRef(); - } - return range; -} - -UiaTextRange* UiaTextRange::Create(_In_ IRawElementProviderSimple* const pProvider, - const Cursor& cursor) -{ - UiaTextRange* range = nullptr; - try - { - range = new UiaTextRange(pProvider, cursor); - } - catch (...) - { - range = nullptr; - } - - if (range) - { - pProvider->AddRef(); - } - return range; -} - -UiaTextRange* UiaTextRange::Create(_In_ IRawElementProviderSimple* const pProvider, - const Endpoint start, - const Endpoint end, - const bool degenerate) -{ - UiaTextRange* range = nullptr; - try - { - range = new UiaTextRange(pProvider, - start, - end, - degenerate); - } - catch (...) - { - range = nullptr; - } - - if (range) - { - pProvider->AddRef(); - } - return range; -} - -UiaTextRange* UiaTextRange::Create(_In_ IRawElementProviderSimple* const pProvider, - const UiaPoint point) -{ - UiaTextRange* range = nullptr; - try - { - range = new UiaTextRange(pProvider, point); - } - catch (...) - { - range = nullptr; - } - - if (range) - { - pProvider->AddRef(); - } - return range; -} - // degenerate range constructor. -UiaTextRange::UiaTextRange(_In_ IRawElementProviderSimple* const pProvider) : +UiaTextRangeBase::UiaTextRangeBase(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider) : _cRefs{ 1 }, _pProvider{ THROW_HR_IF_NULL(E_INVALIDARG, pProvider) }, _start{ 0 }, _end{ 0 }, - _degenerate{ true } + _degenerate{ true }, + _pData{ THROW_HR_IF_NULL(E_INVALIDARG, pData) } { _id = id; ++id; + // TODO GitHub #1914: Re-attach Tracing to UIA Tree // tracing - ApiMsgConstructor apiMsg; + /*ApiMsgConstructor apiMsg; apiMsg.Id = _id; - Tracing::s_TraceUia(nullptr, ApiCall::Constructor, &apiMsg); + Tracing::s_TraceUia(nullptr, ApiCall::Constructor, &apiMsg);*/ } -UiaTextRange::UiaTextRange(_In_ IRawElementProviderSimple* const pProvider, - const Cursor& cursor) : - UiaTextRange(pProvider) +UiaTextRangeBase::UiaTextRangeBase(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const Cursor& cursor) : + UiaTextRangeBase(pData, pProvider) { _degenerate = true; - _start = _screenInfoRowToEndpoint(cursor.GetPosition().Y) + cursor.GetPosition().X; + _start = _screenInfoRowToEndpoint(_pData, cursor.GetPosition().Y) + cursor.GetPosition().X; _end = _start; #if defined(_DEBUG) && defined(UIATEXTRANGE_DEBUG_MSGS) @@ -260,11 +135,12 @@ UiaTextRange::UiaTextRange(_In_ IRawElementProviderSimple* const pProvider, #endif } -UiaTextRange::UiaTextRange(_In_ IRawElementProviderSimple* const pProvider, - const Endpoint start, - const Endpoint end, - const bool degenerate) : - UiaTextRange(pProvider) +UiaTextRangeBase::UiaTextRangeBase(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const Endpoint start, + const Endpoint end, + const bool degenerate) : + UiaTextRangeBase(pData, pProvider) { THROW_HR_IF(E_INVALIDARG, !degenerate && start > end); @@ -278,20 +154,15 @@ UiaTextRange::UiaTextRange(_In_ IRawElementProviderSimple* const pProvider, #endif } -// returns a degenerate text range of the start of the row closest to the y value of point -UiaTextRange::UiaTextRange(_In_ IRawElementProviderSimple* const pProvider, - const UiaPoint point) : - UiaTextRange(pProvider) +void UiaTextRangeBase::Initialize(_In_ const UiaPoint point) { POINT clientPoint; clientPoint.x = static_cast(point.x); clientPoint.y = static_cast(point.y); // get row that point resides in - const IConsoleWindow* const pIConsoleWindow = _getIConsoleWindow(); - const Window* const pWindow = static_cast(pIConsoleWindow); - const RECT windowRect = pWindow->GetWindowRect(); - const SMALL_RECT viewport = _getViewport().ToInclusive(); - ScreenInfoRow row; + const RECT windowRect = _getTerminalRect(); + const SMALL_RECT viewport = _pData->GetViewport().ToInclusive(); + ScreenInfoRow row = 0; if (clientPoint.y <= windowRect.top) { row = viewport.Top; @@ -303,31 +174,24 @@ UiaTextRange::UiaTextRange(_In_ IRawElementProviderSimple* const pProvider, else { // change point coords to pixels relative to window - HWND hwnd = _getWindowHandle(); - ScreenToClient(hwnd, &clientPoint); + _TranslatePointFromScreen(&clientPoint); - const SCREEN_INFORMATION& _pScreenInfo = _getScreenInfo(); - const COORD currentFontSize = _pScreenInfo.GetScreenFontSize(); + const COORD currentFontSize = _getScreenFontSize(); row = (clientPoint.y / currentFontSize.Y) + viewport.Top; } - _start = _screenInfoRowToEndpoint(row); + _start = _screenInfoRowToEndpoint(_pData, row); _end = _start; _degenerate = true; - -#if defined(_DEBUG) && defined(UIATEXTRANGE_DEBUG_MSGS) - OutputDebugString(L"Constructor\n"); - _outputObjectState(); -#endif } -UiaTextRange::UiaTextRange(const UiaTextRange& a) : +UiaTextRangeBase::UiaTextRangeBase(const UiaTextRangeBase& a) noexcept : _cRefs{ 1 }, _pProvider{ a._pProvider }, _start{ a._start }, _end{ a._end }, - _degenerate{ a._degenerate } + _degenerate{ a._degenerate }, + _pData{ a._pData } { - (static_cast(_pProvider))->AddRef(); _id = id; ++id; @@ -337,22 +201,17 @@ UiaTextRange::UiaTextRange(const UiaTextRange& a) : #endif } -UiaTextRange::~UiaTextRange() -{ - (static_cast(_pProvider))->Release(); -} - -const IdType UiaTextRange::GetId() const +const IdType UiaTextRangeBase::GetId() const noexcept { return _id; } -const Endpoint UiaTextRange::GetStart() const +const Endpoint UiaTextRangeBase::GetStart() const noexcept { return _start; } -const Endpoint UiaTextRange::GetEnd() const +const Endpoint UiaTextRangeBase::GetEnd() const noexcept { return _end; } @@ -363,24 +222,33 @@ const Endpoint UiaTextRange::GetEnd() const // - // Return Value: // - true if range is degenerate, false otherwise. -const bool UiaTextRange::IsDegenerate() const +const bool UiaTextRangeBase::IsDegenerate() const noexcept { return _degenerate; } +void UiaTextRangeBase::SetRangeValues(const Endpoint start, const Endpoint end, const bool isDegenerate) noexcept +{ + _start = start; + _end = end; + _degenerate = isDegenerate; +} + #pragma region IUnknown IFACEMETHODIMP_(ULONG) -UiaTextRange::AddRef() +UiaTextRangeBase::AddRef() { - Tracing::s_TraceUia(this, ApiCall::AddRef, nullptr); + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::AddRef, nullptr); return InterlockedIncrement(&_cRefs); } IFACEMETHODIMP_(ULONG) -UiaTextRange::Release() +UiaTextRangeBase::Release() { - Tracing::s_TraceUia(this, ApiCall::Release, nullptr); + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::Release, nullptr); const long val = InterlockedDecrement(&_cRefs); if (val == 0) @@ -390,17 +258,18 @@ UiaTextRange::Release() return val; } -IFACEMETHODIMP UiaTextRange::QueryInterface(_In_ REFIID riid, _COM_Outptr_result_maybenull_ void** ppInterface) +IFACEMETHODIMP UiaTextRangeBase::QueryInterface(_In_ REFIID riid, _COM_Outptr_result_maybenull_ void** ppInterface) { - Tracing::s_TraceUia(this, ApiCall::QueryInterface, nullptr); + RETURN_HR_IF(E_INVALIDARG, ppInterface == nullptr); + *ppInterface = nullptr; - if (riid == __uuidof(IUnknown)) - { - *ppInterface = static_cast(this); - } - else if (riid == __uuidof(ITextRangeProvider)) + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::QueryInterface, nullptr); + + if (riid == __uuidof(IUnknown) || + riid == __uuidof(ITextRangeProvider)) { - *ppInterface = static_cast(this); + *ppInterface = this; } else { @@ -408,7 +277,7 @@ IFACEMETHODIMP UiaTextRange::QueryInterface(_In_ REFIID riid, _COM_Outptr_result return E_NOINTERFACE; } - (static_cast(*ppInterface))->AddRef(); + AddRef(); return S_OK; } @@ -416,116 +285,73 @@ IFACEMETHODIMP UiaTextRange::QueryInterface(_In_ REFIID riid, _COM_Outptr_result #pragma region ITextRangeProvider -IFACEMETHODIMP UiaTextRange::Clone(_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) +IFACEMETHODIMP UiaTextRangeBase::Compare(_In_opt_ ITextRangeProvider* pRange, _Out_ BOOL* pRetVal) noexcept { - try - { - *ppRetVal = new UiaTextRange(*this); - } - catch (...) - { - *ppRetVal = nullptr; - return wil::ResultFromCaughtException(); - } - if (*ppRetVal == nullptr) - { - return E_OUTOFMEMORY; - } - -#if defined(_DEBUG) && defined(UIATEXTRANGE_DEBUG_MSGS) - OutputDebugString(L"Clone\n"); - std::wstringstream ss; - ss << _id << L" cloned to " << (static_cast(*ppRetVal))->_id; - std::wstring str = ss.str(); - OutputDebugString(str.c_str()); - OutputDebugString(L"\n"); -#endif - // tracing - ApiMsgClone apiMsg; - apiMsg.CloneId = static_cast(*ppRetVal)->GetId(); - Tracing::s_TraceUia(this, ApiCall::Clone, &apiMsg); - - return S_OK; -} - -IFACEMETHODIMP UiaTextRange::Compare(_In_opt_ ITextRangeProvider* pRange, _Out_ BOOL* pRetVal) -{ - CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - gci.LockConsole(); - auto Unlock = wil::scope_exit([&] { - gci.UnlockConsole(); + _pData->LockConsole(); + auto Unlock = wil::scope_exit([&]() noexcept { + _pData->UnlockConsole(); }); + RETURN_HR_IF(E_INVALIDARG, pRetVal == nullptr); *pRetVal = FALSE; - UiaTextRange* other = static_cast(pRange); + const UiaTextRangeBase* other = static_cast(pRange); if (other) { *pRetVal = !!(_start == other->GetStart() && _end == other->GetEnd() && _degenerate == other->IsDegenerate()); } + // TODO GitHub #1914: Re-attach Tracing to UIA Tree // tracing - ApiMsgCompare apiMsg; + /*ApiMsgCompare apiMsg; apiMsg.OtherId = other == nullptr ? InvalidId : other->GetId(); apiMsg.Equal = !!*pRetVal; - Tracing::s_TraceUia(this, ApiCall::Compare, &apiMsg); + Tracing::s_TraceUia(this, ApiCall::Compare, &apiMsg);*/ return S_OK; } -IFACEMETHODIMP UiaTextRange::CompareEndpoints(_In_ TextPatternRangeEndpoint endpoint, - _In_ ITextRangeProvider* pTargetRange, - _In_ TextPatternRangeEndpoint targetEndpoint, - _Out_ int* pRetVal) +IFACEMETHODIMP UiaTextRangeBase::CompareEndpoints(_In_ TextPatternRangeEndpoint endpoint, + _In_ ITextRangeProvider* pTargetRange, + _In_ TextPatternRangeEndpoint targetEndpoint, + _Out_ int* pRetVal) noexcept { + RETURN_HR_IF(E_INVALIDARG, pRetVal == nullptr); + *pRetVal = 0; + // get the text range that we're comparing to - UiaTextRange* range = static_cast(pTargetRange); + const UiaTextRangeBase* range = static_cast(pTargetRange); if (range == nullptr) { return E_INVALIDARG; } // get endpoint value that we're comparing to - Endpoint theirValue; - if (targetEndpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) - { - theirValue = range->GetStart(); - } - else - { - theirValue = range->GetEnd() + 1; - } + const Endpoint theirValue = targetEndpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start ? range->GetStart() : range->GetEnd() + 1; // get the values of our endpoint - Endpoint ourValue; - if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) - { - ourValue = _start; - } - else - { - ourValue = _end + 1; - } + const Endpoint ourValue = endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start ? _start : _end + 1; // compare them *pRetVal = std::clamp(static_cast(ourValue) - static_cast(theirValue), -1, 1); + // TODO GitHub #1914: Re-attach Tracing to UIA Tree // tracing - ApiMsgCompareEndpoints apiMsg; + /*ApiMsgCompareEndpoints apiMsg; apiMsg.OtherId = range->GetId(); apiMsg.Endpoint = endpoint; apiMsg.TargetEndpoint = targetEndpoint; apiMsg.Result = *pRetVal; - Tracing::s_TraceUia(this, ApiCall::CompareEndpoints, &apiMsg); + Tracing::s_TraceUia(this, ApiCall::CompareEndpoints, &apiMsg);*/ return S_OK; } -IFACEMETHODIMP UiaTextRange::ExpandToEnclosingUnit(_In_ TextUnit unit) +IFACEMETHODIMP UiaTextRangeBase::ExpandToEnclosingUnit(_In_ TextUnit unit) { - ServiceLocator::LocateGlobals().getConsoleInformation().LockConsole(); - auto Unlock = wil::scope_exit([&] { - ServiceLocator::LocateGlobals().getConsoleInformation().UnlockConsole(); + _pData->LockConsole(); + auto Unlock = wil::scope_exit([&]() noexcept { + _pData->UnlockConsole(); }); ApiMsgExpandToEnclosingUnit apiMsg; @@ -536,7 +362,7 @@ IFACEMETHODIMP UiaTextRange::ExpandToEnclosingUnit(_In_ TextUnit unit) try { const ScreenInfoRow topRow = _getFirstScreenInfoRowIndex(); - const ScreenInfoRow bottomRow = _getLastScreenInfoRowIndex(); + const ScreenInfoRow bottomRow = _getLastScreenInfoRowIndex(_pData); if (unit == TextUnit::TextUnit_Character) { @@ -545,20 +371,21 @@ IFACEMETHODIMP UiaTextRange::ExpandToEnclosingUnit(_In_ TextUnit unit) else if (unit <= TextUnit::TextUnit_Line) { // expand to line - _start = _textBufferRowToEndpoint(_endpointToTextBufferRow(_start)); - _end = _start + _getLastColumnIndex(); + _start = _textBufferRowToEndpoint(_pData, _endpointToTextBufferRow(_pData, _start)); + _end = _start + _getLastColumnIndex(_pData); FAIL_FAST_IF(!(_start <= _end)); } else { // expand to document - _start = _screenInfoRowToEndpoint(topRow); - _end = _screenInfoRowToEndpoint(bottomRow) + _getLastColumnIndex(); + _start = _screenInfoRowToEndpoint(_pData, topRow); + _end = _screenInfoRowToEndpoint(_pData, bottomRow) + _getLastColumnIndex(_pData); } _degenerate = false; - Tracing::s_TraceUia(this, ApiCall::ExpandToEnclosingUnit, &apiMsg); + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::ExpandToEnclosingUnit, &apiMsg); return S_OK; } @@ -566,67 +393,23 @@ IFACEMETHODIMP UiaTextRange::ExpandToEnclosingUnit(_In_ TextUnit unit) } // we don't support this currently -IFACEMETHODIMP UiaTextRange::FindAttribute(_In_ TEXTATTRIBUTEID /*textAttributeId*/, - _In_ VARIANT /*val*/, - _In_ BOOL /*searchBackward*/, - _Outptr_result_maybenull_ ITextRangeProvider** /*ppRetVal*/) +IFACEMETHODIMP UiaTextRangeBase::FindAttribute(_In_ TEXTATTRIBUTEID /*textAttributeId*/, + _In_ VARIANT /*val*/, + _In_ BOOL /*searchBackward*/, + _Outptr_result_maybenull_ ITextRangeProvider** /*ppRetVal*/) noexcept { - Tracing::s_TraceUia(this, ApiCall::FindAttribute, nullptr); + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::FindAttribute, nullptr); return E_NOTIMPL; } -IFACEMETHODIMP UiaTextRange::FindText(_In_ BSTR text, - _In_ BOOL searchBackward, - _In_ BOOL ignoreCase, - _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) +IFACEMETHODIMP UiaTextRangeBase::GetAttributeValue(_In_ TEXTATTRIBUTEID textAttributeId, + _Out_ VARIANT* pRetVal) noexcept { - Tracing::s_TraceUia(this, ApiCall::FindText, nullptr); + RETURN_HR_IF(E_INVALIDARG, pRetVal == nullptr); - *ppRetVal = nullptr; - try - { - const std::wstring wstr{ text, SysStringLen(text) }; - const auto sensitivity = ignoreCase ? Search::Sensitivity::CaseInsensitive : Search::Sensitivity::CaseSensitive; - - auto searchDirection = Search::Direction::Forward; - Endpoint searchAnchor = _start; - if (searchBackward) - { - searchDirection = Search::Direction::Backward; - searchAnchor = _end; - } - - Search searcher{ _getScreenInfo(), wstr, searchDirection, sensitivity, _endpointToCoord(searchAnchor) }; - - HRESULT hr = S_OK; - if (searcher.FindNext()) - { - const auto foundLocation = searcher.GetFoundLocation(); - const Endpoint start = _coordToEndpoint(foundLocation.first); - const Endpoint end = _coordToEndpoint(foundLocation.second); - // make sure what was found is within the bounds of the current range - if ((searchDirection == Search::Direction::Forward && end < _end) || - (searchDirection == Search::Direction::Backward && start > _start)) - { - hr = Clone(ppRetVal); - if (SUCCEEDED(hr)) - { - UiaTextRange& range = static_cast(**ppRetVal); - range._start = start; - range._end = end; - range._degenerate = false; - } - } - } - return hr; - } - CATCH_RETURN(); -} - -IFACEMETHODIMP UiaTextRange::GetAttributeValue(_In_ TEXTATTRIBUTEID textAttributeId, - _Out_ VARIANT* pRetVal) -{ - Tracing::s_TraceUia(this, ApiCall::GetAttributeValue, nullptr); + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::GetAttributeValue, nullptr); if (textAttributeId == UIA_IsReadOnlyAttributeId) { pRetVal->vt = VT_BOOL; @@ -640,14 +423,14 @@ IFACEMETHODIMP UiaTextRange::GetAttributeValue(_In_ TEXTATTRIBUTEID textAttribut return S_OK; } -IFACEMETHODIMP UiaTextRange::GetBoundingRectangles(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) +IFACEMETHODIMP UiaTextRangeBase::GetBoundingRectangles(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) { - CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - gci.LockConsole(); - auto Unlock = wil::scope_exit([&] { - gci.UnlockConsole(); + _pData->LockConsole(); + auto Unlock = wil::scope_exit([&]() noexcept { + _pData->UnlockConsole(); }); + RETURN_HR_IF(E_INVALIDARG, ppRetVal == nullptr); *ppRetVal = nullptr; try @@ -656,36 +439,36 @@ IFACEMETHODIMP UiaTextRange::GetBoundingRectangles(_Outptr_result_maybenull_ SAF // order: left, top, width, height. each line will have its own // set of coords. std::vector coords; - const TextBufferRow startRow = _endpointToTextBufferRow(_start); + const TextBufferRow startRow = _endpointToTextBufferRow(_pData, _start); - if (_degenerate && _isScreenInfoRowInViewport(startRow)) + if (_degenerate && _isScreenInfoRowInViewport(_pData, startRow)) { - _addScreenInfoRowBoundaries(_textBufferRowToScreenInfoRow(startRow), coords); + _addScreenInfoRowBoundaries(_pData, _textBufferRowToScreenInfoRow(_pData, startRow), coords); } else { - const unsigned int totalRowsInRange = _rowCountInRange(); + const unsigned int totalRowsInRange = _rowCountInRange(_pData); for (unsigned int i = 0; i < totalRowsInRange; ++i) { - ScreenInfoRow screenInfoRow = _textBufferRowToScreenInfoRow(startRow + i); - if (!_isScreenInfoRowInViewport(screenInfoRow)) + const ScreenInfoRow screenInfoRow = _textBufferRowToScreenInfoRow(_pData, startRow + i); + if (!_isScreenInfoRowInViewport(_pData, screenInfoRow)) { continue; } - _addScreenInfoRowBoundaries(screenInfoRow, coords); + _addScreenInfoRowBoundaries(_pData, screenInfoRow, coords); } } // convert to a safearray - *ppRetVal = SafeArrayCreateVector(VT_R8, 0, static_cast(coords.size())); + *ppRetVal = SafeArrayCreateVector(VT_R8, 0, gsl::narrow(coords.size())); if (*ppRetVal == nullptr) { return E_OUTOFMEMORY; } - HRESULT hr; - for (LONG i = 0; i < static_cast(coords.size()); ++i) + HRESULT hr = E_UNEXPECTED; + for (LONG i = 0; i < gsl::narrow(coords.size()); ++i) { - hr = SafeArrayPutElement(*ppRetVal, &i, &coords[i]); + hr = SafeArrayPutElement(*ppRetVal, &i, &coords.at(i)); if (FAILED(hr)) { SafeArrayDestroy(*ppRetVal); @@ -696,25 +479,31 @@ IFACEMETHODIMP UiaTextRange::GetBoundingRectangles(_Outptr_result_maybenull_ SAF } CATCH_RETURN(); - Tracing::s_TraceUia(this, ApiCall::GetBoundingRectangles, nullptr); + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::GetBoundingRectangles, nullptr); return S_OK; } -IFACEMETHODIMP UiaTextRange::GetEnclosingElement(_Outptr_result_maybenull_ IRawElementProviderSimple** ppRetVal) +IFACEMETHODIMP UiaTextRangeBase::GetEnclosingElement(_Outptr_result_maybenull_ IRawElementProviderSimple** ppRetVal) { - Tracing::s_TraceUia(this, ApiCall::GetBoundingRectangles, nullptr); + RETURN_HR_IF(E_INVALIDARG, ppRetVal == nullptr); + *ppRetVal = nullptr; + + //Tracing::s_TraceUia(this, ApiCall::GetBoundingRectangles, nullptr); return _pProvider->QueryInterface(IID_PPV_ARGS(ppRetVal)); } -IFACEMETHODIMP UiaTextRange::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal) +IFACEMETHODIMP UiaTextRangeBase::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal) { - CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - gci.LockConsole(); - auto Unlock = wil::scope_exit([&] { - gci.UnlockConsole(); + _pData->LockConsole(); + auto Unlock = wil::scope_exit([&]() noexcept { + _pData->UnlockConsole(); }); + RETURN_HR_IF(E_INVALIDARG, pRetVal == nullptr); + *pRetVal = nullptr; + std::wstring wstr = L""; if (maxLength < -1) @@ -730,12 +519,12 @@ IFACEMETHODIMP UiaTextRange::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal) { try { - const ScreenInfoRow startScreenInfoRow = _endpointToScreenInfoRow(_start); - const Column startColumn = _endpointToColumn(_start); - const ScreenInfoRow endScreenInfoRow = _endpointToScreenInfoRow(_end); - const Column endColumn = _endpointToColumn(_end); - const unsigned int totalRowsInRange = _rowCountInRange(); - const TextBuffer& textBuffer = _getTextBuffer(); + const ScreenInfoRow startScreenInfoRow = _endpointToScreenInfoRow(_pData, _start); + const Column startColumn = _endpointToColumn(_pData, _start); + const ScreenInfoRow endScreenInfoRow = _endpointToScreenInfoRow(_pData, _end); + const Column endColumn = _endpointToColumn(_pData, _end); + const unsigned int totalRowsInRange = _rowCountInRange(_pData); + const TextBuffer& textBuffer = _pData->GetTextBuffer(); #if defined(_DEBUG) && defined(UIATEXTRANGE_DEBUG_MSGS) std::wstringstream ss; @@ -744,7 +533,7 @@ IFACEMETHODIMP UiaTextRange::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal) OutputDebugString(ss.str().c_str()); #endif - ScreenInfoRow currentScreenInfoRow; + ScreenInfoRow currentScreenInfoRow = 0; for (unsigned int i = 0; i < totalRowsInRange; ++i) { currentScreenInfoRow = startScreenInfoRow + i; @@ -761,7 +550,7 @@ IFACEMETHODIMP UiaTextRange::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal) if (currentScreenInfoRow == endScreenInfoRow) { // prevent the end from going past the last non-whitespace char in the row - endIndex = std::min(static_cast(endColumn + 1), rowRight); + endIndex = std::min(gsl::narrow_cast(endColumn) + 1, rowRight); } // if startIndex >= endIndex then _start is @@ -791,10 +580,11 @@ IFACEMETHODIMP UiaTextRange::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal) *pRetVal = SysAllocString(wstr.c_str()); + // TODO GitHub #1914: Re-attach Tracing to UIA Tree // tracing - ApiMsgGetText apiMsg; + /*ApiMsgGetText apiMsg; apiMsg.Text = wstr.c_str(); - Tracing::s_TraceUia(this, ApiCall::GetText, &apiMsg); + Tracing::s_TraceUia(this, ApiCall::GetText, &apiMsg);*/ #if defined(_DEBUG) && defined(UIATEXTRANGE_DEBUG_MSGS) std::wstringstream ss; @@ -805,15 +595,16 @@ IFACEMETHODIMP UiaTextRange::GetText(_In_ int maxLength, _Out_ BSTR* pRetVal) return S_OK; } -IFACEMETHODIMP UiaTextRange::Move(_In_ TextUnit unit, - _In_ int count, - _Out_ int* pRetVal) +IFACEMETHODIMP UiaTextRangeBase::Move(_In_ TextUnit unit, + _In_ int count, + _Out_ int* pRetVal) { - ServiceLocator::LocateGlobals().getConsoleInformation().LockConsole(); - auto Unlock = wil::scope_exit([&] { - ServiceLocator::LocateGlobals().getConsoleInformation().UnlockConsole(); + _pData->LockConsole(); + auto Unlock = wil::scope_exit([&]() noexcept { + _pData->UnlockConsole(); }); + RETURN_HR_IF(E_INVALIDARG, pRetVal == nullptr); *pRetVal = 0; if (count == 0) { @@ -847,13 +638,14 @@ IFACEMETHODIMP UiaTextRange::Move(_In_ TextUnit unit, moveFunc = &_moveByLine; } - MovementDirection moveDirection = (count > 0) ? MovementDirection::Forward : MovementDirection::Backward; + const MovementDirection moveDirection = (count > 0) ? MovementDirection::Forward : MovementDirection::Backward; std::pair newEndpoints; try { - MoveState moveState{ *this, moveDirection }; - newEndpoints = moveFunc(count, + const MoveState moveState{ _pData, *this, moveDirection }; + newEndpoints = moveFunc(_pData, + count, moveState, pRetVal); } @@ -866,23 +658,25 @@ IFACEMETHODIMP UiaTextRange::Move(_In_ TextUnit unit, // moved. _degenerate = false; + // TODO GitHub #1914: Re-attach Tracing to UIA Tree // tracing - apiMsg.MovedCount = *pRetVal; - Tracing::s_TraceUia(this, ApiCall::Move, &apiMsg); + /*apiMsg.MovedCount = *pRetVal; + Tracing::s_TraceUia(this, ApiCall::Move, &apiMsg);*/ return S_OK; } -IFACEMETHODIMP UiaTextRange::MoveEndpointByUnit(_In_ TextPatternRangeEndpoint endpoint, - _In_ TextUnit unit, - _In_ int count, - _Out_ int* pRetVal) +IFACEMETHODIMP UiaTextRangeBase::MoveEndpointByUnit(_In_ TextPatternRangeEndpoint endpoint, + _In_ TextUnit unit, + _In_ int count, + _Out_ int* pRetVal) { - ServiceLocator::LocateGlobals().getConsoleInformation().LockConsole(); - auto Unlock = wil::scope_exit([&] { - ServiceLocator::LocateGlobals().getConsoleInformation().UnlockConsole(); + _pData->LockConsole(); + auto Unlock = wil::scope_exit([&]() noexcept { + _pData->UnlockConsole(); }); + RETURN_HR_IF(E_INVALIDARG, pRetVal == nullptr); *pRetVal = 0; if (count == 0) { @@ -908,7 +702,7 @@ IFACEMETHODIMP UiaTextRange::MoveEndpointByUnit(_In_ TextPatternRangeEndpoint en _outputRowConversions(); #endif - MovementDirection moveDirection = (count > 0) ? MovementDirection::Forward : MovementDirection::Backward; + const MovementDirection moveDirection = (count > 0) ? MovementDirection::Forward : MovementDirection::Backward; auto moveFunc = &_moveEndpointByUnitDocument; if (unit == TextUnit::TextUnit_Character) @@ -923,8 +717,8 @@ IFACEMETHODIMP UiaTextRange::MoveEndpointByUnit(_In_ TextPatternRangeEndpoint en std::tuple moveResults; try { - MoveState moveState{ *this, moveDirection }; - moveResults = moveFunc(count, endpoint, moveState, pRetVal); + const MoveState moveState{ _pData, *this, moveDirection }; + moveResults = moveFunc(_pData, count, endpoint, moveState, pRetVal); } CATCH_RETURN(); @@ -932,23 +726,24 @@ IFACEMETHODIMP UiaTextRange::MoveEndpointByUnit(_In_ TextPatternRangeEndpoint en _end = std::get<1>(moveResults); _degenerate = std::get<2>(moveResults); + // TODO GitHub #1914: Re-attach Tracing to UIA Tree // tracing - apiMsg.MovedCount = *pRetVal; - Tracing::s_TraceUia(this, ApiCall::MoveEndpointByUnit, &apiMsg); + /*apiMsg.MovedCount = *pRetVal; + Tracing::s_TraceUia(this, ApiCall::MoveEndpointByUnit, &apiMsg);*/ return S_OK; } -IFACEMETHODIMP UiaTextRange::MoveEndpointByRange(_In_ TextPatternRangeEndpoint endpoint, - _In_ ITextRangeProvider* pTargetRange, - _In_ TextPatternRangeEndpoint targetEndpoint) +IFACEMETHODIMP UiaTextRangeBase::MoveEndpointByRange(_In_ TextPatternRangeEndpoint endpoint, + _In_ ITextRangeProvider* pTargetRange, + _In_ TextPatternRangeEndpoint targetEndpoint) { - ServiceLocator::LocateGlobals().getConsoleInformation().LockConsole(); - auto Unlock = wil::scope_exit([&] { - ServiceLocator::LocateGlobals().getConsoleInformation().UnlockConsole(); + _pData->LockConsole(); + auto Unlock = wil::scope_exit([&]() noexcept { + _pData->UnlockConsole(); }); - UiaTextRange* range = static_cast(pTargetRange); + const UiaTextRangeBase* range = static_cast(pTargetRange); if (range == nullptr) { return E_INVALIDARG; @@ -975,7 +770,7 @@ IFACEMETHODIMP UiaTextRange::MoveEndpointByRange(_In_ TextPatternRangeEndpoint e #endif // get the value that we're updating to - Endpoint targetEndpointValue; + Endpoint targetEndpointValue = 0; if (targetEndpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) { targetEndpointValue = range->GetStart(); @@ -1002,188 +797,171 @@ IFACEMETHODIMP UiaTextRange::MoveEndpointByRange(_In_ TextPatternRangeEndpoint e } } - // convert then endpoints to screen info rows/columns - ScreenInfoRow startScreenInfoRow; - Column startColumn; - ScreenInfoRow endScreenInfoRow; - Column endColumn; - ScreenInfoRow targetScreenInfoRow; - Column targetColumn; try { - startScreenInfoRow = _endpointToScreenInfoRow(_start); - startColumn = _endpointToColumn(_start); - endScreenInfoRow = _endpointToScreenInfoRow(_end); - endColumn = _endpointToColumn(_end); - targetScreenInfoRow = _endpointToScreenInfoRow(targetEndpointValue); - targetColumn = _endpointToColumn(targetEndpointValue); - } - CATCH_RETURN(); + // convert then endpoints to screen info rows/columns + const auto startScreenInfoRow = _endpointToScreenInfoRow(_pData, _start); + const auto startColumn = _endpointToColumn(_pData, _start); + const auto endScreenInfoRow = _endpointToScreenInfoRow(_pData, _end); + const auto endColumn = _endpointToColumn(_pData, _end); + const auto targetScreenInfoRow = _endpointToScreenInfoRow(_pData, targetEndpointValue); + const auto targetColumn = _endpointToColumn(_pData, targetEndpointValue); - // set endpoint value and check for crossed endpoints - bool crossedEndpoints = false; - if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) - { - _start = targetEndpointValue; - if (_compareScreenCoords(endScreenInfoRow, endColumn, targetScreenInfoRow, targetColumn) == -1) + // set endpoint value and check for crossed endpoints + bool crossedEndpoints = false; + if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) { - // endpoints were crossed - _end = _start; - crossedEndpoints = true; + _start = targetEndpointValue; + if (_compareScreenCoords(_pData, endScreenInfoRow, endColumn, targetScreenInfoRow, targetColumn) == -1) + { + // endpoints were crossed + _end = _start; + crossedEndpoints = true; + } } - } - else - { - _end = targetEndpointValue; - if (_compareScreenCoords(startScreenInfoRow, startColumn, targetScreenInfoRow, targetColumn) == 1) + else { - // endpoints were crossed - _start = _end; - crossedEndpoints = true; + _end = targetEndpointValue; + if (_compareScreenCoords(_pData, startScreenInfoRow, startColumn, targetScreenInfoRow, targetColumn) == 1) + { + // endpoints were crossed + _start = _end; + crossedEndpoints = true; + } } + _degenerate = crossedEndpoints; } - _degenerate = crossedEndpoints; + CATCH_RETURN(); - Tracing::s_TraceUia(this, ApiCall::MoveEndpointByRange, &apiMsg); + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::MoveEndpointByRange, &apiMsg); return S_OK; } -IFACEMETHODIMP UiaTextRange::Select() +IFACEMETHODIMP UiaTextRangeBase::Select() { - CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - gci.LockConsole(); - auto Unlock = wil::scope_exit([&] { - gci.UnlockConsole(); + _pData->LockConsole(); + auto Unlock = wil::scope_exit([&]() noexcept { + _pData->UnlockConsole(); }); if (_degenerate) { // calling Select on a degenerate range should clear any current selections - Selection::Instance().ClearSelection(); + _pData->ClearSelection(); } else { - COORD coordStart; - COORD coordEnd; + const COORD coordStart{ gsl::narrow(_endpointToColumn(_pData, _start)), + gsl::narrow(_endpointToScreenInfoRow(_pData, _start)) }; + const COORD coordEnd{ gsl::narrow(_endpointToColumn(_pData, _end)), + gsl::narrow(_endpointToScreenInfoRow(_pData, _end)) }; - coordStart.X = static_cast(_endpointToColumn(_start)); - coordStart.Y = static_cast(_endpointToScreenInfoRow(_start)); - - coordEnd.X = static_cast(_endpointToColumn(_end)); - coordEnd.Y = static_cast(_endpointToScreenInfoRow(_end)); - - Selection::Instance().SelectNewRegion(coordStart, coordEnd); + _pData->SelectNewRegion(coordStart, coordEnd); } - Tracing::s_TraceUia(this, ApiCall::Select, nullptr); + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::Select, nullptr); return S_OK; } // we don't support this -IFACEMETHODIMP UiaTextRange::AddToSelection() +IFACEMETHODIMP UiaTextRangeBase::AddToSelection() noexcept { - Tracing::s_TraceUia(this, ApiCall::AddToSelection, nullptr); + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::AddToSelection, nullptr); return E_NOTIMPL; } // we don't support this -IFACEMETHODIMP UiaTextRange::RemoveFromSelection() +IFACEMETHODIMP UiaTextRangeBase::RemoveFromSelection() noexcept { - Tracing::s_TraceUia(this, ApiCall::RemoveFromSelection, nullptr); + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::RemoveFromSelection, nullptr); return E_NOTIMPL; } -IFACEMETHODIMP UiaTextRange::ScrollIntoView(_In_ BOOL alignToTop) +IFACEMETHODIMP UiaTextRangeBase::ScrollIntoView(_In_ BOOL alignToTop) { - CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - gci.LockConsole(); - auto Unlock = wil::scope_exit([&] { - gci.UnlockConsole(); + _pData->LockConsole(); + auto Unlock = wil::scope_exit([&]() noexcept { + _pData->UnlockConsole(); }); - SMALL_RECT oldViewport; - unsigned int viewportHeight; - // range rows - ScreenInfoRow startScreenInfoRow; - ScreenInfoRow endScreenInfoRow; - // screen buffer rows - ScreenInfoRow topRow; - ScreenInfoRow bottomRow; try { - oldViewport = _getViewport().ToInclusive(); - viewportHeight = _getViewportHeight(oldViewport); + const auto oldViewport = _pData->GetViewport().ToInclusive(); + const auto viewportHeight = _getViewportHeight(oldViewport); // range rows - startScreenInfoRow = _endpointToScreenInfoRow(_start); - endScreenInfoRow = _endpointToScreenInfoRow(_end); + const auto startScreenInfoRow = _endpointToScreenInfoRow(_pData, _start); + const auto endScreenInfoRow = _endpointToScreenInfoRow(_pData, _end); // screen buffer rows - topRow = _getFirstScreenInfoRowIndex(); - bottomRow = _getLastScreenInfoRowIndex(); - } - CATCH_RETURN(); + const auto topRow = _getFirstScreenInfoRowIndex(); + const auto bottomRow = _getLastScreenInfoRowIndex(_pData); - SMALL_RECT newViewport = oldViewport; + SMALL_RECT newViewport = oldViewport; - // there's a bunch of +1/-1s here for setting the viewport. These - // are to account for the inclusivity of the viewport boundaries. - if (alignToTop) - { - // determine if we can align the start row to the top - if (startScreenInfoRow + viewportHeight <= bottomRow) - { - // we can align to the top - newViewport.Top = static_cast(startScreenInfoRow); - newViewport.Bottom = static_cast(startScreenInfoRow + viewportHeight - 1); - } - else - { - // we can align to the top so we'll just move the viewport - // to the bottom of the screen buffer - newViewport.Bottom = static_cast(bottomRow); - newViewport.Top = static_cast(bottomRow - viewportHeight + 1); - } - } - else - { - // we need to align to the bottom - // check if we can align to the bottom - if (endScreenInfoRow >= viewportHeight) + // there's a bunch of +1/-1s here for setting the viewport. These + // are to account for the inclusivity of the viewport boundaries. + if (alignToTop) { - // we can align to bottom - newViewport.Bottom = static_cast(endScreenInfoRow); - newViewport.Top = static_cast(endScreenInfoRow - viewportHeight + 1); + // determine if we can align the start row to the top + if (startScreenInfoRow + viewportHeight <= bottomRow) + { + // we can align to the top + newViewport.Top = gsl::narrow(startScreenInfoRow); + newViewport.Bottom = gsl::narrow(startScreenInfoRow + viewportHeight - 1); + } + else + { + // we can align to the top so we'll just move the viewport + // to the bottom of the screen buffer + newViewport.Bottom = gsl::narrow(bottomRow); + newViewport.Top = gsl::narrow(bottomRow - viewportHeight + 1); + } } else { - // we can't align to bottom so we'll move the viewport to - // the top of the screen buffer - newViewport.Top = static_cast(topRow); - newViewport.Bottom = static_cast(topRow + viewportHeight - 1); + // we need to align to the bottom + // check if we can align to the bottom + if (endScreenInfoRow >= viewportHeight) + { + // we can align to bottom + newViewport.Bottom = gsl::narrow(endScreenInfoRow); + newViewport.Top = gsl::narrow(endScreenInfoRow - viewportHeight + 1); + } + else + { + // we can't align to bottom so we'll move the viewport to + // the top of the screen buffer + newViewport.Top = gsl::narrow(topRow); + newViewport.Bottom = gsl::narrow(topRow + viewportHeight - 1); + } } - } - FAIL_FAST_IF(!(newViewport.Top >= static_cast(topRow))); - FAIL_FAST_IF(!(newViewport.Bottom <= static_cast(bottomRow))); - FAIL_FAST_IF(!(_getViewportHeight(oldViewport) == _getViewportHeight(newViewport))); + FAIL_FAST_IF(!(newViewport.Top >= gsl::narrow(topRow))); + FAIL_FAST_IF(!(newViewport.Bottom <= gsl::narrow(bottomRow))); + FAIL_FAST_IF(!(_getViewportHeight(oldViewport) == _getViewportHeight(newViewport))); - try - { - IConsoleWindow* pIConsoleWindow = _getIConsoleWindow(); - pIConsoleWindow->ChangeViewport(newViewport); + _ChangeViewport(newViewport); + + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + // tracing + /*ApiMsgScrollIntoView apiMsg; + apiMsg.AlignToTop = !!alignToTop; + Tracing::s_TraceUia(this, ApiCall::ScrollIntoView, &apiMsg);*/ } CATCH_RETURN(); - - // tracing - ApiMsgScrollIntoView apiMsg; - apiMsg.AlignToTop = !!alignToTop; - Tracing::s_TraceUia(this, ApiCall::ScrollIntoView, &apiMsg); - return S_OK; } -IFACEMETHODIMP UiaTextRange::GetChildren(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) +IFACEMETHODIMP UiaTextRangeBase::GetChildren(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) noexcept { - Tracing::s_TraceUia(this, ApiCall::GetChildren, nullptr); + // TODO GitHub #1914: Re-attach Tracing to UIA Tree + //Tracing::s_TraceUia(this, ApiCall::GetChildren, nullptr); + + RETURN_HR_IF(E_INVALIDARG, ppRetVal == nullptr); + // we don't have any children *ppRetVal = SafeArrayCreateVector(VT_UNKNOWN, 0, 0); if (*ppRetVal == nullptr) @@ -1195,65 +973,20 @@ IFACEMETHODIMP UiaTextRange::GetChildren(_Outptr_result_maybenull_ SAFEARRAY** p #pragma endregion -// Routine Description: -// - Gets the current viewport -// Arguments: -// - -// Return Value: -// - The screen info's current viewport -const Microsoft::Console::Types::Viewport& UiaTextRange::_getViewport() -{ - return _getScreenInfo().GetViewport(); -} - -// Routine Description: -// - Gets the current window -// Arguments: -// - -// Return Value: -// - The current window. May return nullptr if there is no current -// window. -Microsoft::Console::Interactivity::IConsoleWindow* const UiaTextRange::_getIConsoleWindow() +const COORD UiaTextRangeBase::_getScreenBufferCoords(gsl::not_null pData) { - using namespace Microsoft::Console::Interactivity; - IConsoleWindow* const pIConsoleWindow = ServiceLocator::LocateConsoleWindow(); - THROW_HR_IF_NULL(E_POINTER, pIConsoleWindow); - return pIConsoleWindow; + return pData->GetTextBuffer().GetSize().Dimensions(); } -// Routine Description: -// - gets the current window handle -// Arguments: -// - -// Return Value -// - the current window handle -HWND UiaTextRange::_getWindowHandle() +const COORD UiaTextRangeBase::_getScreenFontSize() const { - return _getIConsoleWindow()->GetWindowHandle(); -} + COORD coordRet = _pData->GetFontInfo().GetSize(); -// Routine Description: -// - gets the current screen info -// Arguments: -// - -// Return Value -// - the current screen info. May return nullptr. -SCREEN_INFORMATION& UiaTextRange::_getScreenInfo() -{ - CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - THROW_HR_IF(E_POINTER, !gci.HasActiveOutputBuffer()); - return gci.GetActiveOutputBuffer().GetActiveBuffer(); -} + // For sanity's sake, make sure not to leak 0 out as a possible value. These values are used in division operations. + coordRet.X = std::max(coordRet.X, 1i16); + coordRet.Y = std::max(coordRet.Y, 1i16); -// Routine Description: -// - gets the current output text buffer -// Arguments: -// - -// Return Value -// - the current output text buffer. May return nullptr. -TextBuffer& UiaTextRange::_getTextBuffer() -{ - return _getScreenInfo().GetTextBuffer(); + return coordRet; } // Routine Description: @@ -1262,20 +995,9 @@ TextBuffer& UiaTextRange::_getTextBuffer() // - // Return Value: // - The number of rows -const unsigned int UiaTextRange::_getTotalRows() -{ - return _getTextBuffer().TotalRowCount(); -} - -// Routine Description: -// - gets the current screen buffer size. -// Arguments: -// - -// Return Value: -// - The screen buffer size -const COORD UiaTextRange::_getScreenBufferCoords() +const unsigned int UiaTextRangeBase::_getTotalRows(gsl::not_null pData) noexcept { - return _getScreenInfo().GetBufferSize().Dimensions(); + return pData->GetTextBuffer().TotalRowCount(); } // Routine Description: @@ -1284,10 +1006,10 @@ const COORD UiaTextRange::_getScreenBufferCoords() // - // Return Value: // - The row width -const unsigned int UiaTextRange::_getRowWidth() +const unsigned int UiaTextRangeBase::_getRowWidth(gsl::not_null pData) { // make sure that we can't leak a 0 - return std::max(static_cast(_getScreenBufferCoords().X), 1u); + return std::max(static_cast(_getScreenBufferCoords(pData).X), 1u); } // Routine Description: @@ -1296,9 +1018,9 @@ const unsigned int UiaTextRange::_getRowWidth() // - endpoint - the endpoint to translate // Return Value: // - the column value -const Column UiaTextRange::_endpointToColumn(const Endpoint endpoint) +const Column UiaTextRangeBase::_endpointToColumn(gsl::not_null pData, const Endpoint endpoint) { - return endpoint % _getRowWidth(); + return endpoint % _getRowWidth(pData); } // Routine Description: @@ -1307,9 +1029,10 @@ const Column UiaTextRange::_endpointToColumn(const Endpoint endpoint) // - endpoint - the endpoint to convert // Return Value: // - the text buffer row value -const TextBufferRow UiaTextRange::_endpointToTextBufferRow(const Endpoint endpoint) +const TextBufferRow UiaTextRangeBase::_endpointToTextBufferRow(gsl::not_null pData, + const Endpoint endpoint) { - return endpoint / _getRowWidth(); + return endpoint / _getRowWidth(pData); } // Routine Description: @@ -1319,19 +1042,19 @@ const TextBufferRow UiaTextRange::_endpointToTextBufferRow(const Endpoint endpoi // - // Return Value: // - The number of rows in the range. -const unsigned int UiaTextRange::_rowCountInRange() const +const unsigned int UiaTextRangeBase::_rowCountInRange(gsl::not_null pData) const { if (_degenerate) { return 0; } - const ScreenInfoRow startScreenInfoRow = _endpointToScreenInfoRow(_start); - const Column startColumn = _endpointToColumn(_start); - const ScreenInfoRow endScreenInfoRow = _endpointToScreenInfoRow(_end); - const Column endColumn = _endpointToColumn(_end); + const ScreenInfoRow startScreenInfoRow = _endpointToScreenInfoRow(pData, _start); + const Column startColumn = _endpointToColumn(pData, _start); + const ScreenInfoRow endScreenInfoRow = _endpointToScreenInfoRow(pData, _end); + const Column endColumn = _endpointToColumn(pData, _end); - FAIL_FAST_IF(!(_compareScreenCoords(startScreenInfoRow, startColumn, endScreenInfoRow, endColumn) <= 0)); + FAIL_FAST_IF(!(_compareScreenCoords(pData, startScreenInfoRow, startColumn, endScreenInfoRow, endColumn) <= 0)); // + 1 to balance subtracting ScreenInfoRows from each other return endScreenInfoRow - startScreenInfoRow + 1; @@ -1343,10 +1066,11 @@ const unsigned int UiaTextRange::_rowCountInRange() const // - row - the TextBufferRow to convert // Return Value: // - the equivalent ScreenInfoRow. -const ScreenInfoRow UiaTextRange::_textBufferRowToScreenInfoRow(const TextBufferRow row) +const ScreenInfoRow UiaTextRangeBase::_textBufferRowToScreenInfoRow(gsl::not_null pData, + const TextBufferRow row) noexcept { - const int firstRowIndex = _getTextBuffer().GetFirstRowIndex(); - return _normalizeRow(row - firstRowIndex); + const int firstRowIndex = pData->GetTextBuffer().GetFirstRowIndex(); + return _normalizeRow(pData, row - firstRowIndex); } // Routine Description: @@ -1356,25 +1080,12 @@ const ScreenInfoRow UiaTextRange::_textBufferRowToScreenInfoRow(const TextBuffer // - row - the ScreenInfoRow to convert // Return Value: // - the equivalent ViewportRow. -const ViewportRow UiaTextRange::_screenInfoRowToViewportRow(const ScreenInfoRow row) +const ViewportRow UiaTextRangeBase::_screenInfoRowToViewportRow(gsl::not_null pData, const ScreenInfoRow row) noexcept { - const SMALL_RECT viewport = _getViewport().ToInclusive(); + const SMALL_RECT viewport = pData->GetViewport().ToInclusive(); return _screenInfoRowToViewportRow(row, viewport); } -// Routine Description: -// - Converts a ScreenInfoRow to a ViewportRow. -// Arguments: -// - row - the ScreenInfoRow to convert -// - viewport - the viewport to use for the conversion -// Return Value: -// - the equivalent ViewportRow. -const ViewportRow UiaTextRange::_screenInfoRowToViewportRow(const ScreenInfoRow row, - const SMALL_RECT viewport) -{ - return row - viewport.Top; -} - // Routine Description: // - normalizes the row index to within the bounds of the output // buffer. The output buffer stores the text in a circular buffer so @@ -1383,9 +1094,9 @@ const ViewportRow UiaTextRange::_screenInfoRowToViewportRow(const ScreenInfoRow // - the non-normalized row index // Return Value: // - the normalized row index -const Row UiaTextRange::_normalizeRow(const Row row) +const Row UiaTextRangeBase::_normalizeRow(gsl::not_null pData, const Row row) noexcept { - const unsigned int totalRows = _getTotalRows(); + const unsigned int totalRows = _getTotalRows(pData); return ((row + totalRows) % totalRows); } @@ -1395,7 +1106,7 @@ const Row UiaTextRange::_normalizeRow(const Row row) // - viewport - The viewport to measure // Return Value: // - The viewport height -const unsigned int UiaTextRange::_getViewportHeight(const SMALL_RECT viewport) +const unsigned int UiaTextRangeBase::_getViewportHeight(const SMALL_RECT viewport) noexcept { FAIL_FAST_IF(!(viewport.Bottom >= viewport.Top)); // + 1 because COORD is inclusive on both sides so subtracting top @@ -1409,7 +1120,7 @@ const unsigned int UiaTextRange::_getViewportHeight(const SMALL_RECT viewport) // - viewport - The viewport to measure // Return Value: // - The viewport width -const unsigned int UiaTextRange::_getViewportWidth(const SMALL_RECT viewport) +const unsigned int UiaTextRangeBase::_getViewportWidth(const SMALL_RECT viewport) noexcept { FAIL_FAST_IF(!(viewport.Right >= viewport.Left)); @@ -1425,9 +1136,10 @@ const unsigned int UiaTextRange::_getViewportWidth(const SMALL_RECT viewport) // - row - the screen info row to check // Return Value: // - true if the row is within the bounds of the viewport -const bool UiaTextRange::_isScreenInfoRowInViewport(const ScreenInfoRow row) +const bool UiaTextRangeBase::_isScreenInfoRowInViewport(gsl::not_null pData, + const ScreenInfoRow row) noexcept { - return _isScreenInfoRowInViewport(row, _getViewport().ToInclusive()); + return _isScreenInfoRowInViewport(row, pData->GetViewport().ToInclusive()); } // Routine Description: @@ -1437,12 +1149,12 @@ const bool UiaTextRange::_isScreenInfoRowInViewport(const ScreenInfoRow row) // - viewport - the viewport to use for the bounds // Return Value: // - true if the row is within the bounds of the viewport -const bool UiaTextRange::_isScreenInfoRowInViewport(const ScreenInfoRow row, - const SMALL_RECT viewport) +const bool UiaTextRangeBase::_isScreenInfoRowInViewport(const ScreenInfoRow row, + const SMALL_RECT viewport) noexcept { - ViewportRow viewportRow = _screenInfoRowToViewportRow(row, viewport); + const ViewportRow viewportRow = _screenInfoRowToViewportRow(row, viewport); return viewportRow >= 0 && - viewportRow < static_cast(_getViewportHeight(viewport)); + viewportRow < gsl::narrow(_getViewportHeight(viewport)); } // Routine Description: @@ -1451,10 +1163,11 @@ const bool UiaTextRange::_isScreenInfoRowInViewport(const ScreenInfoRow row, // - row - the ScreenInfoRow to convert // Return Value: // - the equivalent TextBufferRow. -const TextBufferRow UiaTextRange::_screenInfoRowToTextBufferRow(const ScreenInfoRow row) +const TextBufferRow UiaTextRangeBase::_screenInfoRowToTextBufferRow(gsl::not_null pData, + const ScreenInfoRow row) noexcept { - const TextBufferRow firstRowIndex = _getTextBuffer().GetFirstRowIndex(); - return _normalizeRow(row + firstRowIndex); + const TextBufferRow firstRowIndex = pData->GetTextBuffer().GetFirstRowIndex(); + return _normalizeRow(pData, row + firstRowIndex); } // Routine Description: @@ -1463,9 +1176,9 @@ const TextBufferRow UiaTextRange::_screenInfoRowToTextBufferRow(const ScreenInfo // - row - the TextBufferRow to convert // Return Value: // - the equivalent Endpoint, starting at the beginning of the TextBufferRow. -const Endpoint UiaTextRange::_textBufferRowToEndpoint(const TextBufferRow row) +const Endpoint UiaTextRangeBase::_textBufferRowToEndpoint(gsl::not_null pData, const TextBufferRow row) { - return _getRowWidth() * row; + return _getRowWidth(pData) * row; } // Routine Description: @@ -1474,9 +1187,10 @@ const Endpoint UiaTextRange::_textBufferRowToEndpoint(const TextBufferRow row) // - row - the ScreenInfoRow to convert // Return Value: // - the equivalent Endpoint. -const Endpoint UiaTextRange::_screenInfoRowToEndpoint(const ScreenInfoRow row) +const Endpoint UiaTextRangeBase::_screenInfoRowToEndpoint(gsl::not_null pData, + const ScreenInfoRow row) { - return _textBufferRowToEndpoint(_screenInfoRowToTextBufferRow(row)); + return _textBufferRowToEndpoint(pData, _screenInfoRowToTextBufferRow(pData, row)); } // Routine Description: @@ -1485,9 +1199,10 @@ const Endpoint UiaTextRange::_screenInfoRowToEndpoint(const ScreenInfoRow row) // - endpoint - the endpoint to convert // Return Value: // - the equivalent ScreenInfoRow. -const ScreenInfoRow UiaTextRange::_endpointToScreenInfoRow(const Endpoint endpoint) +const ScreenInfoRow UiaTextRangeBase::_endpointToScreenInfoRow(gsl::not_null pData, + const Endpoint endpoint) { - return _textBufferRowToScreenInfoRow(_endpointToTextBufferRow(endpoint)); + return _textBufferRowToScreenInfoRow(pData, _endpointToTextBufferRow(pData, endpoint)); } // Routine Description: @@ -1499,19 +1214,19 @@ const ScreenInfoRow UiaTextRange::_endpointToScreenInfoRow(const Endpoint endpoi // - // Notes: // - alters coords. may throw an exception. -void UiaTextRange::_addScreenInfoRowBoundaries(const ScreenInfoRow screenInfoRow, - _Inout_ std::vector& coords) const +void UiaTextRangeBase::_addScreenInfoRowBoundaries(gsl::not_null pData, + const ScreenInfoRow screenInfoRow, + _Inout_ std::vector& coords) const { - const SCREEN_INFORMATION& screenInfo = _getScreenInfo(); - const COORD currentFontSize = screenInfo.GetScreenFontSize(); + const COORD currentFontSize = _getScreenFontSize(); - POINT topLeft; - POINT bottomRight; + POINT topLeft{ 0 }; + POINT bottomRight{ 0 }; - if (_endpointToScreenInfoRow(_start) == screenInfoRow) + if (_endpointToScreenInfoRow(pData, _start) == screenInfoRow) { // start is somewhere in this row so we start from its position - topLeft.x = _endpointToColumn(_start) * currentFontSize.X; + topLeft.x = _endpointToColumn(pData, _start) * currentFontSize.X; } else { @@ -1519,17 +1234,17 @@ void UiaTextRange::_addScreenInfoRowBoundaries(const ScreenInfoRow screenInfoRow topLeft.x = 0; } - topLeft.y = _screenInfoRowToViewportRow(screenInfoRow) * currentFontSize.Y; + topLeft.y = _screenInfoRowToViewportRow(pData, screenInfoRow) * currentFontSize.Y; - if (_endpointToScreenInfoRow(_end) == screenInfoRow) + if (_endpointToScreenInfoRow(pData, _end) == screenInfoRow) { // the endpoints are on the same row - bottomRight.x = (_endpointToColumn(_end) + 1) * currentFontSize.X; + bottomRight.x = (_endpointToColumn(pData, _end) + 1) * currentFontSize.X; } else { // _end is not on this row so span to the end of the row - bottomRight.x = _getViewportWidth(_getViewport().ToInclusive()) * currentFontSize.X; + bottomRight.x = _getViewportWidth(_pData->GetViewport().ToInclusive()) * currentFontSize.X; } // we add the font height only once here because we are adding each line individually @@ -1537,9 +1252,8 @@ void UiaTextRange::_addScreenInfoRowBoundaries(const ScreenInfoRow screenInfoRow // convert the coords to be relative to the screen instead of // the client window - HWND hwnd = _getWindowHandle(); - ClientToScreen(hwnd, &topLeft); - ClientToScreen(hwnd, &bottomRight); + _TranslatePointToScreen(&topLeft); + _TranslatePointToScreen(&bottomRight); const LONG width = bottomRight.x - topLeft.x; const LONG height = bottomRight.y - topLeft.y; @@ -1557,7 +1271,7 @@ void UiaTextRange::_addScreenInfoRowBoundaries(const ScreenInfoRow screenInfoRow // - // Return Value: // - the index of the first row (0-indexed) of the screen info -const unsigned int UiaTextRange::_getFirstScreenInfoRowIndex() +const unsigned int UiaTextRangeBase::_getFirstScreenInfoRowIndex() noexcept { return 0; } @@ -1568,9 +1282,9 @@ const unsigned int UiaTextRange::_getFirstScreenInfoRowIndex() // - // Return Value: // - the index of the last row (0-indexed) of the screen info -const unsigned int UiaTextRange::_getLastScreenInfoRowIndex() +const unsigned int UiaTextRangeBase::_getLastScreenInfoRowIndex(gsl::not_null pData) noexcept { - return _getTotalRows() - 1; + return _getTotalRows(pData) - 1; } // Routine Description: @@ -1579,7 +1293,7 @@ const unsigned int UiaTextRange::_getLastScreenInfoRowIndex() // - // Return Value: // - the index of the first column (0-indexed) of the screen info rows -const Column UiaTextRange::_getFirstColumnIndex() +const Column UiaTextRangeBase::_getFirstColumnIndex() noexcept { return 0; } @@ -1590,9 +1304,9 @@ const Column UiaTextRange::_getFirstColumnIndex() // - // Return Value: // - the index of the last column (0-indexed) of the screen info rows -const Column UiaTextRange::_getLastColumnIndex() +const Column UiaTextRangeBase::_getLastColumnIndex(gsl::not_null pData) { - return _getRowWidth() - 1; + return _getRowWidth(pData) - 1; } // Routine Description: @@ -1606,22 +1320,23 @@ const Column UiaTextRange::_getLastColumnIndex() // -1 if A < B // 1 if A > B // 0 if A == B -const int UiaTextRange::_compareScreenCoords(const ScreenInfoRow rowA, - const Column colA, - const ScreenInfoRow rowB, - const Column colB) +const int UiaTextRangeBase::_compareScreenCoords(gsl::not_null pData, + const ScreenInfoRow rowA, + const Column colA, + const ScreenInfoRow rowB, + const Column colB) { FAIL_FAST_IF(!(rowA >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(rowA <= _getLastScreenInfoRowIndex())); + FAIL_FAST_IF(!(rowA <= _getLastScreenInfoRowIndex(pData))); FAIL_FAST_IF(!(colA >= _getFirstColumnIndex())); - FAIL_FAST_IF(!(colA <= _getLastColumnIndex())); + FAIL_FAST_IF(!(colA <= _getLastColumnIndex(pData))); FAIL_FAST_IF(!(rowB >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(rowB <= _getLastScreenInfoRowIndex())); + FAIL_FAST_IF(!(rowB <= _getLastScreenInfoRowIndex(pData))); FAIL_FAST_IF(!(colB >= _getFirstColumnIndex())); - FAIL_FAST_IF(!(colB <= _getLastColumnIndex())); + FAIL_FAST_IF(!(colB <= _getLastColumnIndex(pData))); if (rowA < rowB) { @@ -1654,42 +1369,44 @@ const int UiaTextRange::_compareScreenCoords(const ScreenInfoRow rowA, // - pAmountMoved - the number of times that the return values are "moved" // Return Value: // - a pair of endpoints of the form -std::pair UiaTextRange::_moveByCharacter(const int moveCount, - const MoveState moveState, - _Out_ int* const pAmountMoved) +std::pair UiaTextRangeBase::_moveByCharacter(gsl::not_null pData, + const int moveCount, + const MoveState moveState, + _Out_ gsl::not_null const pAmountMoved) { if (moveState.Direction == MovementDirection::Forward) { - return _moveByCharacterForward(moveCount, moveState, pAmountMoved); + return _moveByCharacterForward(pData, moveCount, moveState, pAmountMoved); } else { - return _moveByCharacterBackward(moveCount, moveState, pAmountMoved); + return _moveByCharacterBackward(pData, moveCount, moveState, pAmountMoved); } } -std::pair UiaTextRange::_moveByCharacterForward(const int moveCount, - const MoveState moveState, - _Out_ int* const pAmountMoved) +std::pair UiaTextRangeBase::_moveByCharacterForward(gsl::not_null pData, + const int moveCount, + const MoveState moveState, + _Out_ gsl::not_null const pAmountMoved) { *pAmountMoved = 0; - int count = moveCount; + const int count = moveCount; ScreenInfoRow currentScreenInfoRow = moveState.StartScreenInfoRow; Column currentColumn = moveState.StartColumn; for (int i = 0; i < abs(count); ++i) { // get the current row's right - const ROW& row = _getTextBuffer().GetRowByOffset(currentScreenInfoRow); + const ROW& row = pData->GetTextBuffer().GetRowByOffset(currentScreenInfoRow); const size_t right = row.GetCharRow().MeasureRight(); // check if we're at the edge of the screen info buffer if (currentScreenInfoRow == moveState.LimitingRow && - currentColumn + 1 >= right) + gsl::narrow_cast(currentColumn) + 1 >= right) { break; } - else if (currentColumn + 1 >= right) + else if (gsl::narrow_cast(currentColumn) + 1 >= right) { // we're at the edge of a row and need to go to the next one currentColumn = moveState.FirstColumnInRow; @@ -1703,22 +1420,23 @@ std::pair UiaTextRange::_moveByCharacterForward(const int mo *pAmountMoved += static_cast(moveState.Increment); FAIL_FAST_IF(!(currentColumn >= _getFirstColumnIndex())); - FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex())); + FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex(pData))); FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex())); + FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData))); } - Endpoint start = _screenInfoRowToEndpoint(currentScreenInfoRow) + currentColumn; + Endpoint start = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; Endpoint end = start; return std::make_pair(std::move(start), std::move(end)); } -std::pair UiaTextRange::_moveByCharacterBackward(const int moveCount, - const MoveState moveState, - _Out_ int* const pAmountMoved) +std::pair UiaTextRangeBase::_moveByCharacterBackward(gsl::not_null pData, + const int moveCount, + const MoveState moveState, + _Out_ gsl::not_null const pAmountMoved) { *pAmountMoved = 0; - int count = moveCount; + const int count = moveCount; ScreenInfoRow currentScreenInfoRow = moveState.StartScreenInfoRow; Column currentColumn = moveState.StartColumn; @@ -1737,9 +1455,9 @@ std::pair UiaTextRange::_moveByCharacterBackward(const int m currentScreenInfoRow += static_cast(moveState.Increment); // get the right cell for the next row - const ROW& row = _getTextBuffer().GetRowByOffset(currentScreenInfoRow); + const ROW& row = pData->GetTextBuffer().GetRowByOffset(currentScreenInfoRow); const size_t right = row.GetCharRow().MeasureRight(); - currentColumn = static_cast((right == 0) ? 0 : right - 1); + currentColumn = gsl::narrow((right == 0) ? 0 : right - 1); } else { @@ -1749,12 +1467,12 @@ std::pair UiaTextRange::_moveByCharacterBackward(const int m *pAmountMoved += static_cast(moveState.Increment); FAIL_FAST_IF(!(currentColumn >= _getFirstColumnIndex())); - FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex())); + FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex(pData))); FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex())); + FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData))); } - Endpoint start = _screenInfoRowToEndpoint(currentScreenInfoRow) + currentColumn; + Endpoint start = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; Endpoint end = start; return std::make_pair(std::move(start), std::move(end)); } @@ -1769,13 +1487,14 @@ std::pair UiaTextRange::_moveByCharacterBackward(const int m // - pAmountMoved - the number of times that the return values are "moved" // Return Value: // - a pair of endpoints of the form -std::pair UiaTextRange::_moveByLine(const int moveCount, - const MoveState moveState, - _Out_ int* const pAmountMoved) +std::pair UiaTextRangeBase::_moveByLine(gsl::not_null pData, + const int moveCount, + const MoveState moveState, + _Out_ gsl::not_null const pAmountMoved) { *pAmountMoved = 0; - Endpoint start = _screenInfoRowToEndpoint(moveState.StartScreenInfoRow) + moveState.StartColumn; - Endpoint end = _screenInfoRowToEndpoint(moveState.EndScreenInfoRow) + moveState.EndColumn; + Endpoint start = _screenInfoRowToEndpoint(pData, moveState.StartScreenInfoRow) + moveState.StartColumn; + Endpoint end = _screenInfoRowToEndpoint(pData, moveState.EndScreenInfoRow) + moveState.EndColumn; ScreenInfoRow currentScreenInfoRow = moveState.StartScreenInfoRow; // we don't want to move the range if we're already in the // limiting row and trying to move off the end of the screen buffer @@ -1796,10 +1515,10 @@ std::pair UiaTextRange::_moveByLine(const int moveCount, *pAmountMoved += static_cast(moveState.Increment); FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex())); + FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData))); } - start = _screenInfoRowToEndpoint(currentScreenInfoRow); - end = start + _getLastColumnIndex(); + start = _screenInfoRowToEndpoint(pData, currentScreenInfoRow); + end = start + _getLastColumnIndex(pData); } return std::make_pair(std::move(start), std::move(end)); @@ -1815,17 +1534,18 @@ std::pair UiaTextRange::_moveByLine(const int moveCount, // - pAmountMoved - the number of times that the return values are "moved" // Return Value: // - a pair of endpoints of the form -std::pair UiaTextRange::_moveByDocument(const int /*moveCount*/, - const MoveState moveState, - _Out_ int* const pAmountMoved) +std::pair UiaTextRangeBase::_moveByDocument(gsl::not_null pData, + const int /*moveCount*/, + const MoveState moveState, + _Out_ gsl::not_null const pAmountMoved) { // We can't move by anything larger than a line, so move by document will apply and will // just report that it can't do that. *pAmountMoved = 0; // We then have to return the same endpoints as what we initially had so nothing happens. - Endpoint start = _screenInfoRowToEndpoint(moveState.StartScreenInfoRow) + moveState.StartColumn; - Endpoint end = _screenInfoRowToEndpoint(moveState.EndScreenInfoRow) + moveState.EndColumn; + Endpoint start = _screenInfoRowToEndpoint(pData, moveState.StartScreenInfoRow) + moveState.StartColumn; + Endpoint end = _screenInfoRowToEndpoint(pData, moveState.EndScreenInfoRow) + moveState.EndColumn; return std::make_pair(std::move(start), std::move(end)); } @@ -1841,31 +1561,33 @@ std::pair UiaTextRange::_moveByDocument(const int /*moveCoun // - pAmountMoved - the number of times that the return values are "moved" // Return Value: // - A tuple of elements of the form -std::tuple UiaTextRange::_moveEndpointByUnitCharacter(const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, - _Out_ int* const pAmountMoved) +std::tuple UiaTextRangeBase::_moveEndpointByUnitCharacter(gsl::not_null pData, + const int moveCount, + const TextPatternRangeEndpoint endpoint, + const MoveState moveState, + _Out_ gsl::not_null const pAmountMoved) { if (moveState.Direction == MovementDirection::Forward) { - return _moveEndpointByUnitCharacterForward(moveCount, endpoint, moveState, pAmountMoved); + return _moveEndpointByUnitCharacterForward(pData, moveCount, endpoint, moveState, pAmountMoved); } else { - return _moveEndpointByUnitCharacterBackward(moveCount, endpoint, moveState, pAmountMoved); + return _moveEndpointByUnitCharacterBackward(pData, moveCount, endpoint, moveState, pAmountMoved); } } std::tuple -UiaTextRange::_moveEndpointByUnitCharacterForward(const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, - _Out_ int* const pAmountMoved) +UiaTextRangeBase::_moveEndpointByUnitCharacterForward(gsl::not_null pData, + const int moveCount, + const TextPatternRangeEndpoint endpoint, + const MoveState moveState, + _Out_ gsl::not_null const pAmountMoved) { *pAmountMoved = 0; - int count = moveCount; - ScreenInfoRow currentScreenInfoRow; - Column currentColumn; + const int count = moveCount; + ScreenInfoRow currentScreenInfoRow = 0; + Column currentColumn = 0; // set current location vars if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) @@ -1882,16 +1604,16 @@ UiaTextRange::_moveEndpointByUnitCharacterForward(const int moveCount, for (int i = 0; i < abs(count); ++i) { // get the current row's right - const ROW& row = _getTextBuffer().GetRowByOffset(currentScreenInfoRow); + const ROW& row = pData->GetTextBuffer().GetRowByOffset(currentScreenInfoRow); const size_t right = row.GetCharRow().MeasureRight(); // check if we're at the edge of the screen info buffer if (currentScreenInfoRow == moveState.LimitingRow && - currentColumn + 1 >= right) + gsl::narrow_cast(currentColumn) + 1 >= right) { break; } - else if (currentColumn + 1 >= right) + else if (gsl::narrow_cast(currentColumn) + 1 >= right) { // we're at the edge of a row and need to go to the next one currentColumn = moveState.FirstColumnInRow; @@ -1905,20 +1627,21 @@ UiaTextRange::_moveEndpointByUnitCharacterForward(const int moveCount, *pAmountMoved += static_cast(moveState.Increment); FAIL_FAST_IF(!(currentColumn >= _getFirstColumnIndex())); - FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex())); + FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex(pData))); FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex())); + FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData))); } // translate the row back to an endpoint and handle any crossed endpoints - Endpoint convertedEndpoint = _screenInfoRowToEndpoint(currentScreenInfoRow) + currentColumn; - Endpoint start = _screenInfoRowToEndpoint(moveState.StartScreenInfoRow) + moveState.StartColumn; - Endpoint end = _screenInfoRowToEndpoint(moveState.EndScreenInfoRow) + moveState.EndColumn; + const Endpoint convertedEndpoint = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; + Endpoint start = _screenInfoRowToEndpoint(pData, moveState.StartScreenInfoRow) + moveState.StartColumn; + Endpoint end = _screenInfoRowToEndpoint(pData, moveState.EndScreenInfoRow) + moveState.EndColumn; bool degenerate = false; if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) { start = convertedEndpoint; - if (_compareScreenCoords(currentScreenInfoRow, + if (_compareScreenCoords(pData, + currentScreenInfoRow, currentColumn, moveState.EndScreenInfoRow, moveState.EndColumn) == 1) @@ -1930,7 +1653,8 @@ UiaTextRange::_moveEndpointByUnitCharacterForward(const int moveCount, else { end = convertedEndpoint; - if (_compareScreenCoords(currentScreenInfoRow, + if (_compareScreenCoords(pData, + currentScreenInfoRow, currentColumn, moveState.StartScreenInfoRow, moveState.StartColumn) == -1) @@ -1943,15 +1667,16 @@ UiaTextRange::_moveEndpointByUnitCharacterForward(const int moveCount, } std::tuple -UiaTextRange::_moveEndpointByUnitCharacterBackward(const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, - _Out_ int* const pAmountMoved) +UiaTextRangeBase::_moveEndpointByUnitCharacterBackward(gsl::not_null pData, + const int moveCount, + const TextPatternRangeEndpoint endpoint, + const MoveState moveState, + _Out_ gsl::not_null const pAmountMoved) { *pAmountMoved = 0; - int count = moveCount; - ScreenInfoRow currentScreenInfoRow; - Column currentColumn; + const int count = moveCount; + ScreenInfoRow currentScreenInfoRow = 0; + Column currentColumn = 0; // set current location vars if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) @@ -1980,9 +1705,9 @@ UiaTextRange::_moveEndpointByUnitCharacterBackward(const int moveCount, currentScreenInfoRow += static_cast(moveState.Increment); // get the right cell for the next row - const ROW& row = _getTextBuffer().GetRowByOffset(currentScreenInfoRow); + const ROW& row = pData->GetTextBuffer().GetRowByOffset(currentScreenInfoRow); const size_t right = row.GetCharRow().MeasureRight(); - currentColumn = static_cast((right == 0) ? 0 : right - 1); + currentColumn = gsl::narrow((right == 0) ? 0 : right - 1); } else { @@ -1992,20 +1717,21 @@ UiaTextRange::_moveEndpointByUnitCharacterBackward(const int moveCount, *pAmountMoved += static_cast(moveState.Increment); FAIL_FAST_IF(!(currentColumn >= _getFirstColumnIndex())); - FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex())); + FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex(pData))); FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex())); + FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData))); } // translate the row back to an endpoint and handle any crossed endpoints - Endpoint convertedEndpoint = _screenInfoRowToEndpoint(currentScreenInfoRow) + currentColumn; - Endpoint start = _screenInfoRowToEndpoint(moveState.StartScreenInfoRow) + moveState.StartColumn; - Endpoint end = _screenInfoRowToEndpoint(moveState.EndScreenInfoRow) + moveState.EndColumn; + const Endpoint convertedEndpoint = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; + Endpoint start = _screenInfoRowToEndpoint(pData, moveState.StartScreenInfoRow) + moveState.StartColumn; + Endpoint end = _screenInfoRowToEndpoint(pData, moveState.EndScreenInfoRow) + moveState.EndColumn; bool degenerate = false; if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) { start = convertedEndpoint; - if (_compareScreenCoords(currentScreenInfoRow, + if (_compareScreenCoords(pData, + currentScreenInfoRow, currentColumn, moveState.EndScreenInfoRow, moveState.EndColumn) == 1) @@ -2017,7 +1743,8 @@ UiaTextRange::_moveEndpointByUnitCharacterBackward(const int moveCount, else { end = convertedEndpoint; - if (_compareScreenCoords(currentScreenInfoRow, + if (_compareScreenCoords(pData, + currentScreenInfoRow, currentColumn, moveState.StartScreenInfoRow, moveState.StartColumn) == -1) @@ -2040,18 +1767,19 @@ UiaTextRange::_moveEndpointByUnitCharacterBackward(const int moveCount, // - pAmountMoved - the number of times that the return values are "moved" // Return Value: // - A tuple of elements of the form -std::tuple UiaTextRange::_moveEndpointByUnitLine(const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, - _Out_ int* const pAmountMoved) +std::tuple UiaTextRangeBase::_moveEndpointByUnitLine(gsl::not_null pData, + const int moveCount, + const TextPatternRangeEndpoint endpoint, + const MoveState moveState, + _Out_ gsl::not_null const pAmountMoved) { *pAmountMoved = 0; int count = moveCount; - ScreenInfoRow currentScreenInfoRow; - Column currentColumn; + ScreenInfoRow currentScreenInfoRow = 0; + Column currentColumn = 0; bool forceDegenerate = false; - Endpoint start = _screenInfoRowToEndpoint(moveState.StartScreenInfoRow) + moveState.StartColumn; - Endpoint end = _screenInfoRowToEndpoint(moveState.EndScreenInfoRow) + moveState.EndColumn; + Endpoint start = _screenInfoRowToEndpoint(pData, moveState.StartScreenInfoRow) + moveState.StartColumn; + Endpoint end = _screenInfoRowToEndpoint(pData, moveState.EndScreenInfoRow) + moveState.EndColumn; bool degenerate = false; if (moveCount == 0) @@ -2059,7 +1787,7 @@ std::tuple UiaTextRange::_moveEndpointByUnitLine(const return std::make_tuple(start, end, degenerate); } - MovementDirection moveDirection = (moveCount > 0) ? MovementDirection::Forward : MovementDirection::Backward; + const MovementDirection moveDirection = (moveCount > 0) ? MovementDirection::Forward : MovementDirection::Backward; if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) { @@ -2087,7 +1815,7 @@ std::tuple UiaTextRange::_moveEndpointByUnitLine(const // the very end. move to the end of the last row count -= static_cast(moveState.Increment); *pAmountMoved += static_cast(moveState.Increment); - currentColumn = _getLastColumnIndex(); + currentColumn = _getLastColumnIndex(pData); forceDegenerate = true; } if (moveState.StartColumn != _getFirstColumnIndex()) @@ -2115,13 +1843,13 @@ std::tuple UiaTextRange::_moveEndpointByUnitLine(const else if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_End && moveDirection == MovementDirection::Forward) { - if (moveState.EndColumn != _getLastColumnIndex()) + if (moveState.EndColumn != _getLastColumnIndex(pData)) { // _end is not at the last column in a row, so we move // forward to it with a partial movement count -= static_cast(moveState.Increment); *pAmountMoved += static_cast(moveState.Increment); - currentColumn = _getLastColumnIndex(); + currentColumn = _getLastColumnIndex(pData); } } else @@ -2136,21 +1864,21 @@ std::tuple UiaTextRange::_moveEndpointByUnitLine(const currentColumn = _getFirstColumnIndex(); forceDegenerate = true; } - else if (moveState.EndColumn != _getLastColumnIndex()) + else if (moveState.EndColumn != _getLastColumnIndex(pData)) { // _end is not at the last column in a row, so we move it // backwards to it with a partial move count -= static_cast(moveState.Increment); *pAmountMoved += static_cast(moveState.Increment); - currentColumn = _getLastColumnIndex(); + currentColumn = _getLastColumnIndex(pData); currentScreenInfoRow += static_cast(moveState.Increment); } } FAIL_FAST_IF(!(currentColumn >= _getFirstColumnIndex())); - FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex())); + FAIL_FAST_IF(!(currentColumn <= _getLastColumnIndex(pData))); FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex())); + FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData))); // move the row that the endpoint corresponds to while (count != 0 && currentScreenInfoRow != moveState.LimitingRow) @@ -2160,11 +1888,11 @@ std::tuple UiaTextRange::_moveEndpointByUnitLine(const *pAmountMoved += static_cast(moveState.Increment); FAIL_FAST_IF(!(currentScreenInfoRow >= _getFirstScreenInfoRowIndex())); - FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex())); + FAIL_FAST_IF(!(currentScreenInfoRow <= _getLastScreenInfoRowIndex(pData))); } // translate the row back to an endpoint and handle any crossed endpoints - Endpoint convertedEndpoint = _screenInfoRowToEndpoint(currentScreenInfoRow) + currentColumn; + const Endpoint convertedEndpoint = _screenInfoRowToEndpoint(pData, currentScreenInfoRow) + currentColumn; if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) { start = convertedEndpoint; @@ -2198,23 +1926,24 @@ std::tuple UiaTextRange::_moveEndpointByUnitLine(const // - pAmountMoved - the number of times that the return values are "moved" // Return Value: // - A tuple of elements of the form -std::tuple UiaTextRange::_moveEndpointByUnitDocument(const int moveCount, - const TextPatternRangeEndpoint endpoint, - const MoveState moveState, - _Out_ int* const pAmountMoved) +std::tuple UiaTextRangeBase::_moveEndpointByUnitDocument(gsl::not_null pData, + const int moveCount, + const TextPatternRangeEndpoint endpoint, + const MoveState moveState, + _Out_ gsl::not_null const pAmountMoved) { *pAmountMoved = 0; - Endpoint start; - Endpoint end; + Endpoint start = 0; + Endpoint end = 0; bool degenerate = false; if (endpoint == TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start) { if (moveCount < 0) { // moving _start backwards - start = _screenInfoRowToEndpoint(_getFirstScreenInfoRowIndex()) + _getFirstColumnIndex(); - end = _screenInfoRowToEndpoint(moveState.EndScreenInfoRow) + moveState.EndColumn; + start = _screenInfoRowToEndpoint(pData, _getFirstScreenInfoRowIndex()) + _getFirstColumnIndex(); + end = _screenInfoRowToEndpoint(pData, moveState.EndScreenInfoRow) + moveState.EndColumn; if (!(moveState.StartScreenInfoRow == _getFirstScreenInfoRowIndex() && moveState.StartColumn == _getFirstColumnIndex())) { @@ -2224,11 +1953,11 @@ std::tuple UiaTextRange::_moveEndpointByUnitDocument(c else { // moving _start forwards - start = _screenInfoRowToEndpoint(_getLastScreenInfoRowIndex()) + _getLastColumnIndex(); + start = _screenInfoRowToEndpoint(pData, _getLastScreenInfoRowIndex(pData)) + _getLastColumnIndex(pData); end = start; degenerate = true; - if (!(moveState.StartScreenInfoRow == _getLastScreenInfoRowIndex() && - moveState.StartColumn == _getLastColumnIndex())) + if (!(moveState.StartScreenInfoRow == _getLastScreenInfoRowIndex(pData) && + moveState.StartColumn == _getLastColumnIndex(pData))) { *pAmountMoved += static_cast(moveState.Increment); } @@ -2239,7 +1968,7 @@ std::tuple UiaTextRange::_moveEndpointByUnitDocument(c if (moveCount < 0) { // moving _end backwards - end = _screenInfoRowToEndpoint(_getFirstScreenInfoRowIndex()) + _getFirstColumnIndex(); + end = _screenInfoRowToEndpoint(pData, _getFirstScreenInfoRowIndex()) + _getFirstColumnIndex(); start = end; degenerate = true; if (!(moveState.EndScreenInfoRow == _getFirstScreenInfoRowIndex() && @@ -2251,10 +1980,10 @@ std::tuple UiaTextRange::_moveEndpointByUnitDocument(c else { // moving _end forwards - end = _screenInfoRowToEndpoint(_getLastScreenInfoRowIndex()) + _getLastColumnIndex(); - start = _screenInfoRowToEndpoint(moveState.StartScreenInfoRow) + moveState.StartColumn; - if (!(moveState.EndScreenInfoRow == _getLastScreenInfoRowIndex() && - moveState.EndColumn == _getLastColumnIndex())) + end = _screenInfoRowToEndpoint(pData, _getLastScreenInfoRowIndex(pData)) + _getLastColumnIndex(pData); + start = _screenInfoRowToEndpoint(pData, moveState.StartScreenInfoRow) + moveState.StartColumn; + if (!(moveState.EndScreenInfoRow == _getLastScreenInfoRowIndex(pData) && + moveState.EndColumn == _getLastColumnIndex(pData))) { *pAmountMoved += static_cast(moveState.Increment); } @@ -2264,12 +1993,32 @@ std::tuple UiaTextRange::_moveEndpointByUnitDocument(c return std::make_tuple(start, end, degenerate); } -COORD UiaTextRange::_endpointToCoord(const Endpoint endpoint) +COORD UiaTextRangeBase::_endpointToCoord(gsl::not_null pData, const Endpoint endpoint) { - return { gsl::narrow(_endpointToColumn(endpoint)), gsl::narrow(_endpointToScreenInfoRow(endpoint)) }; + return { gsl::narrow(_endpointToColumn(pData, endpoint)), gsl::narrow(_endpointToScreenInfoRow(pData, endpoint)) }; } -Endpoint UiaTextRange::_coordToEndpoint(const COORD coord) +Endpoint UiaTextRangeBase::_coordToEndpoint(gsl::not_null pData, + const COORD coord) { - return _screenInfoRowToEndpoint(coord.Y) + coord.X; + return _screenInfoRowToEndpoint(pData, coord.Y) + coord.X; +} + +RECT UiaTextRangeBase::_getTerminalRect() const +{ + UiaRect result{ 0 }; + + IRawElementProviderFragment* pRawElementProviderFragment; + THROW_IF_FAILED(_pProvider->QueryInterface(&pRawElementProviderFragment)); + if (pRawElementProviderFragment) + { + pRawElementProviderFragment->get_BoundingRectangle(&result); + } + + return { + gsl::narrow(result.left), + gsl::narrow(result.top), + gsl::narrow(result.left + result.width), + gsl::narrow(result.top + result.height) + }; } diff --git a/src/interactivity/win32/UiaTextRange.hpp b/src/types/UiaTextRangeBase.hpp similarity index 56% rename from src/interactivity/win32/UiaTextRange.hpp rename to src/types/UiaTextRangeBase.hpp index 7a0e716db4a..3b9e4c01b4b 100644 --- a/src/interactivity/win32/UiaTextRange.hpp +++ b/src/types/UiaTextRangeBase.hpp @@ -3,24 +3,26 @@ Copyright (c) Microsoft Corporation Licensed under the MIT license. Module Name: -- UiaTextRange.hpp +- UiaTextRangeBase.hpp Abstract: - This module provides UI Automation access to the text of the console window to support both automation tests and accessibility (screen reading) applications. +- ConHost and Windows Terminal must implement their own virtual functions separately. Author(s): - Austin Diviness (AustDi) 2017 +- Carlos Zamora (CaZamor) 2019 --*/ #pragma once #include "precomp.h" -#include "../inc/IConsoleWindow.hpp" -#include "../types/inc/viewport.hpp" -#include "../../buffer/out/cursor.h" +#include "inc/viewport.hpp" +#include "../buffer/out/textBuffer.hpp" +#include "IUiaData.h" #include #include @@ -29,7 +31,7 @@ Author(s): class UiaTextRangeTests; #endif -// The UiaTextRange deals with several data structures that have +// The UiaTextRangeBase deals with several data structures that have // similar semantics. In order to keep the information from these data // structures separated, each structure has its own naming for a // row. @@ -69,9 +71,9 @@ typedef unsigned int Endpoint; constexpr IdType InvalidId = 0; -namespace Microsoft::Console::Interactivity::Win32 +namespace Microsoft::Console::Types { - class UiaTextRange final : public ITextRangeProvider + class UiaTextRangeBase : public ITextRangeProvider { private: static IdType id; @@ -114,7 +116,8 @@ namespace Microsoft::Console::Interactivity::Win32 // direction moving MovementDirection Direction; - MoveState(const UiaTextRange& range, + MoveState(IUiaData* pData, + const UiaTextRangeBase& range, const MovementDirection direction); private: @@ -126,7 +129,7 @@ namespace Microsoft::Console::Interactivity::Win32 const Column firstColumnInRow, const Column lastColumnInRow, const MovementIncrement increment, - const MovementDirection direction); + const MovementDirection direction) noexcept; #ifdef UNIT_TESTING friend class ::UiaTextRangeTests; @@ -134,105 +137,98 @@ namespace Microsoft::Console::Interactivity::Win32 }; public: - static std::deque GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider); - - // degenerate range - static UiaTextRange* Create(_In_ IRawElementProviderSimple* const pProvider); - - // degenerate range at cursor position - static UiaTextRange* Create(_In_ IRawElementProviderSimple* const pProvider, - const Cursor& cursor); - - // specific endpoint range - static UiaTextRange* Create(_In_ IRawElementProviderSimple* const pProvider, - const Endpoint start, - const Endpoint end, - const bool degenerate); - - // range from a UiaPoint - static UiaTextRange* Create(_In_ IRawElementProviderSimple* const pProvider, - const UiaPoint point); + UiaTextRangeBase(UiaTextRangeBase&&) = default; + UiaTextRangeBase& operator=(const UiaTextRangeBase&) = default; + UiaTextRangeBase& operator=(UiaTextRangeBase&&) = default; + virtual ~UiaTextRangeBase() = default; - ~UiaTextRange(); + const IdType GetId() const noexcept; + const Endpoint GetStart() const noexcept; + const Endpoint GetEnd() const noexcept; + const bool IsDegenerate() const noexcept; - const IdType GetId() const; - const Endpoint GetStart() const; - const Endpoint GetEnd() const; - const bool IsDegenerate() const; + // TODO GitHub #605: + // only used for UiaData::FindText. Remove after Search added properly + void SetRangeValues(const Endpoint start, const Endpoint end, const bool isDegenerate) noexcept; // IUnknown methods IFACEMETHODIMP_(ULONG) - AddRef(); + AddRef() override; IFACEMETHODIMP_(ULONG) - Release(); + Release() override; IFACEMETHODIMP QueryInterface(_In_ REFIID riid, - _COM_Outptr_result_maybenull_ void** ppInterface); + _COM_Outptr_result_maybenull_ void** ppInterface) override; // ITextRangeProvider methods - IFACEMETHODIMP Clone(_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal); - IFACEMETHODIMP Compare(_In_opt_ ITextRangeProvider* pRange, _Out_ BOOL* pRetVal); + virtual IFACEMETHODIMP Clone(_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) = 0; + IFACEMETHODIMP Compare(_In_opt_ ITextRangeProvider* pRange, _Out_ BOOL* pRetVal) noexcept override; IFACEMETHODIMP CompareEndpoints(_In_ TextPatternRangeEndpoint endpoint, _In_ ITextRangeProvider* pTargetRange, _In_ TextPatternRangeEndpoint targetEndpoint, - _Out_ int* pRetVal); - IFACEMETHODIMP ExpandToEnclosingUnit(_In_ TextUnit unit); + _Out_ int* pRetVal) noexcept override; + IFACEMETHODIMP ExpandToEnclosingUnit(_In_ TextUnit unit) override; IFACEMETHODIMP FindAttribute(_In_ TEXTATTRIBUTEID textAttributeId, _In_ VARIANT val, _In_ BOOL searchBackward, - _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal); - IFACEMETHODIMP FindText(_In_ BSTR text, - _In_ BOOL searchBackward, - _In_ BOOL ignoreCase, - _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal); + _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) noexcept override; + virtual IFACEMETHODIMP FindText(_In_ BSTR text, + _In_ BOOL searchBackward, + _In_ BOOL ignoreCase, + _Outptr_result_maybenull_ ITextRangeProvider** ppRetVal) = 0; IFACEMETHODIMP GetAttributeValue(_In_ TEXTATTRIBUTEID textAttributeId, - _Out_ VARIANT* pRetVal); - IFACEMETHODIMP GetBoundingRectangles(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal); - IFACEMETHODIMP GetEnclosingElement(_Outptr_result_maybenull_ IRawElementProviderSimple** ppRetVal); + _Out_ VARIANT* pRetVal) noexcept override; + IFACEMETHODIMP GetBoundingRectangles(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) override; + IFACEMETHODIMP GetEnclosingElement(_Outptr_result_maybenull_ IRawElementProviderSimple** ppRetVal) override; IFACEMETHODIMP GetText(_In_ int maxLength, - _Out_ BSTR* pRetVal); + _Out_ BSTR* pRetVal) override; IFACEMETHODIMP Move(_In_ TextUnit unit, _In_ int count, - _Out_ int* pRetVal); + _Out_ int* pRetVal) override; IFACEMETHODIMP MoveEndpointByUnit(_In_ TextPatternRangeEndpoint endpoint, _In_ TextUnit unit, _In_ int count, - _Out_ int* pRetVal); + _Out_ int* pRetVal) override; IFACEMETHODIMP MoveEndpointByRange(_In_ TextPatternRangeEndpoint endpoint, _In_ ITextRangeProvider* pTargetRange, - _In_ TextPatternRangeEndpoint targetEndpoint); - IFACEMETHODIMP Select(); - IFACEMETHODIMP AddToSelection(); - IFACEMETHODIMP RemoveFromSelection(); - IFACEMETHODIMP ScrollIntoView(_In_ BOOL alignToTop); - IFACEMETHODIMP GetChildren(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal); + _In_ TextPatternRangeEndpoint targetEndpoint) override; + IFACEMETHODIMP Select() override; + IFACEMETHODIMP AddToSelection() noexcept override; + IFACEMETHODIMP RemoveFromSelection() noexcept override; + IFACEMETHODIMP ScrollIntoView(_In_ BOOL alignToTop) override; + IFACEMETHODIMP GetChildren(_Outptr_result_maybenull_ SAFEARRAY** ppRetVal) noexcept override; protected: #if _DEBUG - void _outputRowConversions(); + void _outputRowConversions(IUiaData* pData); void _outputObjectState(); #endif + IUiaData* const _pData; - IRawElementProviderSimple* const _pProvider; + wil::com_ptr const _pProvider; + + virtual void _ChangeViewport(const SMALL_RECT NewWindow) = 0; + virtual void _TranslatePointToScreen(LPPOINT clientPoint) const = 0; + virtual void _TranslatePointFromScreen(LPPOINT screenPoint) const = 0; - private: // degenerate range - UiaTextRange(_In_ IRawElementProviderSimple* const pProvider); + UiaTextRangeBase(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider); // degenerate range at cursor position - UiaTextRange(_In_ IRawElementProviderSimple* const pProvider, - const Cursor& cursor); + UiaTextRangeBase(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const Cursor& cursor); // specific endpoint range - UiaTextRange(_In_ IRawElementProviderSimple* const pProvider, - const Endpoint start, - const Endpoint end, - const bool degenerate); + UiaTextRangeBase(_In_ IUiaData* pData, + _In_ IRawElementProviderSimple* const pProvider, + const Endpoint start, + const Endpoint end, + const bool degenerate); - // range from a UiaPoint - UiaTextRange(_In_ IRawElementProviderSimple* const pProvider, - const UiaPoint point); + void Initialize(_In_ const UiaPoint point); - UiaTextRange(const UiaTextRange& a); + UiaTextRangeBase(const UiaTextRangeBase& a) noexcept; // used to debug objects passed back and forth // between the provider and the client @@ -264,115 +260,145 @@ namespace Microsoft::Console::Interactivity::Win32 // then both endpoints will contain the same value. bool _degenerate; - static const Microsoft::Console::Types::Viewport& _getViewport(); - static HWND _getWindowHandle(); - static IConsoleWindow* const _getIConsoleWindow(); - static SCREEN_INFORMATION& _getScreenInfo(); - static TextBuffer& _getTextBuffer(); - static const COORD _getScreenBufferCoords(); + RECT _getTerminalRect() const; + + static const COORD _getScreenBufferCoords(gsl::not_null pData); + virtual const COORD _getScreenFontSize() const; - static const unsigned int _getTotalRows(); - static const unsigned int _getRowWidth(); + static const unsigned int _getTotalRows(gsl::not_null pData) noexcept; + static const unsigned int _getRowWidth(gsl::not_null pData); - static const unsigned int _getFirstScreenInfoRowIndex(); - static const unsigned int _getLastScreenInfoRowIndex(); + static const unsigned int _getFirstScreenInfoRowIndex() noexcept; + static const unsigned int _getLastScreenInfoRowIndex(gsl::not_null pData) noexcept; - static const Column _getFirstColumnIndex(); - static const Column _getLastColumnIndex(); + static const Column _getFirstColumnIndex() noexcept; + static const Column _getLastColumnIndex(gsl::not_null pData); - const unsigned int _rowCountInRange() const; + const unsigned int _rowCountInRange(gsl::not_null pData) const; - static const TextBufferRow _endpointToTextBufferRow(const Endpoint endpoint); - static const ScreenInfoRow _textBufferRowToScreenInfoRow(const TextBufferRow row); + static const TextBufferRow _endpointToTextBufferRow(gsl::not_null pData, + const Endpoint endpoint); + static const ScreenInfoRow _textBufferRowToScreenInfoRow(gsl::not_null pData, + const TextBufferRow row) noexcept; - static const TextBufferRow _screenInfoRowToTextBufferRow(const ScreenInfoRow row); - static const Endpoint _textBufferRowToEndpoint(const TextBufferRow row); + static const TextBufferRow _screenInfoRowToTextBufferRow(gsl::not_null pData, + const ScreenInfoRow row) noexcept; + static const Endpoint _textBufferRowToEndpoint(gsl::not_null pData, const TextBufferRow row); - static const ScreenInfoRow _endpointToScreenInfoRow(const Endpoint endpoint); - static const Endpoint _screenInfoRowToEndpoint(const ScreenInfoRow row); + static const ScreenInfoRow _endpointToScreenInfoRow(gsl::not_null pData, + const Endpoint endpoint); + static const Endpoint _screenInfoRowToEndpoint(gsl::not_null pData, + const ScreenInfoRow row); - static COORD _endpointToCoord(const Endpoint endpoint); - static Endpoint _coordToEndpoint(const COORD coord); + static COORD _endpointToCoord(gsl::not_null pData, + const Endpoint endpoint); + static Endpoint _coordToEndpoint(gsl::not_null pData, + const COORD coord); - static const Column _endpointToColumn(const Endpoint endpoint); + static const Column _endpointToColumn(gsl::not_null pData, + const Endpoint endpoint); - static const Row _normalizeRow(const Row row); + static const Row _normalizeRow(gsl::not_null pData, const Row row) noexcept; - static const ViewportRow _screenInfoRowToViewportRow(const ScreenInfoRow row); - static const ViewportRow _screenInfoRowToViewportRow(const ScreenInfoRow row, - const SMALL_RECT viewport); + static const ViewportRow _screenInfoRowToViewportRow(gsl::not_null pData, + const ScreenInfoRow row) noexcept; + // Routine Description: + // - Converts a ScreenInfoRow to a ViewportRow. + // Arguments: + // - row - the ScreenInfoRow to convert + // - viewport - the viewport to use for the conversion + // Return Value: + // - the equivalent ViewportRow. + static constexpr const ViewportRow _screenInfoRowToViewportRow(const ScreenInfoRow row, + const SMALL_RECT viewport) noexcept + { + return row - viewport.Top; + } - static const bool _isScreenInfoRowInViewport(const ScreenInfoRow row); + static const bool _isScreenInfoRowInViewport(gsl::not_null pData, + const ScreenInfoRow row) noexcept; static const bool _isScreenInfoRowInViewport(const ScreenInfoRow row, - const SMALL_RECT viewport); + const SMALL_RECT viewport) noexcept; - static const unsigned int _getViewportHeight(const SMALL_RECT viewport); - static const unsigned int _getViewportWidth(const SMALL_RECT viewport); + static const unsigned int _getViewportHeight(const SMALL_RECT viewport) noexcept; + static const unsigned int _getViewportWidth(const SMALL_RECT viewport) noexcept; - void _addScreenInfoRowBoundaries(const ScreenInfoRow screenInfoRow, + void _addScreenInfoRowBoundaries(gsl::not_null pData, + const ScreenInfoRow screenInfoRow, _Inout_ std::vector& coords) const; - static const int _compareScreenCoords(const ScreenInfoRow rowA, + static const int _compareScreenCoords(gsl::not_null pData, + const ScreenInfoRow rowA, const Column colA, const ScreenInfoRow rowB, const Column colB); - static std::pair _moveByCharacter(const int moveCount, + static std::pair _moveByCharacter(gsl::not_null pData, + const int moveCount, const MoveState moveState, - _Out_ int* const pAmountMoved); + _Out_ gsl::not_null const pAmountMoved); - static std::pair _moveByCharacterForward(const int moveCount, + static std::pair _moveByCharacterForward(gsl::not_null pData, + const int moveCount, const MoveState moveState, - _Out_ int* const pAmountMoved); + _Out_ gsl::not_null const pAmountMoved); - static std::pair _moveByCharacterBackward(const int moveCount, + static std::pair _moveByCharacterBackward(gsl::not_null pData, + const int moveCount, const MoveState moveState, - _Out_ int* const pAmountMoved); + _Out_ gsl::not_null const pAmountMoved); - static std::pair _moveByLine(const int moveCount, + static std::pair _moveByLine(gsl::not_null pData, + const int moveCount, const MoveState moveState, - _Out_ int* const pAmountMoved); + _Out_ gsl::not_null const pAmountMoved); - static std::pair _moveByDocument(const int moveCount, + static std::pair _moveByDocument(gsl::not_null pData, + const int moveCount, const MoveState moveState, - _Out_ int* const pAmountMoved); + _Out_ gsl::not_null const pAmountMoved); static std::tuple - _moveEndpointByUnitCharacter(const int moveCount, + _moveEndpointByUnitCharacter(gsl::not_null pData, + const int moveCount, const TextPatternRangeEndpoint endpoint, const MoveState moveState, - _Out_ int* const pAmountMoved); + _Out_ gsl::not_null const pAmountMoved); static std::tuple - _moveEndpointByUnitCharacterForward(const int moveCount, + _moveEndpointByUnitCharacterForward(gsl::not_null pData, + const int moveCount, const TextPatternRangeEndpoint endpoint, const MoveState moveState, - _Out_ int* const pAmountMoved); + _Out_ gsl::not_null const pAmountMoved); static std::tuple - _moveEndpointByUnitCharacterBackward(const int moveCount, + _moveEndpointByUnitCharacterBackward(gsl::not_null pData, + const int moveCount, const TextPatternRangeEndpoint endpoint, const MoveState moveState, - _Out_ int* const pAmountMoved); + _Out_ gsl::not_null const pAmountMoved); static std::tuple - _moveEndpointByUnitLine(const int moveCount, + _moveEndpointByUnitLine(gsl::not_null pData, + const int moveCount, const TextPatternRangeEndpoint endpoint, const MoveState moveState, - _Out_ int* const pAmountMoved); + _Out_ gsl::not_null const pAmountMoved); static std::tuple - _moveEndpointByUnitDocument(const int moveCount, + _moveEndpointByUnitDocument(gsl::not_null pData, + const int moveCount, const TextPatternRangeEndpoint endpoint, const MoveState moveState, - _Out_ int* const pAmountMoved); + _Out_ gsl::not_null const pAmountMoved); #ifdef UNIT_TESTING friend class ::UiaTextRangeTests; #endif }; - namespace UiaTextRangeTracing + namespace UiaTextRangeBaseTracing { enum class ApiCall { diff --git a/src/types/Utf16Parser.cpp b/src/types/Utf16Parser.cpp index 2090af62312..777388c6406 100644 --- a/src/types/Utf16Parser.cpp +++ b/src/types/Utf16Parser.cpp @@ -14,7 +14,7 @@ // - wstr - The UTF-16 string to parse. // Return Value: // - A view into the string given of just the next codepoint unit. -std::wstring_view Utf16Parser::ParseNext(std::wstring_view wstr) +std::wstring_view Utf16Parser::ParseNext(std::wstring_view wstr) noexcept { for (size_t pos = 0; pos < wstr.size(); ++pos) { diff --git a/src/types/WindowUiaProviderBase.cpp b/src/types/WindowUiaProviderBase.cpp new file mode 100644 index 00000000000..63735e34b6a --- /dev/null +++ b/src/types/WindowUiaProviderBase.cpp @@ -0,0 +1,247 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" + +#include "IUiaWindow.h" +#include "WindowUiaProviderBase.hpp" + +using namespace Microsoft::Console::Types; + +WindowUiaProviderBase::WindowUiaProviderBase(IUiaWindow* baseWindow) : + _signalEventFiring{}, + _baseWindow{ baseWindow }, + _cRefs(1) +{ +} + +#pragma region IUnknown + +IFACEMETHODIMP_(ULONG) +WindowUiaProviderBase::AddRef() +{ + return InterlockedIncrement(&_cRefs); +} + +IFACEMETHODIMP_(ULONG) +WindowUiaProviderBase::Release() +{ + const long val = InterlockedDecrement(&_cRefs); + if (val == 0) + { + delete this; + } + return val; +} + +IFACEMETHODIMP WindowUiaProviderBase::QueryInterface(_In_ REFIID riid, _COM_Outptr_result_maybenull_ void** ppInterface) +{ + RETURN_HR_IF_NULL(E_INVALIDARG, ppInterface); + if (riid == __uuidof(IUnknown) || + riid == __uuidof(IRawElementProviderSimple) || + riid == __uuidof(IRawElementProviderFragment) || + riid == __uuidof(IRawElementProviderFragmentRoot)) + { + *ppInterface = this; + } + else + { + *ppInterface = nullptr; + return E_NOINTERFACE; + } + + AddRef(); + + return S_OK; +} + +#pragma endregion + +#pragma region IRawElementProviderSimple + +// Implementation of IRawElementProviderSimple::get_ProviderOptions. +// Gets UI Automation provider options. +IFACEMETHODIMP WindowUiaProviderBase::get_ProviderOptions(_Out_ ProviderOptions* pOptions) +{ + RETURN_HR_IF_NULL(E_INVALIDARG, pOptions); + RETURN_IF_FAILED(_EnsureValidHwnd()); + + *pOptions = ProviderOptions_ServerSideProvider; + return S_OK; +} + +// Implementation of IRawElementProviderSimple::get_PatternProvider. +// Gets the object that supports ISelectionPattern. +IFACEMETHODIMP WindowUiaProviderBase::GetPatternProvider(_In_ PATTERNID /*patternId*/, + _COM_Outptr_result_maybenull_ IUnknown** ppInterface) +{ + RETURN_HR_IF_NULL(E_INVALIDARG, ppInterface); + *ppInterface = nullptr; + RETURN_IF_FAILED(_EnsureValidHwnd()); + + return S_OK; +} + +// Implementation of IRawElementProviderSimple::get_PropertyValue. +// Gets custom properties. +IFACEMETHODIMP WindowUiaProviderBase::GetPropertyValue(_In_ PROPERTYID propertyId, _Out_ VARIANT* pVariant) +{ + RETURN_HR_IF_NULL(E_INVALIDARG, pVariant); + RETURN_IF_FAILED(_EnsureValidHwnd()); + + pVariant->vt = VT_EMPTY; + + // Returning the default will leave the property as the default + // so we only really need to touch it for the properties we want to implement + if (propertyId == UIA_ControlTypePropertyId) + { + pVariant->vt = VT_I4; + pVariant->lVal = UIA_WindowControlTypeId; + } + else if (propertyId == UIA_AutomationIdPropertyId) + { + pVariant->bstrVal = SysAllocString(AutomationIdPropertyName); + if (pVariant->bstrVal != nullptr) + { + pVariant->vt = VT_BSTR; + } + } + else if (propertyId == UIA_IsControlElementPropertyId) + { + pVariant->vt = VT_BOOL; + pVariant->boolVal = VARIANT_TRUE; + } + else if (propertyId == UIA_IsContentElementPropertyId) + { + pVariant->vt = VT_BOOL; + pVariant->boolVal = VARIANT_TRUE; + } + else if (propertyId == UIA_IsKeyboardFocusablePropertyId) + { + pVariant->vt = VT_BOOL; + pVariant->boolVal = VARIANT_TRUE; + } + else if (propertyId == UIA_HasKeyboardFocusPropertyId) + { + pVariant->vt = VT_BOOL; + pVariant->boolVal = VARIANT_TRUE; + } + else if (propertyId == UIA_ProviderDescriptionPropertyId) + { + pVariant->bstrVal = SysAllocString(ProviderDescriptionPropertyName); + if (pVariant->bstrVal != nullptr) + { + pVariant->vt = VT_BSTR; + } + } + + return S_OK; +} + +// Implementation of IRawElementProviderSimple::get_HostRawElementProvider. +// Gets the default UI Automation provider for the host window. This provider +// supplies many properties. +IFACEMETHODIMP WindowUiaProviderBase::get_HostRawElementProvider(_COM_Outptr_result_maybenull_ IRawElementProviderSimple** ppProvider) +{ + RETURN_HR_IF_NULL(E_INVALIDARG, ppProvider); + try + { + const HWND hwnd = GetWindowHandle(); + return UiaHostProviderFromHwnd(hwnd, ppProvider); + } + catch (...) + { + return gsl::narrow_cast(UIA_E_ELEMENTNOTAVAILABLE); + } +} +#pragma endregion + +#pragma region IRawElementProviderFragment + +IFACEMETHODIMP WindowUiaProviderBase::GetRuntimeId(_Outptr_result_maybenull_ SAFEARRAY** ppRuntimeId) +{ + RETURN_HR_IF_NULL(E_INVALIDARG, ppRuntimeId); + RETURN_IF_FAILED(_EnsureValidHwnd()); + // Root defers this to host, others must implement it... + *ppRuntimeId = nullptr; + + return S_OK; +} + +IFACEMETHODIMP WindowUiaProviderBase::get_BoundingRectangle(_Out_ UiaRect* pRect) +{ + RETURN_HR_IF_NULL(E_INVALIDARG, pRect); + RETURN_IF_FAILED(_EnsureValidHwnd()); + + const IUiaWindow* const pConsoleWindow = _baseWindow; + RETURN_HR_IF_NULL((HRESULT)UIA_E_ELEMENTNOTAVAILABLE, pConsoleWindow); + + RECT const rc = pConsoleWindow->GetWindowRect(); + + pRect->left = rc.left; + pRect->top = rc.top; + + LONG longWidth = 0; + RETURN_IF_FAILED(LongSub(rc.right, rc.left, &longWidth)); + pRect->width = longWidth; + LONG longHeight = 0; + RETURN_IF_FAILED(LongSub(rc.bottom, rc.top, &longHeight)); + pRect->height = longHeight; + + return S_OK; +} + +IFACEMETHODIMP WindowUiaProviderBase::GetEmbeddedFragmentRoots(_Outptr_result_maybenull_ SAFEARRAY** ppRoots) +{ + RETURN_HR_IF_NULL(E_INVALIDARG, ppRoots); + RETURN_IF_FAILED(_EnsureValidHwnd()); + + *ppRoots = nullptr; + return S_OK; +} + +IFACEMETHODIMP WindowUiaProviderBase::get_FragmentRoot(_COM_Outptr_result_maybenull_ IRawElementProviderFragmentRoot** ppProvider) +{ + RETURN_HR_IF_NULL(E_INVALIDARG, ppProvider); + RETURN_IF_FAILED(_EnsureValidHwnd()); + + *ppProvider = this; + AddRef(); + return S_OK; +} + +#pragma endregion + +HWND WindowUiaProviderBase::GetWindowHandle() const +{ + const IUiaWindow* const pConsoleWindow = _baseWindow; + if (pConsoleWindow) + { + return pConsoleWindow->GetWindowHandle(); + } + else + { + return nullptr; + } +} + +[[nodiscard]] HRESULT WindowUiaProviderBase::_EnsureValidHwnd() const +{ + try + { + HWND const hwnd = GetWindowHandle(); + RETURN_HR_IF((HRESULT)UIA_E_ELEMENTNOTAVAILABLE, !(IsWindow(hwnd))); + } + CATCH_RETURN(); + return S_OK; +} + +void WindowUiaProviderBase::ChangeViewport(const SMALL_RECT NewWindow) +{ + _baseWindow->ChangeViewport(NewWindow); +} + +RECT WindowUiaProviderBase::GetWindowRect() const noexcept +{ + return _baseWindow->GetWindowRect(); +} diff --git a/src/types/WindowUiaProviderBase.hpp b/src/types/WindowUiaProviderBase.hpp new file mode 100644 index 00000000000..3cd68b1a2fa --- /dev/null +++ b/src/types/WindowUiaProviderBase.hpp @@ -0,0 +1,148 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- WindowUiaProviderBase.hpp + +Abstract: +- This module provides UI Automation access to the console window to + support both automation tests and accessibility (screen reading) + applications. +- Based on examples, sample code, and guidance from + https://msdn.microsoft.com/en-us/library/windows/desktop/ee671596(v=vs.85).aspx + +Author(s): +- Michael Niksa (MiNiksa) 2017 +- Austin Diviness (AustDi) 2017 +- Carlos Zamora (cazamor) 2019 +--*/ + +#pragma once + +#include "precomp.h" + +namespace Microsoft::Console::Types +{ + class IUiaWindow; + + class WindowUiaProviderBase : + public IRawElementProviderSimple, + public IRawElementProviderFragment, + public IRawElementProviderFragmentRoot + { + public: + virtual ~WindowUiaProviderBase() = default; + + protected: + WindowUiaProviderBase() = default; + WindowUiaProviderBase(const WindowUiaProviderBase&) = default; + WindowUiaProviderBase(WindowUiaProviderBase&&) = default; + WindowUiaProviderBase& operator=(const WindowUiaProviderBase&) = default; + WindowUiaProviderBase& operator=(WindowUiaProviderBase&&) = default; + + public: + [[nodiscard]] virtual HRESULT Signal(_In_ EVENTID id) = 0; + [[nodiscard]] virtual HRESULT SetTextAreaFocus() = 0; + + // IUnknown methods + IFACEMETHODIMP_(ULONG) + AddRef() override; + IFACEMETHODIMP_(ULONG) + Release() override; + IFACEMETHODIMP QueryInterface(_In_ REFIID riid, + _COM_Outptr_result_maybenull_ void** ppInterface) override; + + // IRawElementProviderSimple methods + IFACEMETHODIMP get_ProviderOptions(_Out_ ProviderOptions* pOptions) override; + IFACEMETHODIMP GetPatternProvider(_In_ PATTERNID iid, + _COM_Outptr_result_maybenull_ IUnknown** ppInterface) override; + IFACEMETHODIMP GetPropertyValue(_In_ PROPERTYID idProp, + _Out_ VARIANT* pVariant) override; + IFACEMETHODIMP get_HostRawElementProvider(_COM_Outptr_result_maybenull_ IRawElementProviderSimple** ppProvider) override; + + // IRawElementProviderFragment methods + virtual IFACEMETHODIMP Navigate(_In_ NavigateDirection direction, + _COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider) = 0; + IFACEMETHODIMP GetRuntimeId(_Outptr_result_maybenull_ SAFEARRAY** ppRuntimeId) override; + IFACEMETHODIMP get_BoundingRectangle(_Out_ UiaRect* pRect) override; + IFACEMETHODIMP GetEmbeddedFragmentRoots(_Outptr_result_maybenull_ SAFEARRAY** ppRoots) override; + virtual IFACEMETHODIMP SetFocus() = 0; + IFACEMETHODIMP get_FragmentRoot(_COM_Outptr_result_maybenull_ IRawElementProviderFragmentRoot** ppProvider) override; + + // IRawElementProviderFragmentRoot methods + virtual IFACEMETHODIMP ElementProviderFromPoint(_In_ double x, + _In_ double y, + _COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider) = 0; + virtual IFACEMETHODIMP GetFocus(_COM_Outptr_result_maybenull_ IRawElementProviderFragment** ppProvider) = 0; + + WindowUiaProviderBase(IUiaWindow* baseWindow); + + RECT GetWindowRect() const noexcept; + HWND GetWindowHandle() const; + void ChangeViewport(const SMALL_RECT NewWindow); + + protected: + // this is used to prevent the object from + // signaling an event while it is already in the + // process of signalling another event. + // This fixes a problem with JAWS where it would + // call a public method that calls + // UiaRaiseAutomationEvent to signal something + // happened, which JAWS then detects the signal + // and calls the same method in response, + // eventually overflowing the stack. + // We aren't using this as a cheap locking + // mechanism for multi-threaded code. + std::map _signalEventFiring; + + [[nodiscard]] HRESULT _EnsureValidHwnd() const; + + const OLECHAR* AutomationIdPropertyName = L"Console Window"; + const OLECHAR* ProviderDescriptionPropertyName = L"Microsoft Console Host Window"; + + private: + // Ref counter for COM object + ULONG _cRefs; + + IUiaWindow* _baseWindow; + }; + + namespace WindowUiaProviderTracing + { + enum class ApiCall + { + Create, + Signal, + AddRef, + Release, + QueryInterface, + GetProviderOptions, + GetPatternProvider, + GetPropertyValue, + GetHostRawElementProvider, + Navigate, + GetRuntimeId, + GetBoundingRectangle, + GetEmbeddedFragmentRoots, + SetFocus, + GetFragmentRoot, + ElementProviderFromPoint, + GetFocus + }; + + struct IApiMsg + { + }; + + struct ApiMessageSignal : public IApiMsg + { + EVENTID Signal; + }; + + struct ApiMsgNavigate : public IApiMsg + { + NavigateDirection Direction; + }; + } +} diff --git a/src/types/convert.cpp b/src/types/convert.cpp index 7ca12962c98..c2049ded918 100644 --- a/src/types/convert.cpp +++ b/src/types/convert.cpp @@ -44,15 +44,15 @@ static const WORD leftShiftScanCode = 0x2A; size_t cchNeeded; THROW_IF_FAILED(IntToSizeT(iTarget, &cchNeeded)); - // Allocate ourselves space in a smart pointer. - std::unique_ptr pwsOut = std::make_unique(cchNeeded); - THROW_IF_NULL_ALLOC(pwsOut); + // Allocate ourselves some space + std::wstring out; + out.resize(cchNeeded); // Attempt conversion for real. - THROW_LAST_ERROR_IF(0 == MultiByteToWideChar(codePage, 0, source.data(), iSource, pwsOut.get(), iTarget)); + THROW_LAST_ERROR_IF(0 == MultiByteToWideChar(codePage, 0, source.data(), iSource, out.data(), iTarget)); // Return as a string - return std::wstring(pwsOut.get(), cchNeeded); + return out; } // Routine Description: @@ -85,18 +85,18 @@ static const WORD leftShiftScanCode = 0x2A; size_t cchNeeded; THROW_IF_FAILED(IntToSizeT(iTarget, &cchNeeded)); - // Allocate ourselves space in a smart pointer - std::unique_ptr psOut = std::make_unique(cchNeeded); - THROW_IF_NULL_ALLOC(psOut.get()); + // Allocate ourselves some space + std::string out; + out.resize(cchNeeded); // Attempt conversion for real. // clang-format off #pragma prefast(suppress: __WARNING_W2A_BEST_FIT, "WC_NO_BEST_FIT_CHARS doesn't work in many codepages. Retain old behavior.") // clang-format on - THROW_LAST_ERROR_IF(0 == WideCharToMultiByte(codepage, 0, source.data(), iSource, psOut.get(), iTarget, nullptr, nullptr)); + THROW_LAST_ERROR_IF(0 == WideCharToMultiByte(codepage, 0, source.data(), iSource, out.data(), iTarget, nullptr, nullptr)); // Return as a string - return std::string(psOut.get(), cchNeeded); + return out; } // Routine Description: @@ -284,7 +284,7 @@ std::deque> SynthesizeNumpadEvents(const wchar_t wch, // But it is absolutely valid as 0xFF or 255 unsigned as the correct CP437 character. // We need to treat it as unsigned because we're going to pretend it was a keypad entry // and you don't enter negative numbers on the keypad. - unsigned char const uch = static_cast(convertedChars[0]); + unsigned char const uch = static_cast(convertedChars.at(0)); // unsigned char values are in the range [0, 255] so we need to be // able to store up to 4 chars from the conversion (including the end of string char) diff --git a/src/types/inc/CodepointWidthDetector.hpp b/src/types/inc/CodepointWidthDetector.hpp index 9055063f310..0dc7ac8b966 100644 --- a/src/types/inc/CodepointWidthDetector.hpp +++ b/src/types/inc/CodepointWidthDetector.hpp @@ -22,87 +22,15 @@ static_assert(sizeof(unsigned int) == sizeof(wchar_t) * 2, // use to measure the width of a codepoint class CodepointWidthDetector final { -protected: - // used to store range data in CodepointWidthDetector's internal map - class UnicodeRange final - { - public: - UnicodeRange(const unsigned int lowerBound, - const unsigned int upperBound) : - _lowerBound{ lowerBound }, - _upperBound{ upperBound }, - _isBounds{ true } - { - } - - UnicodeRange(const unsigned int searchTerm) : - _lowerBound{ searchTerm }, - _upperBound{ searchTerm }, - _isBounds{ false } - { - } - - bool IsBounds() const noexcept - { - return _isBounds; - } - - unsigned int LowerBound() const - { - FAIL_FAST_IF(!_isBounds); - return _lowerBound; - } - - unsigned int UpperBound() const - { - FAIL_FAST_IF(!_isBounds); - return _upperBound; - } - - unsigned int SearchTerm() const - { - FAIL_FAST_IF(_isBounds); - return _lowerBound; - } - - private: - unsigned int _lowerBound; - unsigned int _upperBound; - bool _isBounds; - }; - - // used for comparing if we've found the range that a searching UnicodeRange falls into - struct UnicodeRangeCompare final - { - bool operator()(const UnicodeRange& a, const UnicodeRange& b) const - { - if (!a.IsBounds() && b.IsBounds()) - { - return a.SearchTerm() < b.LowerBound(); - } - else if (a.IsBounds() && !b.IsBounds()) - { - return a.UpperBound() < b.SearchTerm(); - } - else if (a.IsBounds() && b.IsBounds()) - { - return a.LowerBound() < b.LowerBound(); - } - else - { - return a.SearchTerm() < b.SearchTerm(); - } - } - }; - public: - CodepointWidthDetector() = default; + CodepointWidthDetector() noexcept; CodepointWidthDetector(const CodepointWidthDetector&) = delete; CodepointWidthDetector(CodepointWidthDetector&&) = delete; ~CodepointWidthDetector() = default; CodepointWidthDetector& operator=(const CodepointWidthDetector&) = delete; + CodepointWidthDetector& operator=(CodepointWidthDetector&&) = delete; - CodepointWidth GetWidth(const std::wstring_view glyph) const noexcept; + CodepointWidth GetWidth(const std::wstring_view glyph) const; bool IsWide(const std::wstring_view glyph) const; bool IsWide(const wchar_t wch) const noexcept; void SetFallbackMethod(std::function pfnFallback); @@ -115,11 +43,8 @@ class CodepointWidthDetector final private: bool _lookupIsWide(const std::wstring_view glyph) const noexcept; bool _checkFallbackViaCache(const std::wstring_view glyph) const; - unsigned int _extractCodepoint(const std::wstring_view glyph) const noexcept; - void _populateUnicodeSearchMap(); + static unsigned int _extractCodepoint(const std::wstring_view glyph) noexcept; mutable std::map _fallbackCache; - std::map _map; std::function _pfnFallbackMethod; - bool _hasFallback = false; }; diff --git a/src/types/inc/GlyphWidth.hpp b/src/types/inc/GlyphWidth.hpp index e12261a6f83..7888915bebd 100644 --- a/src/types/inc/GlyphWidth.hpp +++ b/src/types/inc/GlyphWidth.hpp @@ -13,6 +13,6 @@ Module Name: #include bool IsGlyphFullWidth(const std::wstring_view glyph); -bool IsGlyphFullWidth(const wchar_t wch); +bool IsGlyphFullWidth(const wchar_t wch) noexcept; void SetGlyphWidthFallback(std::function pfnFallback); -void NotifyGlyphWidthFontChanged(); +void NotifyGlyphWidthFontChanged() noexcept; diff --git a/src/types/inc/IInputEvent.hpp b/src/types/inc/IInputEvent.hpp index ded7c903b7b..486971f82e6 100644 --- a/src/types/inc/IInputEvent.hpp +++ b/src/types/inc/IInputEvent.hpp @@ -245,7 +245,7 @@ class KeyEvent : public IInputEvent void SetActiveModifierKeys(const DWORD activeModifierKeys) noexcept; void DeactivateModifierKey(const ModifierKeyState modifierKey) noexcept; void ActivateModifierKey(const ModifierKeyState modifierKey) noexcept; - bool DoActiveModifierKeysMatch(const std::unordered_set& consoleModifiers) const noexcept; + bool DoActiveModifierKeysMatch(const std::unordered_set& consoleModifiers) const; bool IsCommandLineEditingKey() const noexcept; bool IsPopupKey() const noexcept; diff --git a/src/types/inc/UTF8OutPipeReader.hpp b/src/types/inc/UTF8OutPipeReader.hpp index 4383ad0366a..93c1b5975ff 100644 --- a/src/types/inc/UTF8OutPipeReader.hpp +++ b/src/types/inc/UTF8OutPipeReader.hpp @@ -27,7 +27,7 @@ Author(s): class UTF8OutPipeReader final { public: - UTF8OutPipeReader(HANDLE outPipe); + UTF8OutPipeReader(HANDLE outPipe) noexcept; [[nodiscard]] HRESULT Read(_Out_ std::string_view& strView); private: @@ -46,7 +46,7 @@ class UTF8OutPipeReader final }; // array of bitmasks - constexpr const static BYTE _cmpMasks[]{ + constexpr static std::array _cmpMasks{ 0, // unused _Utf8BitMasks::MaskContinuationByte, _Utf8BitMasks::MaskLeadByteTwoByteSequence, @@ -54,7 +54,7 @@ class UTF8OutPipeReader final }; // array of values for the comparisons - constexpr const static BYTE _cmpOperands[]{ + constexpr static std::array _cmpOperands{ 0, // unused _Utf8BitMasks::IsAsciiByte, // intentionally conflicts with MaskContinuationByte _Utf8BitMasks::IsLeadByteTwoByteSequence, @@ -62,7 +62,7 @@ class UTF8OutPipeReader final }; HANDLE _outPipe; // non-owning reference to a pipe. - BYTE _buffer[4096]{ 0 }; // buffer for the chunk read - BYTE _utf8Partials[4]{ 0 }; // buffer for code units of a partial UTF-8 code point that have to be cached + std::array _buffer; // buffer for the chunk read. + std::array _utf8Partials; // buffer for code units of a partial UTF-8 code point that have to be cached DWORD _dwPartialsLen{}; // number of cached UTF-8 code units }; diff --git a/src/types/inc/Utf16Parser.hpp b/src/types/inc/Utf16Parser.hpp index d093b3396ea..f48bdc0cda2 100644 --- a/src/types/inc/Utf16Parser.hpp +++ b/src/types/inc/Utf16Parser.hpp @@ -28,7 +28,7 @@ class Utf16Parser final public: static std::vector> Parse(std::wstring_view wstr); - static std::wstring_view ParseNext(std::wstring_view wstr); + static std::wstring_view ParseNext(std::wstring_view wstr) noexcept; // Routine Description: // - checks if wchar is a utf16 leading surrogate diff --git a/src/types/inc/utils.hpp b/src/types/inc/utils.hpp index 8d3cd50cc87..ff1c8dd1781 100644 --- a/src/types/inc/utils.hpp +++ b/src/types/inc/utils.hpp @@ -15,7 +15,19 @@ namespace Microsoft::Console::Utils { bool IsValidHandle(const HANDLE handle) noexcept; - short ClampToShortMax(const long value, const short min); + // Function Description: + // - Clamps a long in between `min` and `SHRT_MAX` + // Arguments: + // - value: the value to clamp + // - min: the minimum value to clamp to + // Return Value: + // - The clamped value as a short. + constexpr short ClampToShortMax(const long value, const short min) noexcept + { + return static_cast(std::clamp(value, + static_cast(min), + static_cast(SHRT_MAX))); + } std::wstring GuidToString(const GUID guid); GUID GuidFromString(const std::wstring wstr); @@ -24,11 +36,26 @@ namespace Microsoft::Console::Utils std::string ColorToHexString(const COLORREF color); COLORREF ColorFromHexString(const std::string wstr); - void InitializeCampbellColorTable(gsl::span& table); - void InitializeCampbellColorTableForConhost(gsl::span& table); - void SwapANSIColorOrderForConhost(gsl::span& table); - void Initialize256ColorTable(gsl::span& table); - void SetColorTableAlpha(gsl::span& table, const BYTE newAlpha); + void InitializeCampbellColorTable(const gsl::span table); + void InitializeCampbellColorTableForConhost(const gsl::span table); + void SwapANSIColorOrderForConhost(const gsl::span table); + void Initialize256ColorTable(const gsl::span table); + + // Function Description: + // - Fill the alpha byte of the colors in a given color table with the given value. + // Arguments: + // - table: a color table + // - newAlpha: the new value to use as the alpha for all the entries in that table. + // Return Value: + // - + constexpr void SetColorTableAlpha(const gsl::span table, const BYTE newAlpha) noexcept + { + const auto shiftedAlpha = newAlpha << 24; + for (auto& color : table) + { + WI_UpdateFlagsInMask(color, 0xff000000, shiftedAlpha); + } + } constexpr uint16_t EndianSwap(uint16_t value) { @@ -46,7 +73,7 @@ namespace Microsoft::Console::Utils constexpr unsigned long EndianSwap(unsigned long value) { - return static_cast(EndianSwap(static_cast(value))); + return gsl::narrow_cast(EndianSwap(gsl::narrow_cast(value))); } constexpr GUID EndianSwap(GUID value) @@ -57,5 +84,5 @@ namespace Microsoft::Console::Utils return value; } - GUID CreateV5Uuid(const GUID& namespaceGuid, const gsl::span& name); + GUID CreateV5Uuid(const GUID& namespaceGuid, const gsl::span name); } diff --git a/src/types/inc/viewport.hpp b/src/types/inc/viewport.hpp index d9d8d5c2439..25f5b9d7f66 100644 --- a/src/types/inc/viewport.hpp +++ b/src/types/inc/viewport.hpp @@ -59,7 +59,7 @@ namespace Microsoft::Console::Types bool IsInBounds(const COORD& pos) const noexcept; void Clamp(COORD& pos) const; - Viewport Clamp(const Viewport& other) const; + Viewport Clamp(const Viewport& other) const noexcept; bool MoveInBounds(const ptrdiff_t move, COORD& pos) const noexcept; bool IncrementInBounds(COORD& pos) const noexcept; diff --git a/src/types/lib/types.vcxproj b/src/types/lib/types.vcxproj index 1233c952312..b5c48480eff 100644 --- a/src/types/lib/types.vcxproj +++ b/src/types/lib/types.vcxproj @@ -1,4 +1,4 @@ - +ο»Ώ @@ -11,6 +11,8 @@ + + @@ -19,8 +21,11 @@ Create + + + @@ -28,8 +33,13 @@ + + + + + {18D09A24-8240-42D6-8CB6-236EEE820263} diff --git a/src/types/lib/types.vcxproj.filters b/src/types/lib/types.vcxproj.filters index ec5098ad2b7..60c1dfbde5c 100644 --- a/src/types/lib/types.vcxproj.filters +++ b/src/types/lib/types.vcxproj.filters @@ -60,6 +60,18 @@ Source Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + @@ -89,8 +101,68 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + - \ No newline at end of file + diff --git a/src/types/precomp.h b/src/types/precomp.h index 972fde4ea73..b8b77ae4d6c 100644 --- a/src/types/precomp.h +++ b/src/types/precomp.h @@ -29,39 +29,14 @@ Module Name: // Windows Header Files: #include +#include +#include #include #include // This includes support libraries from the CRT, STL, WIL, and GSL #include "LibraryIncludes.h" -typedef long NTSTATUS; -#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) -#define STATUS_SHARING_VIOLATION ((NTSTATUS)0xC0000043L) -#define STATUS_INSUFFICIENT_RESOURCES ((DWORD)0xC000009AL) -#define STATUS_ILLEGAL_FUNCTION ((DWORD)0xC00000AFL) -#define STATUS_PIPE_DISCONNECTED ((DWORD)0xC00000B0L) -#define STATUS_BUFFER_TOO_SMALL ((DWORD)0xC0000023L) - -// -// Map a WIN32 error value into an NTSTATUS -// Note: This assumes that WIN32 errors fall in the range -32k to 32k. -// - -#define FACILITY_NTWIN32 0x7 - -#define __NTSTATUS_FROM_WIN32(x) ((NTSTATUS)(x) <= 0 ? ((NTSTATUS)(x)) : ((NTSTATUS) (((x) & 0x0000FFFF) | (FACILITY_NTWIN32 << 16) | ERROR_SEVERITY_ERROR))) - -#ifdef INLINE_NTSTATUS_FROM_WIN32 -#ifndef __midl -__inline NTSTATUS_FROM_WIN32(long x) { return x <= 0 ? (NTSTATUS)x : (NTSTATUS)(((x) & 0x0000FFFF) | (FACILITY_NTWIN32 << 16) | ERROR_SEVERITY_ERROR); } -#else -#define NTSTATUS_FROM_WIN32(x) __NTSTATUS_FROM_WIN32(x) -#endif -#else -#define NTSTATUS_FROM_WIN32(x) __NTSTATUS_FROM_WIN32(x) -#endif - #include #pragma prefast(push) #pragma prefast(disable:26071, "Range violation in Intsafe. Not ours.") @@ -81,11 +56,4 @@ __inline NTSTATUS_FROM_WIN32(long x) { return x <= 0 ? (NTSTATUS)x : (NTSTATUS)( #include #include -// TODO: MSFT 9355094 Find a better way of doing this. http://osgvsowi/9355094 -[[nodiscard]] -constexpr NTSTATUS NTSTATUS_FROM_HRESULT(HRESULT hr) noexcept -{ - return NTSTATUS_FROM_WIN32(HRESULT_CODE(hr)); -} - // clang-format on diff --git a/src/types/ut_types/UtilsTests.cpp b/src/types/ut_types/UtilsTests.cpp index 57cab5c0844..06153aee2dd 100644 --- a/src/types/ut_types/UtilsTests.cpp +++ b/src/types/ut_types/UtilsTests.cpp @@ -52,11 +52,11 @@ void UtilsTests::TestSwapColorPalette() std::array consoleTable; gsl::span terminalTableView = { &terminalTable[0], gsl::narrow(terminalTable.size()) }; - gsl::span consoleTableleView = { &consoleTable[0], gsl::narrow(consoleTable.size()) }; + gsl::span consoleTableView = { &consoleTable[0], gsl::narrow(consoleTable.size()) }; // First set up the colors InitializeCampbellColorTable(terminalTableView); - InitializeCampbellColorTableForConhost(consoleTableleView); + InitializeCampbellColorTableForConhost(consoleTableView); VERIFY_ARE_EQUAL(terminalTable[0], consoleTable[0]); VERIFY_ARE_EQUAL(terminalTable[1], consoleTable[4]); diff --git a/src/types/utils.cpp b/src/types/utils.cpp index 8554c00a19b..bfc3ffe6556 100644 --- a/src/types/utils.cpp +++ b/src/types/utils.cpp @@ -6,20 +6,6 @@ using namespace Microsoft::Console; -// Function Description: -// - Clamps a long in between `min` and `SHRT_MAX` -// Arguments: -// - value: the value to clamp -// - min: the minimum value to clamp to -// Return Value: -// - The clamped value as a short. -short Utils::ClampToShortMax(const long value, const short min) -{ - return static_cast(std::clamp(value, - static_cast(min), - static_cast(SHRT_MAX))); -} - // Function Description: // - Creates a String representation of a guid, in the format // "{12345678-ABCD-EF12-3456-7890ABCDEF12}" @@ -29,12 +15,12 @@ short Utils::ClampToShortMax(const long value, const short min) // - a string representation of the GUID. On failure, throws E_INVALIDARG. std::wstring Utils::GuidToString(const GUID guid) { - wchar_t guid_cstr[39]; - const int written = swprintf(guid_cstr, sizeof(guid_cstr), L"{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); + std::array guid_cstr; + const int written = swprintf(guid_cstr.data(), guid_cstr.size(), L"{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); THROW_HR_IF(E_INVALIDARG, written == -1); - return std::wstring(guid_cstr); + return std::wstring(guid_cstr.data(), guid_cstr.size()); } // Method Description: @@ -83,7 +69,7 @@ std::string Utils::ColorToHexString(const COLORREF color) } // Function Description: -// - Parses a color from a string. The string should be in the format "#RRGGBB" +// - Parses a color from a string. The string should be in the format "#RRGGBB" or "#RGB" // Arguments: // - str: a string representation of the COLORREF to parse // Return Value: @@ -91,16 +77,29 @@ std::string Utils::ColorToHexString(const COLORREF color) // the correct format, throws E_INVALIDARG COLORREF Utils::ColorFromHexString(const std::string str) { - THROW_HR_IF(E_INVALIDARG, str.size() < 7 || str.size() >= 8); - THROW_HR_IF(E_INVALIDARG, str[0] != '#'); + THROW_HR_IF(E_INVALIDARG, str.size() != 7 && str.size() != 4); + THROW_HR_IF(E_INVALIDARG, str.at(0) != '#'); - std::string rStr{ &str[1], 2 }; - std::string gStr{ &str[3], 2 }; - std::string bStr{ &str[5], 2 }; + std::string rStr; + std::string gStr; + std::string bStr; - BYTE r = static_cast(std::stoul(rStr, nullptr, 16)); - BYTE g = static_cast(std::stoul(gStr, nullptr, 16)); - BYTE b = static_cast(std::stoul(bStr, nullptr, 16)); + if (str.size() == 4) + { + rStr = std::string(2, str.at(1)); + gStr = std::string(2, str.at(2)); + bStr = std::string(2, str.at(3)); + } + else + { + rStr = std::string(&str.at(1), 2); + gStr = std::string(&str.at(3), 2); + bStr = std::string(&str.at(5), 2); + } + + const BYTE r = gsl::narrow_cast(std::stoul(rStr, nullptr, 16)); + const BYTE g = gsl::narrow_cast(std::stoul(gStr, nullptr, 16)); + const BYTE b = gsl::narrow_cast(std::stoul(bStr, nullptr, 16)); return RGB(r, g, b); } @@ -113,7 +112,7 @@ COLORREF Utils::ColorFromHexString(const std::string str) // - True if non zero and not set to invalid magic value. False otherwise. bool Utils::IsValidHandle(const HANDLE handle) noexcept { - return handle != 0 && handle != INVALID_HANDLE_VALUE; + return handle != nullptr && handle != INVALID_HANDLE_VALUE; } // Function Description: @@ -123,7 +122,7 @@ bool Utils::IsValidHandle(const HANDLE handle) noexcept // - table: a color table with at least 16 entries // Return Value: // - , throws if the table has less that 16 entries -void Utils::InitializeCampbellColorTable(gsl::span& table) +void Utils::InitializeCampbellColorTable(const gsl::span table) { THROW_HR_IF(E_INVALIDARG, table.size() < 16); @@ -154,7 +153,7 @@ void Utils::InitializeCampbellColorTable(gsl::span& table) // - table: a color table with at least 16 entries // Return Value: // - , throws if the table has less that 16 entries -void Utils::InitializeCampbellColorTableForConhost(gsl::span& table) +void Utils::InitializeCampbellColorTableForConhost(const gsl::span table) { THROW_HR_IF(E_INVALIDARG, table.size() < 16); InitializeCampbellColorTable(table); @@ -167,7 +166,7 @@ void Utils::InitializeCampbellColorTableForConhost(gsl::span& table) // - table: a color table with at least 16 entries // Return Value: // - , throws if the table has less that 16 entries -void Utils::SwapANSIColorOrderForConhost(gsl::span& table) +void Utils::SwapANSIColorOrderForConhost(const gsl::span table) { THROW_HR_IF(E_INVALIDARG, table.size() < 16); std::swap(table[1], table[4]); @@ -183,7 +182,7 @@ void Utils::SwapANSIColorOrderForConhost(gsl::span& table) // - table: a color table with at least 256 entries // Return Value: // - , throws if the table has less that 256 entries -void Utils::Initialize256ColorTable(gsl::span& table) +void Utils::Initialize256ColorTable(const gsl::span table) { THROW_HR_IF(E_INVALIDARG, table.size() < 256); @@ -447,22 +446,6 @@ void Utils::Initialize256ColorTable(gsl::span& table) // clang-format on } -// Function Description: -// - Fill the alpha byte of the colors in a given color table with the given value. -// Arguments: -// - table: a color table -// - newAlpha: the new value to use as the alpha for all the entries in that table. -// Return Value: -// - -void Utils::SetColorTableAlpha(gsl::span& table, const BYTE newAlpha) -{ - const auto shiftedAlpha = newAlpha << 24; - for (auto& color : table) - { - WI_UpdateFlagsInMask(color, 0xff000000, shiftedAlpha); - } -} - // Function Description: // - Generate a Version 5 UUID (specified in RFC4122 4.3) // v5 UUIDs are stable given the same namespace and "name". @@ -473,7 +456,7 @@ void Utils::SetColorTableAlpha(gsl::span& table, const BYTE newAlpha) // - name: Bytes comprising the name (in a namespace-specific format) // Return Value: // - a new stable v5 UUID -GUID Utils::CreateV5Uuid(const GUID& namespaceGuid, const gsl::span& name) +GUID Utils::CreateV5Uuid(const GUID& namespaceGuid, const gsl::span name) { // v5 uuid generation happens over values in network byte order, so let's enforce that auto correctEndianNamespaceGuid{ EndianSwap(namespaceGuid) }; @@ -490,8 +473,8 @@ GUID Utils::CreateV5Uuid(const GUID& namespaceGuid, const gsl::span buffer; THROW_IF_NTSTATUS_FAILED(BCryptFinishHash(hash.get(), buffer.data(), gsl::narrow(buffer.size()), 0)); - buffer[6] = (buffer[6] & 0x0F) | 0x50; // set the uuid version to 5 - buffer[8] = (buffer[8] & 0x3F) | 0x80; // set the variant to 2 (RFC4122) + buffer.at(6) = (buffer.at(6) & 0x0F) | 0x50; // set the uuid version to 5 + buffer.at(8) = (buffer.at(8) & 0x3F) | 0x80; // set the variant to 2 (RFC4122) // We're using memcpy here pursuant to N4713 6.7.2/3 [basic.types], // "...the underlying bytes making up the object can be copied into an array diff --git a/src/types/viewport.cpp b/src/types/viewport.cpp index 8484b6536a3..c6772bfdcc6 100644 --- a/src/types/viewport.cpp +++ b/src/types/viewport.cpp @@ -194,7 +194,7 @@ void Viewport::Clamp(COORD& pos) const // - other - Viewport to clamp to the inside of this viewport // Return Value: // - Clamped viewport -Viewport Viewport::Clamp(const Viewport& other) const +Viewport Viewport::Clamp(const Viewport& other) const noexcept { auto clampMe = other.ToInclusive(); @@ -450,7 +450,7 @@ bool Viewport::WalkInBoundsCircular(COORD& pos, const WalkDir dir) const noexcep // if using this same viewport with the `WalkInBounds` methods. COORD Viewport::GetWalkOrigin(const WalkDir dir) const noexcept { - COORD origin; + COORD origin{ 0 }; origin.X = dir.x == XWalk::LeftToRight ? Left() : RightInclusive(); origin.Y = dir.y == YWalk::TopToBottom ? Top() : BottomInclusive(); return origin; @@ -909,7 +909,8 @@ Viewport Viewport::ToOrigin() const noexcept // Just put the original rectangle into the results and return early. result.viewports.at(result.used++) = original; } - else + // If the original rectangle matches the intersection, there is nothing to return. + else if (original != intersection) { // Generate our potential four viewports that represent the region of the original that falls outside of the remove area. // We will bias toward generating wide rectangles over tall rectangles (if possible) so that optimizations that apply @@ -922,8 +923,8 @@ Viewport Viewport::ToOrigin() const noexcept // | | | | // | | | | // | | | | - // | | ======> | intersect | ======> early return of 0x0 Viewport - // | | | | at Original's origin + // | | ======> | intersect | ======> early return of nothing + // | | | | // | | | | // | | | | // |---------removeMe---------| |--------------------------| @@ -992,39 +993,32 @@ Viewport Viewport::ToOrigin() const noexcept // | removeMe | // |---------------| - if (original == intersection) + // We generate these rectangles by the original and intersection points, but some of them might be empty when the intersection + // lines up with the edge of the original. That's OK. That just means that the subtraction didn't leave anything behind. + // We will filter those out below when adding them to the result. + const auto top = Viewport({ original.Left(), original.Top(), original.RightInclusive(), intersection.Top() - 1 }); + const auto bottom = Viewport({ original.Left(), intersection.BottomExclusive(), original.RightInclusive(), original.BottomInclusive() }); + const auto left = Viewport({ original.Left(), intersection.Top(), intersection.Left() - 1, intersection.BottomInclusive() }); + const auto right = Viewport({ intersection.RightExclusive(), intersection.Top(), original.RightInclusive(), intersection.BottomInclusive() }); + + if (top.IsValid()) { - result.viewports.at(result.used++) = Viewport::FromDimensions(original.Origin(), { 0, 0 }); + result.viewports.at(result.used++) = top; } - else - { - // We generate these rectangles by the original and intersection points, but some of them might be empty when the intersection - // lines up with the edge of the original. That's OK. That just means that the subtraction didn't leave anything behind. - // We will filter those out below when adding them to the result. - const auto top = Viewport({ original.Left(), original.Top(), original.RightInclusive(), intersection.Top() - 1 }); - const auto bottom = Viewport({ original.Left(), intersection.BottomExclusive(), original.RightInclusive(), original.BottomInclusive() }); - const auto left = Viewport({ original.Left(), intersection.Top(), intersection.Left() - 1, intersection.BottomInclusive() }); - const auto right = Viewport({ intersection.RightExclusive(), intersection.Top(), original.RightInclusive(), intersection.BottomInclusive() }); - - if (top.IsValid()) - { - result.viewports.at(result.used++) = top; - } - if (bottom.IsValid()) - { - result.viewports.at(result.used++) = bottom; - } + if (bottom.IsValid()) + { + result.viewports.at(result.used++) = bottom; + } - if (left.IsValid()) - { - result.viewports.at(result.used++) = left; - } + if (left.IsValid()) + { + result.viewports.at(result.used++) = left; + } - if (right.IsValid()) - { - result.viewports.at(result.used++) = right; - } + if (right.IsValid()) + { + result.viewports.at(result.used++) = right; } } diff --git a/src/winconpty/device.h b/src/winconpty/device.h new file mode 100644 index 00000000000..61387ab7b3f --- /dev/null +++ b/src/winconpty/device.h @@ -0,0 +1,27 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- device.h + +Abstract: +- This header exists to reduce the differences in winconpty + from the in-box windows source. +- Relies on components from Server to reach into ntdll for NtOpenFile + to get at the NT namespace, which is required to open the console device. +--*/ + +#pragma once + +#include "../server/DeviceHandle.h" + +[[nodiscard]] static inline NTSTATUS CreateClientHandle(PHANDLE Handle, HANDLE ServerHandle, PCWSTR Name, BOOLEAN Inheritable) +{ + return DeviceHandle::CreateClientHandle(Handle, ServerHandle, Name, Inheritable); +} + +[[nodiscard]] static inline NTSTATUS CreateServerHandle(PHANDLE Handle, BOOLEAN Inheritable) +{ + return DeviceHandle::CreateServerHandle(Handle, Inheritable); +} diff --git a/src/winconpty/precomp.cpp b/src/winconpty/precomp.cpp new file mode 100644 index 00000000000..c51e9b31b2f --- /dev/null +++ b/src/winconpty/precomp.cpp @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" diff --git a/src/winconpty/precomp.h b/src/winconpty/precomp.h new file mode 100644 index 00000000000..6bf892eacb4 --- /dev/null +++ b/src/winconpty/precomp.h @@ -0,0 +1,49 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- precomp.h + +Abstract: +- Contains external headers to include in the precompile phase of console build process. +- Avoid including internal project headers. Instead include them only in the classes that need them (helps with test project building). +--*/ + +#pragma once + +// Ignore checked iterators warning from VC compiler. +#define _SCL_SECURE_NO_WARNINGS + +// Block minwindef.h min/max macros to prevent conflict +#define NOMINMAX + +// Define and then undefine WIN32_NO_STATUS because windows.h has no guard to prevent it from double defing certain statuses +// when included with ntstatus.h +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS + +// From ntdef.h, but that can't be included or it'll fight over PROBE_ALIGNMENT and other such arch specific defs +typedef _Return_type_success_(return >= 0) LONG NTSTATUS; +/*lint -save -e624 */ // Don't complain about different typedefs. +typedef NTSTATUS* PNTSTATUS; +/*lint -restore */ // Resume checking for different typedefs. +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) + +// End From ntdef.h + +#define INLINE_NTSTATUS_FROM_WIN32 1 // Must use inline NTSTATUS or it will call the wrapped function twice. +#pragma warning(push) +#pragma warning(disable : 4430) // Must disable 4430 "default int" warning for C++ because ntstatus.h is inflexible SDK definition. +#include +#pragma warning(pop) + +#include + +#include "../host/conddkrefs.h" + +// This includes support libraries from the CRT, STL, WIL, and GSL +#include "LibraryIncludes.h" + +#include diff --git a/src/winconpty/winconpty.cpp b/src/winconpty/winconpty.cpp new file mode 100644 index 00000000000..14a1730da92 --- /dev/null +++ b/src/winconpty/winconpty.cpp @@ -0,0 +1,384 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" + +#include "winconpty.h" + +#ifdef __INSIDE_WINDOWS +#include +#include +// You need kernelbasestaging.h to be able to use wil in libraries consumed by kernelbase.dll +#include +#define RESOURCE_SUPPRESS_STL +#define WIL_SUPPORT_BITOPERATION_PASCAL_NAMES +#include +#else +#include "device.h" +#include +#endif // __INSIDE_WINDOWS + +#pragma warning(push) +#pragma warning(disable : 4273) // inconsistent dll linkage (we are exporting things kernel32 also exports) + +// Function Description: +// - Returns the path to either conhost.exe or the side-by-side OpenConsole, depending on whether this +// module is building with Windows. +// Return Value: +// - A pointer to permanent storage containing the path to the console host. +static wchar_t* _ConsoleHostPath() +{ + // Use the magic of magic statics to only calculate this once. + static wil::unique_process_heap_string consoleHostPath = []() { +#ifdef __INSIDE_WINDOWS + wil::unique_process_heap_string systemDirectory; + wil::GetSystemDirectoryW(systemDirectory); + return wil::str_concat_failfast(L"\\\\?\\", systemDirectory, L"\\conhost.exe"); +#else + // Use the STL only if we're not building in Windows. + std::filesystem::path modulePath{ wil::GetModuleFileNameW(wil::GetModuleInstanceHandle()) }; + modulePath.replace_filename(L"OpenConsole.exe"); + auto modulePathAsString{ modulePath.wstring() }; + return wil::make_process_heap_string_nothrow(modulePathAsString.data(), modulePathAsString.size()); +#endif // __INSIDE_WINDOWS + }(); + return consoleHostPath.get(); +} + +static bool _HandleIsValid(HANDLE h) noexcept +{ + return (h != INVALID_HANDLE_VALUE) && (h != nullptr); +} + +HRESULT _CreatePseudoConsole(const HANDLE hToken, + const COORD size, + const HANDLE hInput, + const HANDLE hOutput, + const DWORD dwFlags, + _Inout_ PseudoConsole* pPty) +{ + if (pPty == NULL) + { + return E_INVALIDARG; + } + if (size.X == 0 || size.Y == 0) + { + return E_INVALIDARG; + } + + wil::unique_handle serverHandle; + RETURN_IF_NTSTATUS_FAILED(CreateServerHandle(serverHandle.addressof(), TRUE)); + + wil::unique_handle signalPipeConhostSide; + wil::unique_handle signalPipeOurSide; + + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(sa); + // Mark inheritable for signal handle when creating. It'll have the same value on the other side. + sa.bInheritHandle = FALSE; + sa.lpSecurityDescriptor = NULL; + + RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(signalPipeConhostSide.addressof(), signalPipeOurSide.addressof(), &sa, 0)); + RETURN_IF_WIN32_BOOL_FALSE(SetHandleInformation(signalPipeConhostSide.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)); + + const wchar_t* pwszFormat = L"%s --headless %s--width %hu --height %hu --signal 0x%x --server 0x%x"; + // This is plenty of space to hold the formatted string + wchar_t cmd[MAX_PATH]; + const BOOL bInheritCursor = (dwFlags & PSEUDOCONSOLE_INHERIT_CURSOR) == PSEUDOCONSOLE_INHERIT_CURSOR; + swprintf_s(cmd, + MAX_PATH, + pwszFormat, + _ConsoleHostPath(), + bInheritCursor ? L"--inheritcursor " : L"", + size.X, + size.Y, + signalPipeConhostSide.get(), + serverHandle.get()); + + STARTUPINFOEXW siEx{ 0 }; + siEx.StartupInfo.cb = sizeof(STARTUPINFOEXW); + siEx.StartupInfo.hStdInput = hInput; + siEx.StartupInfo.hStdOutput = hOutput; + siEx.StartupInfo.hStdError = hOutput; + siEx.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; + + // Only pass the handles we actually want the conhost to know about to it: + const size_t INHERITED_HANDLES_COUNT = 4; + HANDLE inheritedHandles[INHERITED_HANDLES_COUNT]; + inheritedHandles[0] = serverHandle.get(); + inheritedHandles[1] = hInput; + inheritedHandles[2] = hOutput; + inheritedHandles[3] = signalPipeConhostSide.get(); + + // Get the size of the attribute list. We need one attribute, the handle list. + SIZE_T listSize = 0; + InitializeProcThreadAttributeList(NULL, 1, 0, &listSize); + + // I have to use a HeapAlloc here because kernelbase can't link new[] or delete[] + PPROC_THREAD_ATTRIBUTE_LIST attrList = reinterpret_cast(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, listSize)); + RETURN_IF_NULL_ALLOC(attrList); + auto attrListDelete = wil::scope_exit([&] { + HeapFree(GetProcessHeap(), 0, attrList); + }); + + siEx.lpAttributeList = attrList; + RETURN_IF_WIN32_BOOL_FALSE(InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, &listSize)); + // Set cleanup data for ProcThreadAttributeList when successful. + auto cleanupProcThreadAttribute = wil::scope_exit([&] { + DeleteProcThreadAttributeList(siEx.lpAttributeList); + }); + RETURN_IF_WIN32_BOOL_FALSE(UpdateProcThreadAttribute(siEx.lpAttributeList, + 0, + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, + inheritedHandles, + (INHERITED_HANDLES_COUNT * sizeof(HANDLE)), + NULL, + NULL)); + wil::unique_process_information pi; + { // wow64 disabled filesystem redirection scope +#if defined(BUILD_WOW6432) + PVOID RedirectionFlag; + RETURN_IF_NTSTATUS_FAILED(RtlWow64EnableFsRedirectionEx( + WOW64_FILE_SYSTEM_DISABLE_REDIRECT, + &RedirectionFlag)); + auto resetFsRedirection = wil::scope_exit([&] { + RtlWow64EnableFsRedirectionEx(RedirectionFlag, &RedirectionFlag); + }); +#endif + if (hToken == INVALID_HANDLE_VALUE || hToken == NULL) + { + // Call create process + RETURN_IF_WIN32_BOOL_FALSE(CreateProcessW(NULL, + cmd, + NULL, + NULL, + TRUE, + EXTENDED_STARTUPINFO_PRESENT, + NULL, + NULL, + &siEx.StartupInfo, + pi.addressof())); + } + else + { + // Call create process + RETURN_IF_WIN32_BOOL_FALSE(CreateProcessAsUserW(hToken, + NULL, + cmd, + NULL, + NULL, + TRUE, + EXTENDED_STARTUPINFO_PRESENT, + NULL, + NULL, + &siEx.StartupInfo, + pi.addressof())); + } + } + + // Move the process handle out of the PROCESS_INFORMATION into out Pseudoconsole + pPty->hConPtyProcess = pi.hProcess; + pi.hProcess = NULL; + + RETURN_IF_NTSTATUS_FAILED(CreateClientHandle(&pPty->hPtyReference, + serverHandle.get(), + L"\\Reference", + FALSE)); + + pPty->hSignal = signalPipeOurSide.release(); + + return S_OK; +} + +// Function Description: +// - Resizes the conpty +// Arguments: +// - hSignal: A signal pipe as returned by CreateConPty. +// - size: The new dimenstions of the conpty, in characters. +// Return Value: +// - S_OK if the call succeeded, else an appropriate HRESULT for failing to +// write the resize message to the pty. +HRESULT _ResizePseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const COORD size) +{ + if (pPty == NULL) + { + return E_INVALIDARG; + } + + unsigned short signalPacket[3]; + signalPacket[0] = PTY_SIGNAL_RESIZE_WINDOW; + signalPacket[1] = size.X; + signalPacket[2] = size.Y; + + BOOL fSuccess = WriteFile(pPty->hSignal, signalPacket, sizeof(signalPacket), NULL, NULL); + return fSuccess ? S_OK : HRESULT_FROM_WIN32(GetLastError()); +} + +// Function Description: +// - This closes each of the members of a PseudoConsole. It does not free the +// data associated with the PseudoConsole. This is helpful for testing, +// where we might stack allocate a PseudoConsole (instead of getting a +// HPCON via the API). +// Arguments: +// - pPty: A pointer to a PseudoConsole struct. +// Return Value: +// - +void _ClosePseudoConsoleMembers(_In_ PseudoConsole* pPty) +{ + if (pPty != NULL) + { + // See MSFT:19918626 + // First break the signal pipe - this will trigger conhost to tear itself down + if (_HandleIsValid(pPty->hSignal)) + { + CloseHandle(pPty->hSignal); + pPty->hSignal = 0; + } + // Then, wait on the conhost process before killing it. + // We do this to make sure the conhost finishes flushing any output it + // has yet to send before we hard kill it. + if (_HandleIsValid(pPty->hConPtyProcess)) + { + // If the conhost is already dead, then that's fine. Presumably + // it's finished flushing it's output already. + DWORD dwExit = 0; + // If GetExitCodeProcess failed, it's likely conhost is already dead + // If so, skip waiting regardless of whatever error + // GetExitCodeProcess returned. + // We'll just go straight to killing conhost. + if (GetExitCodeProcess(pPty->hConPtyProcess, &dwExit) && dwExit == STILL_ACTIVE) + { + WaitForSingleObject(pPty->hConPtyProcess, INFINITE); + } + + TerminateProcess(pPty->hConPtyProcess, 0); + pPty->hConPtyProcess = 0; + } + // Then take care of the reference handle. + // TODO GH#1810: Closing the reference handle late leaves conhost thinking + // that we have an outstanding connected client. + if (_HandleIsValid(pPty->hPtyReference)) + { + CloseHandle(pPty->hPtyReference); + pPty->hPtyReference = 0; + } + } +} + +// Function Description: +// - This closes each of the members of a PseudoConsole, and HeapFree's the +// memory allocated to it. This should be used to cleanup any +// PseudoConosles that were created with CreatePseudoConsole. +// Arguments: +// - pPty: A pointer to a PseudoConsole struct. +// Return Value: +// - +VOID _ClosePseudoConsole(_In_ PseudoConsole* pPty) +{ + if (pPty != NULL) + { + _ClosePseudoConsoleMembers(pPty); + HeapFree(GetProcessHeap(), 0, pPty); + } +} + +// These functions are defined in the console l1 apiset, which is generated from +// the consoleapi.apx file in minkernel\apiset\libs\Console. + +// Function Description: +// Creates a "Pseudo-console" (conpty) with dimensions (in characters) +// provided by the `size` parameter. The caller should provide two handles: +// - `hInput` is used for writing input to the pty, encoded as UTF-8 and VT sequences. +// - `hOutput` is used for reading the output of the pty, encoded as UTF-8 and VT sequences. +// Once the call completes, `phPty` will receive a token value to identify this +// conpty object. This value should be used in conjunction with the other +// Pseudoconsole API's. +// `dwFlags` is used to specify optional behavior to the created pseudoconsole. +// The flags can be combinations of the following values: +// INHERIT_CURSOR: This will cause the created conpty to attempt to inherit the +// cursor position of the parent terminal application. This can be useful +// for applications like `ssh`, where ssh (currently running in a terminal) +// might want to create a pseudoterminal session for an child application +// and the child inherit the cursor position of ssh. +// The creted conpty will immediately emit a "Device Status Request" VT +// sequence to hOutput, that should be replied to on hInput in the format +// "\x1b[;R", where `` is the row and `` is the column of the +// cursor position. +// This requires a cooperating terminal application - if a caller does not +// reply to this message, the conpty will not process any input until it +// does. Most *nix terminals and the Windows Console (after Windows 10 +// Anniversary Update) will be able to handle such a message. +HRESULT WINAPI CreatePseudoConsole(_In_ COORD size, + _In_ HANDLE hInput, + _In_ HANDLE hOutput, + _In_ DWORD dwFlags, + _Out_ HPCON* phPC) +{ + return CreatePseudoConsoleAsUser(INVALID_HANDLE_VALUE, size, hInput, hOutput, dwFlags, phPC); +} + +HRESULT CreatePseudoConsoleAsUser(_In_ HANDLE hToken, + _In_ COORD size, + _In_ HANDLE hInput, + _In_ HANDLE hOutput, + _In_ DWORD dwFlags, + _Out_ HPCON* phPC) +{ + if (phPC == NULL) + { + return E_INVALIDARG; + } + *phPC = NULL; + if ((!_HandleIsValid(hInput)) && (!_HandleIsValid(hOutput))) + { + return E_INVALIDARG; + } + + PseudoConsole* pPty = (PseudoConsole*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PseudoConsole)); + RETURN_IF_NULL_ALLOC(pPty); + auto cleanupPty = wil::scope_exit([&] { + _ClosePseudoConsole(pPty); + }); + + wil::unique_handle duplicatedInput; + wil::unique_handle duplicatedOutput; + RETURN_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(), hInput, GetCurrentProcess(), duplicatedInput.addressof(), 0, TRUE, DUPLICATE_SAME_ACCESS)); + RETURN_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(), hOutput, GetCurrentProcess(), duplicatedOutput.addressof(), 0, TRUE, DUPLICATE_SAME_ACCESS)); + + RETURN_IF_FAILED(_CreatePseudoConsole(hToken, size, duplicatedInput.get(), duplicatedOutput.get(), dwFlags, pPty)); + + *phPC = (HPCON)pPty; + cleanupPty.release(); + + return S_OK; +} + +// Function Description: +// Resizes the given conpty to the specified size, in characters. +HRESULT WINAPI ResizePseudoConsole(_In_ HPCON hPC, _In_ COORD size) +{ + PseudoConsole* const pPty = (PseudoConsole*)hPC; + HRESULT hr = pPty == NULL ? E_INVALIDARG : S_OK; + if (SUCCEEDED(hr)) + { + hr = _ResizePseudoConsole(pPty, size); + } + return hr; +} + +// Function Description: +// Closes the conpty and all associated state. +// Client applications attached to the conpty will also behave as though the +// console window they were running in was closed. +// This can fail if the conhost hosting the pseudoconsole failed to be +// terminated, or if the pseudoconsole was already terminated. +VOID WINAPI ClosePseudoConsole(_In_ HPCON hPC) +{ + PseudoConsole* const pPty = (PseudoConsole*)hPC; + if (pPty != NULL) + { + _ClosePseudoConsole(pPty); + } +} + +#pragma warning(pop) diff --git a/src/winconpty/winconpty.def b/src/winconpty/winconpty.def new file mode 100644 index 00000000000..51ccf32791b --- /dev/null +++ b/src/winconpty/winconpty.def @@ -0,0 +1,4 @@ +EXPORTS + CreatePseudoConsole + ResizePseudoConsole + ClosePseudoConsole diff --git a/src/winconpty/winconpty.h b/src/winconpty/winconpty.h new file mode 100644 index 00000000000..2cfea5375fd --- /dev/null +++ b/src/winconpty/winconpty.h @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _PseudoConsole +{ + HANDLE hSignal; + HANDLE hPtyReference; + HANDLE hConPtyProcess; +} PseudoConsole; + +// Signals +// These are not defined publicly, but are used for controlling the conpty via +// the signal pipe. +#define PTY_SIGNAL_RESIZE_WINDOW (8u) + +// Implementations of the various PseudoConsole functions. +HRESULT _CreatePseudoConsole(const HANDLE hToken, + const COORD size, + const HANDLE hInput, + const HANDLE hOutput, + const DWORD dwFlags, + _Inout_ PseudoConsole* pPty); + +HRESULT _ResizePseudoConsole(_In_ const PseudoConsole* const pPty, _In_ const COORD size); +void _ClosePseudoConsoleMembers(_In_ PseudoConsole* pPty); +VOID _ClosePseudoConsole(_In_ PseudoConsole* pPty); + +HRESULT CreatePseudoConsoleAsUser(_In_ HANDLE hToken, + _In_ COORD size, + _In_ HANDLE hInput, + _In_ HANDLE hOutput, + _In_ DWORD dwFlags, + _Out_ HPCON* phPC); + +#ifdef __cplusplus +} +#endif diff --git a/src/winconpty/winconpty.vcxproj b/src/winconpty/winconpty.vcxproj new file mode 100644 index 00000000000..6ffd09468e0 --- /dev/null +++ b/src/winconpty/winconpty.vcxproj @@ -0,0 +1,37 @@ + + + + + + + Create + + + + + + + + %(AdditionalIncludeDirectories) + + + + {58a03bb2-df5a-4b66-91a0-7ef3ba01269a} + Win32Proj + winconpty + winconpty + conpty + + + + + + + + winconpty.def + + + onecoreuap_apiset.lib;%(AdditionalDependencies) + + + diff --git a/tools/GenerateAppxFromManifest.ps1 b/tools/GenerateAppxFromManifest.ps1 new file mode 100644 index 00000000000..b23db70a7af --- /dev/null +++ b/tools/GenerateAppxFromManifest.ps1 @@ -0,0 +1,74 @@ +# This script is used for taking all the activatable classes from a SxS manifest +# and adding them as Extensions to an Appxmanifest.xml. +# Params: +# - SxSManifest: The path to the SxS manifest to get the types from +# - AppxManifestPrototype: The path to an AppxManifest.xml-style XML document to add the Extensions to +# - SxSManifest: The path to write the updated XML doc to. + +param ( + [parameter(Mandatory=$true, Position=0)] + [string]$SxSManifest, + + [parameter(Mandatory=$true, Position=1)] + [string]$AppxManifestPrototype, + + [parameter(Mandatory=$true, Position=2)] + [string]$OutPath +) + +# Load the xml files. +[xml]$manifestData = Get-Content $SxSManifest +[xml]$appxPrototypeData = Get-Content $AppxManifestPrototype + +# You need to make sure each element we add is part of the same namespace as the +# Package, otherwise powershell will append a bunch of `xmlns=""` properties +# that will make the appx deployment reject the manifest. +$rootNS = $appxPrototypeData.Package.NamespaceURI + +# Create an XML element for all the extensions we're adding. +$Extensions = $appxPrototypeData.CreateNode("element", "Extensions", $rootNS) + +$assembly = $manifestData.assembly +$files = $assembly.file +$files | ForEach-Object { + + $Extension = $appxPrototypeData.CreateNode("element", "Extension", $rootNS) + $Extension.SetAttribute("Category", "windows.activatableClass.inProcessServer") + + $InProcessServer = $appxPrototypeData.CreateNode("element", "InProcessServer", $rootNS) + $Path = $appxPrototypeData.CreateNode("element", "Path", $rootNS) + + # You need to stash the result here, otherwise a blank line will be echod to + # the console. + $placeholder = $Path.InnerText = $_.name + + $InProcessServer.AppendChild($Path) + $Extension.AppendChild($InProcessServer) | Out-Null + + foreach($class in $_.activatableClass) { + $ActivatableClass = $appxPrototypeData.CreateNode("element", "ActivatableClass", $rootNS) + $ActivatableClass.SetAttribute("ActivatableClassId", $class.name) + $ActivatableClass.SetAttribute("ThreadingModel", $class.threadingModel) + + $InProcessServer.AppendChild($ActivatableClass) | Out-Null + } + + $Extensions.AppendChild($Extension) | Out-Null + +} + +# Add our fully constructed list of extensions to the original Appxmanifest prototype +$appxPrototypeData.Package.AppendChild($Extensions) | Out-Null + +# Write the modified xml back out. +$appxPrototypeData.save($OutPath) + +# Left as a helper for debugging: +# $StringWriter = New-Object System.IO.StringWriter; +# $XmlWriter = New-Object System.Xml.XmlTextWriter $StringWriter; +# $XmlWriter.Formatting = "indented"; +# $appxPrototypeData.WriteTo($XmlWriter); +# $XmlWriter.Flush(); +# $StringWriter.Flush(); +# Write-Output $StringWriter.ToString(); + diff --git a/tools/OpenConsole.psm1 b/tools/OpenConsole.psm1 index 2ba68c21da1..48d3f1c35dd 100644 --- a/tools/OpenConsole.psm1 +++ b/tools/OpenConsole.psm1 @@ -159,7 +159,7 @@ function Invoke-OpenConsoleTests() [switch]$FTOnly, [parameter(Mandatory=$false)] - [ValidateSet('host', 'interactivityWin32', 'terminal', 'adapter', 'feature', 'uia', 'textbuffer', 'types', 'terminalCore', 'terminalApp')] + [ValidateSet('host', 'interactivityWin32', 'terminal', 'adapter', 'feature', 'uia', 'textbuffer', 'types', 'terminalCore', 'terminalApp', 'localTerminalApp')] [string]$Test, [parameter(Mandatory=$false)] diff --git a/tools/bcx.cmd b/tools/bcx.cmd new file mode 100644 index 00000000000..eeb078977b6 --- /dev/null +++ b/tools/bcx.cmd @@ -0,0 +1,7 @@ +@echo off + +rem bcx - Build only the project in this directory, cleaning it first. +rem This is another script to help Microsoft developers feel at home working on +rem the terminal project. + +call bcz exclusive %* diff --git a/tools/bcz.cmd b/tools/bcz.cmd index 22cfb111508..4167b069cb9 100644 --- a/tools/bcz.cmd +++ b/tools/bcz.cmd @@ -1,13 +1,26 @@ @echo off -rem bcz - Clean and build the project -rem This is another script to help Microsoft developers feel at home working on the openconsole project. +rem bcz - Clean and build the solution. +rem This is another script to help Microsoft developers feel at home working on the Terminal project. + +rem Args: +rem dbg: manually build the solution in the Debug configuration. If omitted, +rem we'll use whatever the last configuration build was. +rem rel: manually build the solution in the Release configuration. If +rem omitted, we'll use whatever the last configuration build was. +rem no_clean: Don't clean before building. This is a much faster build +rem typically, but leaves artifacts from previous builds around, which +rem can lead to unexpected build failures. +rem exclusive: Only build the project in the cwd. If omitted, we'll try +rem building the entire solution instead. if (%_LAST_BUILD_CONF%)==() ( set _LAST_BUILD_CONF=%DEFAULT_CONFIGURATION% ) set _MSBUILD_TARGET=Clean,Build +set _EXCLUSIVE= +set _APPX_ARGS= :ARGS_LOOP if (%1) == () goto :POST_ARGS_LOOP @@ -22,30 +35,43 @@ if (%1) == (rel) ( if (%1) == (no_clean) ( set _MSBUILD_TARGET=Build ) +if (%1) == (exclusive) ( + set _EXCLUSIVE=1 +) shift goto :ARGS_LOOP - :POST_ARGS_LOOP -echo Starting build... -nuget.exe restore %OPENCON%\OpenConsole.sln -rem /p:AppxBundle=Never prevents us from building the appxbundle from the commandline. -rem We don't want to do this from a debug build, because it takes ages, so disable it. -rem if you want the appx, build release +if "%_EXCLUSIVE%" == "1" ( + set "PROJECT_NAME=" + call :get_project +) else if (%_LAST_BUILD_CONF%) == (Debug) ( -set _APPX_ARGS= + rem /p:AppxBundle=Never prevents us from building the appxbundle from the + rem commandline. We don't want to do this from a debug build, because it + rem takes ages, so disable it. if you want the appx, build release + + rem Only do this check if we're doing a full solution build. If we're only + rem trying to build the appx, then we obviously want to build the appx. -if (%_LAST_BUILD_CONF%) == (Debug) ( echo Skipping building appx... set _APPX_ARGS=/p:AppxBundle=false ) else ( echo Building Appx... ) +if "%_EXCLUSIVE%" == "1" ( + if "%PROJECT_NAME%" == "" ( goto :eof ) else echo Building only %PROJECT_NAME% +) + +echo Performing nuget restore... +nuget.exe restore %OPENCON%\OpenConsole.sln + set _BUILD_CMDLINE="%MSBUILD%" %OPENCON%\OpenConsole.sln /t:%_MSBUILD_TARGET% /m /p:Configuration=%_LAST_BUILD_CONF% /p:Platform=%ARCH% %_APPX_ARGS% echo %_BUILD_CMDLINE% +echo Starting build... %_BUILD_CMDLINE% rem Cleanup unused variables here. Note we cannot use setlocal because we need to pass modified @@ -53,3 +79,48 @@ rem _LAST_BUILD_CONF out to OpenCon.cmd later. rem set _MSBUILD_TARGET= set _BIN_=%~dp0\bin\%PLATFORM%\%_LAST_BUILD_CONF% +goto :eof + +rem ############################################################################ +rem The code to figure out what project we're building needs to be in its own +rem function. Otherwise, when cmd evaluates the if statement above `if +rem "%_EXCLUSIVE%" == "1"`, it'll evaluate the entire block with the value of +rem the the variables at the time the if was executed. So instead, make a +rem function here with `enabledelayedexpansion` set. +:get_project +setlocal enabledelayedexpansion + +rem TODO:GH#2172 Find a way to only rebuild the metaproj if the sln changed +rem First generate the metaproj file +set MSBuildEmitSolution=1 +"%msbuild%" %OPENCON%\OpenConsole.sln /t:ValidateSolutionConfiguration /m > NUL +set MSBuildEmitSolution= + +rem Use bx.ps1 to figure out which target we're looking at +set _BX_SCRIPT=powershell bx.ps1 +set _OUTPUT= +FOR /F "tokens=* USEBACKQ" %%F IN (`powershell bx.ps1 2^> NUL`) DO ( + set _OUTPUT=%%F +) +if "!_OUTPUT!" == "" ( + echo Could not find a .vcxproj file in this directory. + echo `bx.cmd` only works in directories with a vcxproj file. + echo Please navigate to directory with a project file in it, or try `bz` to build the entire solution. + goto :eof +) +set "__PROJECT_NAME=!_OUTPUT!" + +rem If we're trying to clean build, make sure to update the target here. +if "%_MSBUILD_TARGET%" == "Build" ( + set __MSBUILD_TARGET=%__PROJECT_NAME% +) else if "%_MSBUILD_TARGET%" == "Clean,Build" ( + set __MSBUILD_TARGET=%__PROJECT_NAME%:Rebuild +) +rem This statement will propogate our internal variables up to the calling +rem scope. Because they're all on one line, the value of our local variables +rem will be evaluated before we endlocal +endlocal & set "PROJECT_NAME=%__PROJECT_NAME%" & set "_MSBUILD_TARGET=%__MSBUILD_TARGET%" +rem ############################################################################ + + +:eof diff --git a/tools/bx.cmd b/tools/bx.cmd new file mode 100644 index 00000000000..798be23c073 --- /dev/null +++ b/tools/bx.cmd @@ -0,0 +1,7 @@ +@echo off + +rem bx - Build only the project in this directory without cleaning it first. +rem This is another script to help Microsoft developers feel at home working on +rem the terminal project. + +call bcz exclusive no_clean %* diff --git a/tools/bx.ps1 b/tools/bx.ps1 new file mode 100644 index 00000000000..4332c7bb643 --- /dev/null +++ b/tools/bx.ps1 @@ -0,0 +1,28 @@ +# This is a helper script to figure out which target corresponds to the project +# in this directory. Parses the solution's .metaproj file looking for the +# project file in this directory, to be able to get the project's name. + +$projects = Get-Childitem -Path .\ -Filter *.vcxproj -File +if ($projects.length -eq 0) +{ + exit -1 +} +$projectPath = $projects.FullName + +$msBuildCondition = "'%(ProjectReference.Identity)' == '$projectPath.metaproj'" + +# Parse the solution's metaproj file. +[xml]$Metaproj = Get-Content "$env:OPENCON\OpenConsole.sln.metaproj" + +$targets = $Metaproj.Project.Target + +# Filter to project targets that match out metaproj file. +# For Conhost\Server, this will match: +# [Conhost\Server, Conhost\Server:Clean, Conhost\Server:Rebuild, Conhost\Server:Publish] +$matchingTargets = $targets | Where-Object { $_.MSBuild.Condition -eq $msBuildCondition } + +# Further filter to the targets that dont have a suffix (like ":Clean") +$matchingTargets = $matchingTargets | Where-Object { $hasProperty = $_.MsBuild.PSobject.Properties.name -match "Targets" ; return -Not $hasProperty } + +Write-Host $matchingTargets.Name +exit 0 diff --git a/tools/bz.cmd b/tools/bz.cmd index f2c3c45f6e0..6f4ecda2186 100644 --- a/tools/bz.cmd +++ b/tools/bz.cmd @@ -1,6 +1,7 @@ @echo off -rem bcz - Build the project without clean it first -rem This is another script to help Microsoft developers feel at home working on the openconsole project. +rem bz - Build the entire solution without cleaning it first. +rem This is another script to help Microsoft developers feel at home working on +rem the terminal project. call bcz no_clean %* diff --git a/tools/runut.cmd b/tools/runut.cmd index 419f88b6d13..c4a3961aa95 100644 --- a/tools/runut.cmd +++ b/tools/runut.cmd @@ -12,4 +12,5 @@ call %TAEF% ^ %OPENCON%\bin\%PLATFORM%\%_LAST_BUILD_CONF%\ConAdapter.Unit.Tests.dll ^ %OPENCON%\bin\%PLATFORM%\%_LAST_BUILD_CONF%\Types.Unit.Tests.dll ^ %OPENCON%\bin\%PLATFORM%\%_LAST_BUILD_CONF%\UnitTests_TerminalApp\Terminal.App.Unit.Tests.dll ^ + %OPENCON%\bin\%PLATFORM%\%_LAST_BUILD_CONF%\LocalTests_TerminalApp\TerminalApp.LocalTests.dll ^ %* diff --git a/tools/tests.xml b/tools/tests.xml index 2cf7c3b41f8..930cf5ec34d 100644 --- a/tools/tests.xml +++ b/tools/tests.xml @@ -4,6 +4,7 @@ +