mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-07 14:50:55 +00:00
Compare commits
4 Commits
v0.4.2382.
...
dev/miniks
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c41151aba7 | ||
|
|
9dfbdec349 | ||
|
|
9e8f891460 | ||
|
|
0bf347f428 |
4
.github/ISSUE_TEMPLATE/Bug_Report.md
vendored
4
.github/ISSUE_TEMPLATE/Bug_Report.md
vendored
@@ -26,8 +26,6 @@ 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.
|
||||
|
||||
-->
|
||||
@@ -35,7 +33,7 @@ Please use this form and describe your issue, concisely but precisely, with as m
|
||||
# Environment
|
||||
|
||||
```none
|
||||
Windows build number: [run `[Environment]::OSVersion` for powershell, or `ver` for cmd]
|
||||
Windows build number: [run "ver" at a command prompt]
|
||||
Windows Terminal version (if applicable):
|
||||
|
||||
Any other software?
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/Feature_Request.md
vendored
2
.github/ISSUE_TEMPLATE/Feature_Request.md
vendored
@@ -20,7 +20,7 @@ I ACKNOWLEDGE THE FOLLOWING BEFORE PROCEEDING:
|
||||
All good? Then proceed!
|
||||
-->
|
||||
|
||||
# Description of the new feature/enhancement
|
||||
# Summary of the new feature/enhancement
|
||||
|
||||
<!--
|
||||
A clear and concise description of what the problem is that the new feature would solve.
|
||||
|
||||
@@ -25,10 +25,10 @@
|
||||
"Microsoft.VisualStudio.Component.VC.Redist.14.Latest",
|
||||
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
|
||||
"Microsoft.VisualStudio.Component.VC.Tools.ARM64",
|
||||
"Microsoft.VisualStudio.Component.VC.v142.x86.x64",
|
||||
"Microsoft.VisualStudio.Component.VC.v142.ARM64",
|
||||
"Microsoft.VisualStudio.Component.VC.v141.x86.x64",
|
||||
"Microsoft.VisualStudio.Component.VC.v141.ARM64",
|
||||
"Microsoft.VisualStudio.ComponentGroup.UWP.VC",
|
||||
"Microsoft.VisualStudio.ComponentGroup.UWP.VC.v142",
|
||||
"Microsoft.VisualStudio.ComponentGroup.UWP.VC.v141",
|
||||
"Microsoft.VisualStudio.Component.UWP.VC.ARM64"
|
||||
]
|
||||
}
|
||||
|
||||
162
OpenConsole.sln
162
OpenConsole.sln
@@ -243,11 +243,6 @@ 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
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
AuditMode|ARM64 = AuditMode|ARM64
|
||||
@@ -262,8 +257,14 @@ 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
|
||||
@@ -283,8 +284,11 @@ 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
|
||||
@@ -298,8 +302,11 @@ 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
|
||||
@@ -313,8 +320,11 @@ 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
|
||||
@@ -328,8 +338,11 @@ 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
|
||||
@@ -343,8 +356,11 @@ 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
|
||||
@@ -358,8 +374,11 @@ 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
|
||||
@@ -373,8 +392,11 @@ 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
|
||||
@@ -388,8 +410,11 @@ 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
|
||||
@@ -403,8 +428,11 @@ 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
|
||||
@@ -418,8 +446,11 @@ 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
|
||||
@@ -433,8 +464,11 @@ 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
|
||||
@@ -448,8 +482,11 @@ 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
|
||||
@@ -463,8 +500,11 @@ 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
|
||||
@@ -476,8 +516,11 @@ 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
|
||||
@@ -491,8 +534,11 @@ 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
|
||||
@@ -506,8 +552,11 @@ 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
|
||||
@@ -521,8 +570,11 @@ 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
|
||||
@@ -536,8 +588,11 @@ 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
|
||||
@@ -551,8 +606,11 @@ 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
|
||||
@@ -566,8 +624,11 @@ 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
|
||||
@@ -579,8 +640,11 @@ 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
|
||||
@@ -592,8 +656,11 @@ 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
|
||||
@@ -607,8 +674,11 @@ 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
|
||||
@@ -622,8 +692,11 @@ 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
|
||||
@@ -637,8 +710,11 @@ 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
|
||||
@@ -652,8 +728,11 @@ 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
|
||||
@@ -667,8 +746,11 @@ 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
|
||||
@@ -682,8 +764,11 @@ 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
|
||||
@@ -697,8 +782,11 @@ 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
|
||||
@@ -712,8 +800,11 @@ 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
|
||||
@@ -727,8 +818,11 @@ 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
|
||||
@@ -742,8 +836,11 @@ 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
|
||||
@@ -775,8 +872,11 @@ 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
|
||||
@@ -826,8 +926,11 @@ 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
|
||||
@@ -841,8 +944,11 @@ 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
|
||||
@@ -856,8 +962,11 @@ 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
|
||||
@@ -871,8 +980,11 @@ 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
|
||||
@@ -886,8 +998,11 @@ 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
|
||||
@@ -901,8 +1016,11 @@ 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
|
||||
@@ -916,8 +1034,14 @@ 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
|
||||
@@ -937,8 +1061,11 @@ 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
|
||||
@@ -970,8 +1097,11 @@ 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
|
||||
@@ -985,8 +1115,11 @@ 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
|
||||
@@ -1000,8 +1133,11 @@ 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
|
||||
@@ -1014,21 +1150,6 @@ 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
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -1089,7 +1210,6 @@ 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}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271}
|
||||
|
||||
28
README.md
28
README.md
@@ -1,7 +1,7 @@
|
||||
# Welcome\!
|
||||
#### This repository contains the source code for:
|
||||
|
||||
* [Windows Terminal](https://www.microsoft.com/en-us/p/windows-terminal-preview/9n0dx20hk701)
|
||||
* Windows Terminal
|
||||
* The Windows console host (`conhost.exe`)
|
||||
* Components shared between the two projects
|
||||
* [ColorTool](https://github.com/Microsoft/Terminal/tree/master/src/tools/ColorTool)
|
||||
@@ -10,32 +10,6 @@
|
||||
#### Other related repositories include:
|
||||
* [Console API Documentation](https://github.com/MicrosoftDocs/Console-Docs)
|
||||
|
||||
## 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.
|
||||
|
||||
<a href='//www.microsoft.com/store/apps/9n0dx20hk701?cid=storebadge&ocid=badge'><img src='https://assets.windowsphone.com/85864462-9c82-451e-9355-a3d5f874397a/English_get-it-from-MS_InvariantCulture_Default.png' alt='English badge' width="284" height="104" style='width: 284px; height: 104px;'/></a>
|
||||
|
||||
### Chocolatey (Unofficial)
|
||||
|
||||
Download and upgrade the Windows Terminal from [Chocolatey](https://chocolatey.org).
|
||||
|
||||
To install Windows Terminal, run the following command from the command line or from PowerShell:
|
||||
```powershell
|
||||
choco install microsoft-windows-terminal
|
||||
```
|
||||
|
||||
To upgrade Windows Terminal, run the following command from the command line or from PowerShell:
|
||||
```powershell
|
||||
choco upgrade microsoft-windows-terminal
|
||||
```
|
||||
|
||||
If you have any issues when installing/upgrading the package please go to the [package page](https://chocolatey.org/packages/microsoft-windows-terminal) and follow the [Chocolatey triage process](https://chocolatey.org/docs/package-triage-process)
|
||||
|
||||
### Build Status
|
||||
|
||||
Project|Build Status
|
||||
|
||||
@@ -13,11 +13,6 @@ pr:
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
paths:
|
||||
exclude:
|
||||
- doc/*
|
||||
- samples/*
|
||||
- tools/*
|
||||
|
||||
# 0.0.yyMM.dd##
|
||||
# 0.0.1904.0900
|
||||
|
||||
@@ -46,14 +46,6 @@ steps:
|
||||
clean: true
|
||||
maximumCpuCount: true
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Check MSIX for common regressions'
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
$Package = Get-ChildItem -Recurse -Filter "CascadiaPackage_*.msix"
|
||||
.\build\scripts\Test-WindowsTerminalPackage.ps1 -Verbose -Path $Package.FullName
|
||||
|
||||
- task: VSTest@2
|
||||
displayName: 'Run Unit Tests'
|
||||
inputs:
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="16.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<BeforeLinkTargets Condition="'$(WindowsTargetPlatformVersion)' >= '10.0.18362.0'">
|
||||
$(BeforeLinkTargets);
|
||||
_ConsoleGenerateAdditionalWinmdManifests;
|
||||
</BeforeLinkTargets>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="_ConsoleMapWinmdsToManifestFiles" DependsOnTargets="ResolveAssemblyReferences">
|
||||
<ItemGroup>
|
||||
<!-- For each non-system .winmd file in References, generate a .manifest in IntDir for it. -->
|
||||
<_ConsoleWinmdManifest Include="@(ReferencePath->'$(IntDir)\%(FileName).manifest')" Condition="'%(ReferencePath.IsSystemReference)' != 'true' and '%(ReferencePath.WinMDFile)' == 'true' and '%(ReferencePath.ReferenceSourceTarget)' == 'ResolveAssemblyReference'">
|
||||
<WinMDPath>%(ReferencePath.FullPath)</WinMDPath>
|
||||
<Implementation>%(ReferencePath.Implementation)</Implementation>
|
||||
</_ConsoleWinmdManifest>
|
||||
<!-- For each referenced project that _produces_ a winmd, generate a temporary item that maps to
|
||||
the winmd, and use that temporary item to generate a .manifest in IntDir for it.
|
||||
We don't set Implementation here because it's inherited from the _ResolvedNativeProjectReferencePaths. -->
|
||||
<_ConsoleWinmdProjectReference Condition="'%(_ResolvedNativeProjectReferencePaths.ProjectType)' != 'StaticLibrary'" Include="@(_ResolvedNativeProjectReferencePaths->WithMetadataValue('FileType','winmd')->'%(RootDir)%(Directory)%(TargetPath)')" />
|
||||
<_ConsoleWinmdManifest Include="@(_ConsoleWinmdProjectReference->'$(IntDir)\%(FileName).manifest')">
|
||||
<WinMDPath>%(Identity)</WinMDPath>
|
||||
</_ConsoleWinmdManifest>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="_ConsoleGenerateAdditionalWinmdManifests"
|
||||
Inputs="@(_ConsoleWinmdManifest.WinMDPath)"
|
||||
Outputs="@(_ConsoleWinmdManifest)"
|
||||
DependsOnTargets="_ConsoleMapWinmdsToManifestFiles">
|
||||
|
||||
<!-- This target is batched and a new Exec is spawned for each entry in _ConsoleWinmdManifest. -->
|
||||
<Exec Command="mt.exe -winmd:%(_ConsoleWinmdManifest.WinMDPath) -dll:%(_ConsoleWinmdManifest.Implementation) -out:%(_ConsoleWinmdManifest.Identity)" />
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Emit the generated manifest into the Link inputs. -->
|
||||
<Manifest Include="@(_ConsoleWinmdManifest)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -1,79 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$true, ValueFromPipeline=$true,
|
||||
HelpMessage="Path to the .appx/.msix to validate")]
|
||||
[string]
|
||||
$Path,
|
||||
|
||||
[Parameter(HelpMessage="Path to Windows Kit")]
|
||||
[ValidateScript({Test-Path $_ -Type Leaf})]
|
||||
[string]
|
||||
$WindowsKitPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
If ($null -Eq (Get-Item $WindowsKitPath -EA:SilentlyContinue)) {
|
||||
Write-Error "Could not find a windows SDK at at `"$WindowsKitPath`".`nMake sure that WindowsKitPath points to a valid SDK."
|
||||
Exit 1
|
||||
}
|
||||
|
||||
$makeAppx = "$WindowsKitPath\x86\MakeAppx.exe"
|
||||
$makePri = "$WindowsKitPath\x86\MakePri.exe"
|
||||
|
||||
Function Expand-ApplicationPackage {
|
||||
Param(
|
||||
[Parameter(Mandatory, ValueFromPipeline)]
|
||||
[string]
|
||||
$Path
|
||||
)
|
||||
|
||||
$sentinelFile = New-TemporaryFile
|
||||
$directory = New-Item -Type Directory "$($sentinelFile.FullName)_Package"
|
||||
Remove-Item $sentinelFile -Force -EA:Ignore
|
||||
|
||||
& $makeAppx unpack /p $Path /d $directory /nv /o
|
||||
|
||||
If ($LastExitCode -Ne 0) {
|
||||
Throw "Failed to expand AppX"
|
||||
}
|
||||
|
||||
$directory
|
||||
}
|
||||
|
||||
Write-Verbose "Expanding $Path"
|
||||
$AppxPackageRoot = Expand-ApplicationPackage $Path
|
||||
$AppxPackageRootPath = $AppxPackageRoot.FullName
|
||||
|
||||
Write-Verbose "Expanded to $AppxPackageRootPath"
|
||||
|
||||
Try {
|
||||
& $makePri dump /if "$AppxPackageRootPath\resources.pri" /of "$AppxPackageRootPath\resources.pri.xml" /o
|
||||
If ($LastExitCode -Ne 0) {
|
||||
Throw "Failed to dump PRI"
|
||||
}
|
||||
|
||||
$Manifest = [xml](Get-Content "$AppxPackageRootPath\AppxManifest.xml")
|
||||
$PRIFile = [xml](Get-Content "$AppxPackageRootPath\resources.pri.xml")
|
||||
|
||||
### Check the activatable class entries for a few DLLs we need.
|
||||
$inProcServers = $Manifest.Package.Extensions.Extension.InProcessServer.Path
|
||||
$RequiredInProcServers = ("TerminalApp.dll", "TerminalControl.dll", "TerminalConnection.dll")
|
||||
|
||||
Write-Verbose "InProc Servers: $inProcServers"
|
||||
|
||||
ForEach ($req in $RequiredInProcServers) {
|
||||
If ($req -NotIn $inProcServers) {
|
||||
Throw "Failed to find $req in InProcServer list $inProcServers"
|
||||
}
|
||||
}
|
||||
|
||||
### Check that we have an App.xbf (which is a proxy for our resources having been merged)
|
||||
$resourceXpath = '/PriInfo/ResourceMap/ResourceMapSubtree[@name="Files"]/NamedResource[@name="App.xbf"]'
|
||||
$AppXbf = $PRIFile.SelectSingleNode($resourceXpath)
|
||||
If ($null -eq $AppXbf) {
|
||||
Throw "Failed to find App.xbf (TerminalApp project) in resources.pri"
|
||||
}
|
||||
} Finally {
|
||||
Remove-Item -Recurse -Force $AppxPackageRootPath
|
||||
}
|
||||
2
dep/gsl
2
dep/gsl
Submodule dep/gsl updated: 1212beae77...b74b286d5e
2
dep/wil
2
dep/wil
Submodule dep/wil updated: e8c599bca6...fbcd1d2abb
@@ -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,10 +39,6 @@ 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:
|
||||
|
||||
181
doc/Niksa.md
181
doc/Niksa.md
@@ -1,181 +0,0 @@
|
||||
# Niksa's explanations
|
||||
|
||||
Sometimes @miniksa will write a big, long explanatory comment in an issue thread that turns out to be a decent bit of reference material.
|
||||
This document serves as a storage point for those posts.
|
||||
|
||||
- [Why do we avoid changing CMD.exe?](#cmd)
|
||||
- [Why is typing-to-screen performance better than every other app?](#screenPerf)
|
||||
- [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)
|
||||
|
||||
## <a name="cmd"></a>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.
|
||||
|
||||
I don't think anyone is disagreeing with you, @mikemaccana, that this would be a five minute development change to read that environment variable and change the behavior of `cmd.exe`. It absolutely would be a tiny development time.
|
||||
|
||||
It's just that from our experience, we know there's going to be a 3-24 month bug tail here where we get massive investigation callbacks by some billion dollar enterprise customer who for whatever reason was already using the environment variable we pick for another purpose. Their script that they give their rank-and-file folks will tell them to press Ctrl+C at some point in the batch script to do whatever happens, it will do something different, those people will notice the script doesn't match the computer anymore. They will then halt the production line and tell their supervisor. The supervisor tells some director. Their director comes screaming at their Microsoft enterprise support contract person that we've introduced a change to the OS that is costing them millions if not billions of dollars in shipments per month. Our directors at Microsoft then come bashing down our doors angry with us and make us fix it ASAP or revert it, we don't get to go home at 5pm to our families or friends because we're fixing it, we get stressed the heck out, we have to spin up servicing potentially for already shipped operating systems which is expensive and headache-causing...etc.
|
||||
|
||||
We can see this story coming a million miles away because it has happened before with other 'tiny' change we've been asked to make to `cmd.exe` in the past few years.
|
||||
|
||||
I would just ask you to understand that `cmd.exe` is very, very much in a maintenance mode and I just want to set expectations here. We maintain it, yes. We have a renewed interest in command-line development, yes. But our focuses are revolving around improving the terminal and platform itself and bringing modern, supported shells to be the best they can be on Windows. Paul will put this on the backlog of things that people want in `cmd.exe`, yes. But it will sink to the bottom of the backlog because changing `cmd.exe` is our worst nightmare as its compatibility story is among the heaviest of any piece of the operating system.
|
||||
|
||||
I would highly recommend that Gulp convert to using PowerShell scripts and that if such an issue exists with PowerShell, that we get their modern, supported, and better-engineered platform to support the scenario. I don't want you to sit around waiting for `cmd.exe` to change this because it's really not going to happen faster than that script could be converted to `ps1` and it fixed in PowerShell Core (if that's even a problem in that world.)
|
||||
|
||||
Original Source: https://github.com/microsoft/terminal/issues/217#issuecomment-404240443
|
||||
|
||||
## <a name="screenPerf"></a>Why is typing-to-screen performance better than every other app?
|
||||
|
||||
I really do not mind when someone comes by and decides to tell us that we're doing a good job at something. We hear so many complaints every day that a post like this is a breath of fresh air. Thanks for your thanks!
|
||||
|
||||
Also, I'm happy to discuss this with you until you're utterly sick of reading it. Please ask any follow-ons you want. I thrive on blathering about my work. :P
|
||||
|
||||
If I had to take an educated guess as to what is making us faster than pretty much any other application on Windows at putting your text on the screen... I would say it is because that is literally our only job! Also probably because we are using darn near the oldest and lowest level APIs that Windows has to accomplish this work.
|
||||
|
||||
Pretty much everything else you've listed has some sort of layer or framework involved, or many, many layers and frameworks, when you start talking about Electron and Javascript. We don't.
|
||||
|
||||
We have one bare, super un-special window with no additional controls attached to it. We get our keys fed into us from just barely above the kernel given that we're processing them from window messages and not from some sort of eventing framework common to pretty much any other more complicated UI framework than ours (WPF, WinForms, UWP, Electron). And we dump our text straight onto the window surface using GDI's [PolyTextOut](https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-polytextoutw) with no frills.
|
||||
|
||||
Even `notepad.exe` has multiple controls on its window at the very least and is probably (I haven't looked) using some sort of library framework in the edit control to figure out its text layout (which probably is using another library framework for internationalization support...)
|
||||
|
||||
Of course this also means that we have trade offs. We don't support fully international text like pretty much every other application will. RTL? No go zone right now. Surrogate pairs and emoji? We're getting there but not there yet. Indic scripts? Nope.
|
||||
|
||||
Why are we like this? For one, `conhost.exe` is old as dirt. It has to use the bare metal bottom layer of everything because it was created before most of those other frameworks were created. And also it maintains as low/bottom level as possible because it is pretty much the first thing that one needs to bring up when bringing up a new operating system edition or device before you have all the nice things like frameworks or what those frameworks require to operate. Also it's written in C/C++ which is about as low and bare metal as we can get.
|
||||
|
||||
Will this UI enhancement come to other apps on Windows? Almost certainly not. They have too much going on which is both a good and a bad thing. I'm jealous of their ability to just call one method and layout text in an uncomplicated manner in any language without manually calculating pixels or caring about what styles apply to their font. But my manual pixel calculations, dirty region math, scroll region madness, and more makes it so we go faster than them. I'm also jealous that when someone says "hey can you add a status bar to the bottom of your window" that they can pretty much click and drag that into place with their UI Framework and it will just work where as for us, it's been a backlog item forever and gives me heartburn to think about implementing.
|
||||
|
||||
Will we try to keep it from regressing? Yes! Right now it's sort of a manual process. We identify that something is getting slow and then we go haul out [WPR](https://docs.microsoft.com/en-us/windows-hardware/test/wpt/windows-performance-recorder) and start taking traces. We stare down the hot paths and try to reason out what is going on and then improve them. For instance, in the last cycle or two, we focused on heap allocations as a major area where we could improve our end-to-end performance, changing a ton of our code to use stack-constructed iterator-like facades over the underlying request buffer instead of translating and allocating it into a new heap space for each level of processing.
|
||||
|
||||
As an aside, @bitcrazed wants us to automate performance tests in some conhost specific way, but I haven't quite figured out a controlled environment to do this in yet. The Windows Engineering System runs performance tests each night that give us a coarse grained way of knowing if we messed something up for the whole operating system, and they technically offer a fine grained way for us to insert our own performance tests... but I just haven't got around to that yet. If you have an idea for a way for us to do this in an automated fashion, I'm all ears.
|
||||
|
||||
If there's anything else you'd like to know, let me know. I could go on all day. I deleted like 15 tangents from this reply before posting it....
|
||||
|
||||
Original Source: https://github.com/microsoft/terminal/issues/327#issuecomment-447391705
|
||||
|
||||
## <a name="gfxMsgStack"></a>How are the Windows graphics/messaging stack assembled?
|
||||
|
||||
@stakx, I am referring to USER32 and GDI32.
|
||||
|
||||
I'll give you a cursory overview of what I know off the top of my head without spending hours confirming the details. As such, some of this is subject to handwaving and could be mildly incorrect but is probably in the right direction. Consider every statement to be my personal knowledge on how the world works and subject to opinion or error.
|
||||
|
||||
For the graphics part of the pipeline (GDI32), the user-mode portions of GDI are pretty far down. The app calls GDI32, some work is done in that DLL on the user-mode side, then a kernel call jumps over to the kernel and drawing occurs.
|
||||
|
||||
The portion that you're thinking of regarding "silently converted to sit on top of other stuff" is probably that once we hit the kernel calls, a bunch of the kernel GDI stuff tends to be re-platformed on top of the same stuff as DirectX when it is actually handled by the NVIDIA/AMD/Intel/etc. graphics driver and the GPU at the bottom of the stack. I think this happened with the graphics driver re-architecture that came as a part of WDDM for Windows Vista. There's a document out there somewhere about what calls are still really fast in GDI and which are slower as a result of the re-platforming. Last time I found that document and checked, we were using the fast ones.
|
||||
|
||||
On top of GDI, I believe there are things like Common Controls or comctl32.dll which provided folks reusable sets of buttons and elements to make their UIs before we had nicer declarative frameworks. We don't use those in the console really (except in the property sheet off the right click menu).
|
||||
|
||||
As for DirectWrite and D2D and D3D and DXGI themselves, they're a separate set of commands and paths that are completely off to the side from GDI at all both in user and kernel mode. They're not really related other than that there's some interoperability provisions between the two. Most of our other UI frameworks tend to be built on top of the DirectX stack though. XAML is for sure. I think WPF is. Not sure about WinForms. And I believe the composition stack and the window manager are using DirectX as well.
|
||||
|
||||
As for the input/interaction part of the pipeline (USER32), I tend to find most other newer things (at least for desktop PCs) are built on top of what is already there. USER32's major concept is windows and window handles and everything is sent to a window handle. As long as you're on a desktop machine (or a laptop or whatever... I mean a classic-style Windows-powered machine), there's a window handle involved and messages floating around and that means we're talking USER32.
|
||||
|
||||
The window message queue is just a straight up FIFO (more or less) of whatever input has occurred relevant to that window while it's in the foreground + whatever has been sent to the window by other components in the system.
|
||||
|
||||
The newer technologies and the frameworks like XAML and WPF and WinForms tend to receive the messages from the window message queue one way or another and process them and turn them into event callbacks to various objects that they've provisioned within their world.
|
||||
|
||||
However, the newer technologies that also work on other non-desktop platforms like XAML tend to have the ability to process stuff off of a completely different non-USER32 stack as well. There's a separate parallel stack to USER32 with all of our new innovations and realizations on how input and interaction should occur that doesn't exactly deal with classic messaging queues and window handles the same way. This is the whole Core* family of things like CoreWindow and CoreMessaging. They also have a different concept of "what is a user" that isn't so centric around your butt in rolling chair in front of a screen with a keyboard and mouse on the desk.
|
||||
|
||||
Now, if you're on XAML or one of the other Frameworks... all this intricacy is handled for you. XAML figures out how to draw on DirectX for you and negotiates with the compositor and window manager for cool effects on your behalf. It figures out whether to get your input events from USER32 or Core* or whatever transparently depending on your platform and the input stacks can handle pen, touch, keyboard, mouse, and so on in a unified manner. It has provisions inside it embedded to do all the sorts of globalization, accessibility, input interaction, etc. stuff that make your life easy. But you could choose to go directly to the low-level and handle it yourself or skip handling what you don't care about.
|
||||
|
||||
The trick is that GDI32 and USER32 were designed for a limited world with a limited set of commands. Desktop PCs were the only thing that existed, single user at the keyboard and mouse, simple graphics output to a VGA monitor. So using them directly at the "low level" like conhost does is pretty easy. The new platforms could be used at the "low level" but they're orders of magnitude more complicated because they now account for everything that has happened with personal computing in 20+ years like different form factors, multiple active users, multiple graphics adapters, and on and on and on and on. So you tend to use a framework when using the new stuff so your head doesn't explode. They handle it for you, but they handle more than they ever did before so they're slower to some degree.
|
||||
|
||||
So are GDI32 and USER32 "lower" than the new stuff? Sort of.
|
||||
Can you get that low with the newer stuff? Mostly yes, but you probably shouldn't and don't want to.
|
||||
Does new live on top of old or is old replatformed on the new? Sometimes and/or partially.
|
||||
Basically... it's like the answer to anything software... "it's an unmitigated disaster and if we all stepped back a moment, we should be astounded that it works at all." :P
|
||||
|
||||
Anyway, that's enough ramble for one morning. Hopefully that somewhat answered your questions and gave you a bit more insight.
|
||||
|
||||
Original Source: https://github.com/microsoft/terminal/issues/327#issuecomment-447926388
|
||||
|
||||
## <a name="fesb"></a>Output Processing between "Far East" and "Western"
|
||||
|
||||
>
|
||||
> ```
|
||||
> if (WI_IsFlagSet(CharType, C1_CNTRL))
|
||||
> ```
|
||||
|
||||
In short, this is probably fine to fix.
|
||||
|
||||
However, I would personally feed a few characters through `WriteCharsLegacy` under the debugger and assert that your theory is correct first (that multiple flags coming back are what the problem is) before making the change.
|
||||
|
||||
I am mildly terrified, less than Dustin, because it is freaking `WriteCharsLegacy` which is the spawn of hell and I fear some sort of regression in it.
|
||||
|
||||
In long, why is it fine to fix?
|
||||
|
||||
For reference, this particular segment of code https://github.com/microsoft/terminal/blob/9b92986b49bed8cc41fde4d6ef080921c41e6d9e/src/host/_stream.cpp#L514-L539 appears to only be used when the codepoint is < 0x20 or == 0x7F https://github.com/microsoft/terminal/blob/9b92986b49bed8cc41fde4d6ef080921c41e6d9e/src/host/_stream.cpp#L408 and ENABLE_PROCESSED_OUTPUT is off. https://github.com/microsoft/terminal/blob/9b92986b49bed8cc41fde4d6ef080921c41e6d9e/src/host/_stream.cpp#L320
|
||||
|
||||
I looked back at the console v1 code and this particular section had a divergence for "Western" countries and "Far East" countries (a geopolitically-charged term, but what it was, nonetheless.)
|
||||
|
||||
For "Western" countries, we would unconditionally run all the characters through `MultiByteToWideChar` with `MB_USEGLYPHCHARS` without the `C1_CNTRL` test and move the result into the buffer.
|
||||
|
||||
For "Eastern" countries, we did the `C1_CNTRL` test and then if true, we would run through `MultiByteToWideChar` with `MB_USEGLYPHCHARS`. Otherwise, we would just move the original character into the buffer and call it a day.
|
||||
|
||||
Note in both of these, there is a little bit of indirection before `MultiByteToWideChar` is called through some other helper methods like `ConvertOutputToUnicode`, but that's the effective conversion point, as far as I can tell. And that's where the control characters would turn into acceptable low ASCII symbols.
|
||||
|
||||
When we took over the console codebase, this variation between "Western" and "Eastern" countries was especially painful because `conhost.exe` would choose which one it was in based on the `Codepage for Non-Unicode Applications` set in the Control Panel's Regional > Administrative panel and it could only be changed with a reboot. It wouldn't even change properly when you `chcp` to a different codepage. Heck, `chcp` would deny you from switching into many codepages. There was a block in place to prevent going to an "Eastern" codepage if you booted up in a "Western" codepage. There was also a block preventing you from going between "Eastern" codepages, if I recall correctly.
|
||||
|
||||
In modernizing, I decided a few things:
|
||||
1. What's good for the "Far East" should be good for the rest of the world. CJK languages that encompassed the "Far East" code have to be able to handle "Western" text as well even if the reverse wasn't true.
|
||||
2. We need to scrub all usages of "Far East" from the code. Someone already started that and replaced them with "East Asia" except then they left behind the shorthand of "FE" prefixing dozens of functions which made it hard to follow the code. It took us months to realize "FE" and "East Asia" were the same thing.
|
||||
3. It's obnoxious that the way this was handled was to literally double-define every output function in the code base to have two definitions, compile them both into the conhost, then choose to run down the SB_ versions or the FE_ versions depending on the startup Non-Unicode codepage. It was a massive pile of complex pre-compilation `#ifdef` and `#else`s that would sometimes surround individual lines in the function bodies. Gross.
|
||||
4. The fact that the FE_ versions of the functions were way slower than the SB_ ones was unacceptable even for the same output of Latin-character text.
|
||||
5. Anyone should be free to switch between any codepage they want at any time and restricting it based on a value from OS startup or region/locale is not acceptable in the modern world.
|
||||
6. I concluded by all of the above that I was going to tank/delete/remove the SB_ versions of everything and force the entire world to use the FE_ versions as truth. I would fix the FE_ versions to handle everything correctly, I would fix the performance characteristics of the FE_ versions so they were only slower when things were legitimately more complicated and never otherwise, I would banish all usage of "Far East", "East Asia", "FE_", and "SB_" from the codebase, and codepages would be freely switchable.
|
||||
7. Oh. Also, the conhost used to rewrite its entire backing buffer into whatever your current codepage was whenever you switched codepages. I changed that to always hold it as UTF-16.
|
||||
|
||||
Now, after that backstory. This is where the problem comes in. It looks like the code you're pointing to that didn't check flags and instead checked direct equality... is the way that it was ALWAYS done for the "Eastern" copy of the code. So it was ALWAYS broken for the "Eastern" codepages and country variants of the OS.
|
||||
|
||||
I don't know why the "Eastern" copy was checking `C1_CNTRL` at all in the first place. There is no documentation. I presume it has to do with Shift-JIS or GB-2312 or Unified Hangul or something having a conflict < 0x20 || == 0x7F. Or alternatively, it's because someone wrote the code naively thinking it was a good idea in a hurry and never tested it. Very possible and even probable.
|
||||
|
||||
Presuming CJK codepages have no conflict in this range for their DBCS codepages... we could probably remove the check with `GetStringTypeW` entirely and always run everything through `ConvertOutputToUnicode`. More risky than just the flag test change... but theoretically an option as well.
|
||||
|
||||
|
||||
Original Source: https://github.com/microsoft/terminal/issues/166#issuecomment-510953359
|
||||
|
||||
## <a name="backport"></a>Why do we not backport things?
|
||||
|
||||
Someone has to prove that this is costing millions to billions of dollars of lost productivity or revenue to outweigh the risks of shipping the fix to hundreds of millions of Windows machines and potentially breaking something.
|
||||
|
||||
Our team generally finds it pretty hard to prove that against the developer audience given that they're only a small portion of the total installed market of Windows machines.
|
||||
|
||||
Our only backport successes really come from corporations with massive addressable market (like OEMs shipping PCs) who complain that this is fouling up their manufacturing line (or something of that ilk). Otherwise, our management typically says that the risks don't outweigh the benefits.
|
||||
|
||||
It's also costly in terms of time, effort, and testing for us to validate a modification to a released OS. We have a mindbogglingly massive amount of automated machinery dedicated to processing and validating the things that we check in while developing the current OS builds. But it's a special costly ask to spin up some to all of those activities to validate backported fixes. We do it all the time for Patch Tuesday, but in those patches, they only pass through the minimum number of fixes required to maximize the restoration of productivity/security/revenue/etc. because every additional fix adds additional complexity and additional risk.
|
||||
|
||||
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
|
||||
|
||||
## <a name="elevation"></a>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
|
||||
|
||||
29
doc/bot.md
29
doc/bot.md
@@ -8,12 +8,12 @@ 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 improperly
|
||||
1. Look at `Needs-Tag-Fix` when you have a few minutes to fix up things tagged impoperly
|
||||
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
|
||||
1. When new issues arrive, or when issues are not properly tagged... we'll mark them as `Needs-Triage` automatically.
|
||||
- The core contributor team will then come through and mark them up as appropriate. The goal is to have a tag that fits the `Product`, `Area`, and `Issue` category.
|
||||
- The core contributor team will then come through and mark them up as appropriate. The goal is to have a tag that fits the `Product`, `Area`, and `Issue` category.
|
||||
- The `Needs-Triage` tag will be removed manually by the core contributor team during a triage meeting. (Exception, triage may also be done offline by senior team members during high-volume times.)
|
||||
- An issue may or may not be assigned to a contributor during triage. It is not necessary to assign someone to complete it.
|
||||
- We're not focusing on Projects yet.
|
||||
@@ -22,7 +22,7 @@ We'll be using tags, primarily, to help us understand what needs attention, what
|
||||
- When this tag drops off, the bot will apply the `Needs-Attention` tag to get the core contribution team's attention again. If an author cares enough to be active, we will attempt to prioritize engaging with that author.
|
||||
- If the author doesn't come back around in a while, this will become a `No-Recent-Activity` tag.
|
||||
- If there's activity on an issue, the `No-Recent-Activity` tag will automatically drop.
|
||||
- If the `No-Recent-Activity` stays, the issue will be closed as stale.
|
||||
- If the `No-Recent-Activity` stays, the issue will be closed as stale.
|
||||
1. PRs will automatically get a `Needs-Author-Feedback` tag when reviewers wait on the author
|
||||
- This follows a similar decay strategy to issues.
|
||||
- If the author responds, the `Needs-Author-Feedback` tag will drop.
|
||||
@@ -37,7 +37,7 @@ We'll be using tags, primarily, to help us understand what needs attention, what
|
||||
|
||||
#### Mark as Triage Needed
|
||||
- When an issue doesn't meet triage criteria, applies `Needs-Triage` tag. Right now, this is just when it's opened.
|
||||
|
||||
|
||||
#### Author Has Responded
|
||||
- When an issue with `Needs-Author-Feedback` gets an author response, drops that tag in favor of `Needs-Attention` to flag core contributors to drop by.
|
||||
|
||||
@@ -64,12 +64,6 @@ 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
|
||||
- If an issue is tagged `Needs-Feedback-Hub`
|
||||
- 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.
|
||||
- And remove the `Needs-Feedback-Hub` tag
|
||||
- And add the `Needs-Author-Feedback` tag
|
||||
|
||||
### PR Management
|
||||
|
||||
#### Codeflow Link *(Disabled)*
|
||||
@@ -93,25 +87,16 @@ We'll be using tags, primarily, to help us understand what needs attention, what
|
||||
#### Auto-Merge pull requests
|
||||
- When a pull request has the `AutoMerge` label...
|
||||
- If it has been at least 480 minutes and all the statuses pass, merge it in.
|
||||
- Will use Squash merge strategy
|
||||
- Will use Squash merge stratgy
|
||||
- Will attempt to delete branch after merge, if possible
|
||||
- Will automatically remove the `AutoMerge` label if changes are pushed by someone *without* Write Access.
|
||||
- More information on bot-logic that can be controlled with comments is [here](https://github.com/OfficeDev/office-ui-fabric-react/wiki/Advanced-auto-merge)
|
||||
|
||||
|
||||
#### Mark issues with an active PR
|
||||
- If there is an active PR for an issue, label that issue with the `In-PR` label
|
||||
|
||||
#### 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/)
|
||||
|
||||
@@ -1,717 +0,0 @@
|
||||
---
|
||||
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 <kbd>ctrl</kbd>+<kbd>t</kbd> 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 <path-to-app-package>\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 <kbd>Alt</kbd> 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
|
||||
<kbd>ctrl</kbd>+<kbd>alt</kbd>+<kbd>\`</kbd>, similar to `openSettings` on
|
||||
<kbd>ctrl</kbd>+<kbd>\`</kbd>.
|
||||
|
||||
### 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
|
||||
|
||||
|
||||
<!-- Footnotes -->
|
||||
|
||||
[#1348]: https://github.com/microsoft/terminal/issues/1348
|
||||
@@ -1,362 +0,0 @@
|
||||
---
|
||||
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<N>` 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<AppKeyBindings, ActionEventArgs> CopyText;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> NewTab;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> 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<KeyChord, ShortcutAction, ...>`, 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<KeyChord,
|
||||
ActionAndArgs, ...>`.
|
||||
|
||||
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<ShortcutAction, function<IActionArgs(Json::Value)>>`. 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<n>`
|
||||
- We'll need to make sure to build args with the right `profileIndex`
|
||||
corresponding to the old action.
|
||||
* `switchToTab<n>`
|
||||
- We'll need to make sure to build args with the right `index` corresponding
|
||||
to the old action.
|
||||
* `resizePane<direction>` and `moveFocus<direction>`
|
||||
- We'll need to make sure to build args with the right `direction`
|
||||
corresponding to the old action.
|
||||
* `scroll<direction>`
|
||||
- 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, <kbd>Alt+<N></kbd> 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`<sup>[1]</sup> 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
|
||||
@@ -6,14 +6,13 @@ 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 <kbd>Ctrl</kbd> + <kbd>T</kbd>. |
|
||||
| `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 <kbd>Ctrl</kbd> + <kbd>T</kbd> 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. |
|
||||
| `requestedTheme` | _Required_ | String | `system` | Sets the theme of the application. Possible values: `"light"`, `"dark"`, `"system"` |
|
||||
| `showTerminalTitleInTitlebar` | _Required_ | Boolean | `true` | When set to `true`, titlebar displays the title of the selected tab. When set to `false`, titlebar displays "Windows Terminal". |
|
||||
| `showTabsInTitlebar` | Optional | Boolean | `true` | When set to `true`, the tabs are moved into the titlebar and the titlebar disappears. When set to `false`, the titlebar sits above the tabs. |
|
||||
| `wordDelimiters` | Optional | String | <code> /\()"'-:,.;<>~!@#$%^&*|+=[]{}~?│</code><br>_(`│` is `U+2502 BOX DRAWINGS LIGHT VERTICAL`)_ | Determines the delimiters used in a double click selection. |
|
||||
| `wordDelimiters` | Optional | String | ` ./\\()\"'-:,.;<>~!@#$%^&*|+=[]{}~?\u2502` | Determines the delimiters used in a double click selection. |
|
||||
|
||||
## Profiles
|
||||
Properties listed below are specific to each unique profile.
|
||||
@@ -26,26 +25,22 @@ 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. We will try to fallback to Consolas if this can't be found or is invalid. |
|
||||
| `fontFace` | _Required_ | String | `Consolas` | Name of the font face used in the profile. |
|
||||
| `fontSize` | _Required_ | Integer | `10` | 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. <br>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`. |
|
||||
| `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. |
|
||||
| `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. |
|
||||
| `background` | Optional | String | | Sets the background color of the profile. Overrides `background` set in color scheme if `colorscheme` is set. Uses hex color format: `"#rrggbb"`. |
|
||||
| `backgroundImage` | Optional | String | | Sets the file location of the Image to draw over the window background. |
|
||||
| `backgroundImageAlignment` | Optional | String | `center` | Sets how the background image aligns to the boundaries of the window. Possible values: `"center"`, `"left"`, `"top"`, `"right"`, `"bottom"`, `"topLeft"`, `"topRight"`, `"bottomLeft"`, `"bottomRight"` |
|
||||
| `backgroundImageOpacity` | Optional | Number | `1.0` | Sets the transparency of the background image. Accepts floating point values from 0-1. |
|
||||
| `backgroundImageStretchMode` | Optional | String | `uniformToFill` | Sets how the background image is resized to fill the window. Possible values: `"none"`, `"fill"`, `"uniform"`, `"uniformToFill"` |
|
||||
| `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. |
|
||||
| `scrollbarState` | Optional | String | | Defines the visibility of the scrollbar. Possible values: `"visible"`, `"hidden"` |
|
||||
| `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. |
|
||||
| `tabTitle` | Optional | String | | Overrides default title of the tab. |
|
||||
|
||||
## 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.
|
||||
@@ -79,57 +74,3 @@ 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
|
||||
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
---
|
||||
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 <kbd>F1</kbd> to <kbd>F4</kbd> 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
|
||||
---------------|-----------|-----------
|
||||
<kbd>F1</kbd> | `SS3 P` | `ESC P`
|
||||
<kbd>F2</kbd> | `SS3 Q` | `ESC Q`
|
||||
<kbd>F3</kbd> | `SS3 R` | `ESC R`
|
||||
<kbd>F4</kbd> | `SS3 S` | `ESC S`
|
||||
|
||||
The function keys <kbd>F5</kbd> to <kbd>F12</kbd> 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 <kbd>Menu</kbd> key mapping to the VT220 <kbd>DO</kbd> key.
|
||||
|
||||
Key | Sequence
|
||||
----------------|-------------
|
||||
<kbd>F5</kbd> | `CSI 1 5 ~`
|
||||
<kbd>F6</kbd> | `CSI 1 7 ~`
|
||||
<kbd>F7</kbd> | `CSI 1 8 ~`
|
||||
<kbd>F8</kbd> | `CSI 1 9 ~`
|
||||
<kbd>F9</kbd> | `CSI 2 0 ~`
|
||||
<kbd>F10</kbd> | `CSI 2 1 ~`
|
||||
<kbd>F11</kbd> | `CSI 2 3 ~`
|
||||
<kbd>F12</kbd> | `CSI 2 4 ~`
|
||||
<kbd>Menu</kbd> | `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 <kbd>Home</kbd> and <kbd>End</kbd>, 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
|
||||
-----------------|-----------|-----------
|
||||
<kbd>Up</kbd> | `CSI A` | `ESC A`
|
||||
<kbd>Down</kbd> | `CSI B` | `ESC B`
|
||||
<kbd>Right</kbd> | `CSI C` | `ESC C`
|
||||
<kbd>Left</kbd> | `CSI D` | `ESC D`
|
||||
<kbd>End</kbd> | `CSI F` | `ESC F`
|
||||
<kbd>Home</kbd> | `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
|
||||
----------------|-----------
|
||||
<kbd>Ins</kbd> | `CSI 2 ~`
|
||||
<kbd>Del</kbd> | `CSI 3 ~`
|
||||
<kbd>PgUp</kbd> | `CSI 5 ~`
|
||||
<kbd>PgDn</kbd> | `CSI 6 ~`
|
||||
|
||||
**Numeric Keypad**
|
||||
|
||||
With <kbd>Num Lock</kbd> 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 <kbd>5</kbd> 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 <kbd>Shift</kbd>, which is the equivalent of enabling <kbd>Num Lock</kbd> (i.e. the keys just generate their corresponding digit characters or `.`). With <kbd>Num Lock</kbd> enabled, it's the other way arround - the digits are generated by default, while <kbd>Shift</kbd> enables the cursor/editing functionality.
|
||||
|
||||
Key | Alias | ANSI mode | VT52 mode
|
||||
-------------|-------|-----------|-----------
|
||||
<kbd>.</kbd> | Del | `CSI 3 ~` | `CSI 3 ~`
|
||||
<kbd>0</kbd> | Ins | `CSI 2 ~` | `CSI 2 ~`
|
||||
<kbd>1</kbd> | End | `CSI F` | `ESC F`
|
||||
<kbd>2</kbd> | Down | `CSI B` | `ESC B`
|
||||
<kbd>3</kbd> | PgDn | `CSI 6 ~` | `CSI 6 ~`
|
||||
<kbd>4</kbd> | Left | `CSI D` | `ESC D`
|
||||
<kbd>4</kbd> | Clear | `CSI E` | `ESC E`
|
||||
<kbd>6</kbd> | Right | `CSI C` | `ESC C`
|
||||
<kbd>7</kbd> | Home | `CSI H` | `ESC H`
|
||||
<kbd>8</kbd> | Up | `CSI A` | `ESC A`
|
||||
<kbd>9</kbd> | PgUp | `CSI 5 ~` | `CSI 5 ~`
|
||||
|
||||
When the DECKPAM _Alternate/Application Keypad Mode_ is set, though, the <kbd>Shift</kbd> 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 <kbd>Num Lock</kbd> is disabled.
|
||||
|
||||
Key | Alias | ANSI mode | VT52 mode
|
||||
-------------|-------|-----------|-----------
|
||||
<kbd>.</kbd> | Del | `SS3 2 n` | `ESC ? n`
|
||||
<kbd>0</kbd> | Ins | `SS3 2 p` | `ESC ? p`
|
||||
<kbd>1</kbd> | End | `SS3 2 q` | `ESC ? q `
|
||||
<kbd>2</kbd> | Down | `SS3 2 r` | `ESC ? r`
|
||||
<kbd>3</kbd> | PgDn | `SS3 2 s` | `ESC ? s`
|
||||
<kbd>4</kbd> | Left | `SS3 2 t` | `ESC ? t`
|
||||
<kbd>4</kbd> | Clear | `SS3 2 u` | `ESC ? u`
|
||||
<kbd>6</kbd> | Right | `SS3 2 v` | `ESC ? v`
|
||||
<kbd>7</kbd> | Home | `SS3 2 w` | `ESC ? w`
|
||||
<kbd>8</kbd> | Up | `SS3 2 x` | `ESC ? x`
|
||||
<kbd>9</kbd> | 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 <kbd>Enter</kbd> key). The sequences generated again correspond with the VT100/VT52 numeric keys (more or less), but this mapping is active even without the <kbd>Shift</kbd> modifier (and in VT52 mode all other modifiers are ignored too). As above, the mode only applies when <kbd>Num Lock</kbd> is disabled.
|
||||
|
||||
Key | ANSI mode | VT52 mode
|
||||
-----------------|-----------|-----------
|
||||
<kbd>*</kbd> | `SS3 j` | `ESC ? j`
|
||||
<kbd>+</kbd> | `SS3 k` | `ESC ? k`
|
||||
<kbd>-</kbd> | `SS3 m` | `ESC ? m`
|
||||
<kbd>/</kbd> | `SS3 o` | `ESC ? o`
|
||||
<kbd>Enter</kbd> | `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)
|
||||
|
||||
|
||||
|
||||
@@ -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 branches the pattern we use is `dev/<alias>/<whatever you want here>`. 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 branchs the pattern we use is `dev/<alias>/<whatever you want here>`. 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 conflicts and commit
|
||||
4. fix any merge conficts 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
|
||||
|
||||
@@ -67,12 +67,10 @@ 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)
|
||||
@@ -101,7 +99,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 JPG logo https://www.debian.org/logos/openlogo-100.jpg
|
||||
1. Download the Debian SVG logo https://www.debian.org/logos/openlogo.svg
|
||||
2. Put the image in the
|
||||
`$env:LocalAppData\Packages\Microsoft.WindowsTerminal_<randomString>\RoamingState\`
|
||||
directory (same directory as your `profiles.json` file).
|
||||
@@ -110,10 +108,9 @@ 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-100.jpg",
|
||||
"backgroundImageOpacity": 1,
|
||||
"backgroundImageStretchMode" : "none",
|
||||
"backgroundImageAlignment" : "topRight",
|
||||
"backgroundImage": "ms-appdata:///Roaming/openlogo.jpg",
|
||||
"backgroundImageOpacity": 0.3,
|
||||
"backgroundImageStretchMode": "Fill",
|
||||
```
|
||||
5. Make sure that `useAcrylic` is `false`.
|
||||
6. Save the file.
|
||||
@@ -129,47 +126,3 @@ 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 <kbd>ctrl+shift+c</kbd>
|
||||
and <kbd>ctrl+shift+v</kbd> respectively.
|
||||
|
||||
You can set the keybindings to whatever you'd like. If you prefer
|
||||
<kbd>ctrl+c</kbd> 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 <kbd>ctrl+shift+v</kbd> and
|
||||
<kbd>shift+Insert</kbd> to `paste`.
|
||||
|
||||
Note: If you set your copy keybinding to `"ctrl+c"`, you won't be able to send an interrupt to the commandline application using <kbd>Ctrl+C</kbd>. This is a bug, and being tracked by [#2258](https://github.com/microsoft/terminal/issues/2285).
|
||||
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"`
|
||||
|
||||
@@ -45,7 +45,7 @@ To choose a different shell (e.g. `cmd.exe` or WSL `bash`) then
|
||||
|
||||
## Starting a new PowerShell tab with admin privilege
|
||||
|
||||
There is no current plan to support this feature for security reasons. See issue [#623](https://github.com/microsoft/terminal/issues/632)
|
||||
There is no current plan to support this feature for security reaons. See issue [#623](https://github.com/microsoft/terminal/issues/632)
|
||||
|
||||
## Using cut and paste in the Terminal window
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace MiniTerm
|
||||
internal static Process Start(string command, IntPtr attributes, IntPtr hPC)
|
||||
{
|
||||
var startupInfo = ConfigureProcessThread(hPC, attributes);
|
||||
var processInfo = RunProcess(ref startupInfo, command);
|
||||
var processInfo = RunProcess(ref startupInfo, "cmd.exe");
|
||||
return new Process(startupInfo, processInfo);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,10 @@
|
||||
#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:
|
||||
@@ -560,37 +558,22 @@ 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:
|
||||
// - <none>
|
||||
//Return Value:
|
||||
// - Coordinate position in screen coordinates (offset coordinates, not array index coordinates).
|
||||
COORD TextBuffer::GetLastNonSpaceCharacter() const
|
||||
{
|
||||
return GetLastNonSpaceCharacter(GetSize());
|
||||
}
|
||||
|
||||
//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();
|
||||
COORD coordEndOfText;
|
||||
// Always search the whole buffer, by starting at the bottom.
|
||||
coordEndOfText.Y = GetSize().BottomInclusive();
|
||||
|
||||
const ROW* pCurrRow = &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<short>(pCurrRow->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.
|
||||
const auto viewportTop = viewport.Top();
|
||||
bool fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > viewportTop); // this row is empty, and we're not at the top
|
||||
bool fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > 0); // this row is empty, and we're not at the top
|
||||
while (fDoBackUp)
|
||||
{
|
||||
coordEndOfText.Y--;
|
||||
@@ -598,7 +581,7 @@ COORD TextBuffer::GetLastNonSpaceCharacter(const Microsoft::Console::Types::View
|
||||
// We need to back up to the previous row if this line is empty, AND there are more rows
|
||||
|
||||
coordEndOfText.X = static_cast<short>(pCurrRow->GetCharRow().MeasureRight()) - 1;
|
||||
fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > viewportTop);
|
||||
fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > 0);
|
||||
}
|
||||
|
||||
// don't allow negative results
|
||||
@@ -1045,178 +1028,3 @@ 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
|
||||
{
|
||||
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 =
|
||||
"<!DOCTYPE><HTML><HEAD><TITLE>" + htmlTitle + "</TITLE></HEAD><BODY>";
|
||||
htmlBuilder << htmlHeader;
|
||||
|
||||
htmlBuilder << "<!--StartFragment -->";
|
||||
|
||||
// apply global style in div element
|
||||
{
|
||||
htmlBuilder << "<DIV STYLE=\"";
|
||||
htmlBuilder << "display:inline-block;";
|
||||
htmlBuilder << "white-space:pre;";
|
||||
|
||||
// fixme: this is only walkaround for filling background after last char of row.
|
||||
// It is based on first char of first row, not the actual char at correct position.
|
||||
htmlBuilder << "background-color:";
|
||||
const COLORREF globalBgColor = rows.BkAttr.at(0).at(0);
|
||||
htmlBuilder << Utils::ColorToHexString(globalBgColor);
|
||||
htmlBuilder << ";";
|
||||
|
||||
htmlBuilder << "font-family:";
|
||||
if (fontFaceName[0] != '\0')
|
||||
{
|
||||
htmlBuilder << "'";
|
||||
htmlBuilder << ConvertToA(CP_UTF8, fontFaceName);
|
||||
htmlBuilder << "',";
|
||||
}
|
||||
// even with different font, add monospace as fallback
|
||||
htmlBuilder << "monospace;";
|
||||
|
||||
htmlBuilder << "font-size:";
|
||||
htmlBuilder << fontHeightPoints;
|
||||
htmlBuilder << "pt;";
|
||||
|
||||
// note: MS Word doesn't support padding (in this way at least)
|
||||
htmlBuilder << "padding:";
|
||||
htmlBuilder << 4; // todo: customizable padding
|
||||
htmlBuilder << "px;";
|
||||
|
||||
htmlBuilder << "\">";
|
||||
}
|
||||
|
||||
// copy text and info color from buffer
|
||||
bool hasWrittenAnyText = false;
|
||||
std::optional<COLORREF> fgColor = std::nullopt;
|
||||
std::optional<COLORREF> bkColor = std::nullopt;
|
||||
for (UINT row = 0; row < rows.text.size(); row++)
|
||||
{
|
||||
size_t startOffset = 0;
|
||||
|
||||
if (row != 0)
|
||||
{
|
||||
htmlBuilder << "<BR>";
|
||||
}
|
||||
|
||||
for (UINT col = 0; col < rows.text[row].length(); col++)
|
||||
{
|
||||
// do not include \r nor \n as they don't have attributes
|
||||
// and are not HTML friendly. For line break use '<BR>' instead.
|
||||
bool isLastCharInRow =
|
||||
col == rows.text[row].length() - 1 ||
|
||||
rows.text[row][col + 1] == '\r' ||
|
||||
rows.text[row][col + 1] == '\n';
|
||||
|
||||
bool colorChanged = false;
|
||||
if (!fgColor.has_value() || rows.FgAttr[row][col] != fgColor.value())
|
||||
{
|
||||
fgColor = rows.FgAttr[row][col];
|
||||
colorChanged = true;
|
||||
}
|
||||
|
||||
if (!bkColor.has_value() || rows.BkAttr[row][col] != bkColor.value())
|
||||
{
|
||||
bkColor = rows.BkAttr[row][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[row].data() + startOffset, col - startOffset + includeCurrent));
|
||||
startOffset = col;
|
||||
}
|
||||
};
|
||||
|
||||
if (colorChanged)
|
||||
{
|
||||
writeAccumulatedChars(false);
|
||||
|
||||
if (hasWrittenAnyText)
|
||||
{
|
||||
htmlBuilder << "</SPAN>";
|
||||
}
|
||||
|
||||
htmlBuilder << "<SPAN STYLE=\"";
|
||||
htmlBuilder << "color:";
|
||||
htmlBuilder << Utils::ColorToHexString(fgColor.value());
|
||||
htmlBuilder << ";";
|
||||
htmlBuilder << "background-color:";
|
||||
htmlBuilder << Utils::ColorToHexString(bkColor.value());
|
||||
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 << "</SPAN>";
|
||||
}
|
||||
|
||||
htmlBuilder << "</DIV>";
|
||||
|
||||
htmlBuilder << "<!--EndFragment -->";
|
||||
|
||||
constexpr std::string_view HtmlFooter = "</BODY></HTML>";
|
||||
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<size_t>(htmlBuilder.tellp());
|
||||
const size_t fragStartPos = ClipboardHeaderSize + gsl::narrow<size_t>(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 {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,6 @@ public:
|
||||
bool IncrementCircularBuffer();
|
||||
|
||||
COORD GetLastNonSpaceCharacter() const;
|
||||
COORD GetLastNonSpaceCharacter(const Microsoft::Console::Types::Viewport viewport) const;
|
||||
|
||||
Cursor& GetCursor();
|
||||
const Cursor& GetCursor() const;
|
||||
@@ -145,11 +144,6 @@ public:
|
||||
std::function<COLORREF(TextAttribute&)> GetForegroundColor,
|
||||
std::function<COLORREF(TextAttribute&)> GetBackgroundColor) const;
|
||||
|
||||
static std::string GenHTML(const TextAndColor& rows,
|
||||
const int fontHeightPoints,
|
||||
const PCWCHAR fontFaceName,
|
||||
const std::string& htmlTitle);
|
||||
|
||||
private:
|
||||
std::deque<ROW> _storage;
|
||||
Cursor _cursor;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(OpenConsoleDir)src\wap-common.build.pre.props" />
|
||||
<PropertyGroup Label="Version">
|
||||
<!-- These fields are picked up by PackageES -->
|
||||
<VersionMajor>0</VersionMajor>
|
||||
<VersionMinor>4</VersionMinor>
|
||||
<VersionMinor>2</VersionMinor>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration">
|
||||
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
|
||||
@@ -257,11 +257,27 @@
|
||||
<Content Include="ProfileIcons\{61c54bbd-c2c6-5271-96e7-009a87ff44bf}.scale-200.png" />
|
||||
<Content Include="ProfileIcons\{9acb9455-ca41-5af7-950f-6bca1bc9722f}.scale-100.png" />
|
||||
<Content Include="ProfileIcons\{9acb9455-ca41-5af7-950f-6bca1bc9722f}.scale-200.png" />
|
||||
<Content Include="ProfileIcons\{b453ae62-4e3d-5e58-b989-0a998ec441b8}.scale-100.png" />
|
||||
<Content Include="ProfileIcons\{b453ae62-4e3d-5e58-b989-0a998ec441b8}.scale-200.png" />
|
||||
<PRIResource Include="Resources\en-US\Resources.resw" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(OpenConsoleDir)src\wap-common.build.post.props" />
|
||||
<!--
|
||||
Microsoft.UI.Xaml contains some <Content> resource files that need to be included in our package.
|
||||
For some reason, they're not rolled up through dependent projects; if they were, their paths would
|
||||
be wrong.
|
||||
|
||||
WAP Packaging projects don't actually support nuget package references, so we added one manually.
|
||||
|
||||
This does mean that version changes to Microsoft.UI.Xaml must be manually reflected
|
||||
here.
|
||||
-->
|
||||
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.2.190611001-prerelease\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.2.190611001-prerelease\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>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}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.2.190611001-prerelease\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.2.190611001-prerelease\build\native\Microsoft.UI.Xaml.targets'))" />
|
||||
</Target>
|
||||
<!-- End workaround -->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WindowsTerminal\WindowsTerminal.vcxproj" />
|
||||
<ProjectReference Include="..\..\host\exe\Host.EXE.vcxproj" />
|
||||
@@ -279,8 +295,7 @@
|
||||
<!-- Override the filename for OpenConsole.exe (only) -->
|
||||
<TargetPath Condition="'%(Filename)' == 'OpenConsole' and '%(Extension)' == '.exe'">conhost.exe</TargetPath>
|
||||
<!-- Blank the SourceProject here to vend all files into the root of the package. -->
|
||||
<SourceProject>
|
||||
</SourceProject>
|
||||
<SourceProject></SourceProject>
|
||||
</_FilteredNonWapProjProjectOutput>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.18362.0" />
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.18362.0" MaxVersionTested="10.0.18362.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.18362.0" />
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.18362.0" MaxVersionTested="10.0.18362.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 314 B |
Binary file not shown.
|
Before Width: | Height: | Size: 573 B |
@@ -123,16 +123,61 @@
|
||||
<data name="AppName" xml:space="preserve">
|
||||
<value>Windows Terminal (Preview)</value>
|
||||
</data>
|
||||
<data name="InitialJsonParseErrorText" xml:space="preserve">
|
||||
<value>Settings could not be loaded from file - temporarily using the default settings. Check for syntax errors, including trailing commas.</value>
|
||||
</data>
|
||||
<data name="InitialJsonParseErrorTitle" xml:space="preserve">
|
||||
<value>Failed to load settings</value>
|
||||
</data>
|
||||
<data name="Ok" xml:space="preserve">
|
||||
<value>Ok</value>
|
||||
</data>
|
||||
<data name="ReloadJsonParseErrorText" xml:space="preserve">
|
||||
<value>Settings could not be reloaded from file. Check for syntax errors, including trailing commas.</value>
|
||||
</data>
|
||||
<data name="ReloadJsonParseErrorTitle" xml:space="preserve">
|
||||
<value>Failed to reload settings</value>
|
||||
</data>
|
||||
<data name="AppDescriptionDev" xml:space="preserve">
|
||||
<value>The Windows Terminal, but Unofficial</value>
|
||||
</data>
|
||||
<data name="AppNameDev" xml:space="preserve">
|
||||
<value>Windows Terminal (Dev Build)</value>
|
||||
</data>
|
||||
<data name="AboutTitleText" xml:space="preserve">
|
||||
<value>About</value>
|
||||
</data>
|
||||
<data name="VersionLabelText" xml:space="preserve">
|
||||
<value>Version:</value>
|
||||
</data>
|
||||
<data name="AppShortName" xml:space="preserve">
|
||||
<value>Terminal</value>
|
||||
</data>
|
||||
<data name="AppShortNameDev" xml:space="preserve">
|
||||
<value>Terminal (Dev)</value>
|
||||
</data>
|
||||
<data name="DocumentationLabelText" xml:space="preserve">
|
||||
<value>Documentation
|
||||
</value>
|
||||
</data>
|
||||
<data name="GettingStartedLabelText" xml:space="preserve">
|
||||
<value>Getting Started
|
||||
</value>
|
||||
</data>
|
||||
<data name="ReleaseNotesLabelText" xml:space="preserve">
|
||||
<value>Release Notes
|
||||
</value>
|
||||
</data>
|
||||
<data name="DocumentationUriValue" xml:space="preserve">
|
||||
<value>https://aka.ms/terminal-documentation</value>
|
||||
</data>
|
||||
<data name="GettingStartedUriValue" xml:space="preserve">
|
||||
<value>https://aka.ms/terminal-getting-started</value>
|
||||
</data>
|
||||
<data name="ReleaseNotesUriValue" xml:space="preserve">
|
||||
<value>https://aka.ms/terminal-release-notes</value>
|
||||
</data>
|
||||
<data name="FeedbackUriValue" xml:space="preserve">
|
||||
<value>https://aka.ms/terminal-feedback</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1,368 +0,0 @@
|
||||
// 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::CharReader>(Json::CharReaderBuilder::CharReaderBuilder().newCharReader());
|
||||
return true;
|
||||
}
|
||||
Json::Value VerifyParseSucceeded(std::string content);
|
||||
|
||||
private:
|
||||
std::unique_ptr<Json::CharReader> 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<size_t>(0), settings->_warnings.size());
|
||||
VERIFY_ARE_EQUAL(static_cast<size_t>(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<size_t>(1), settings->_warnings.size());
|
||||
VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingDefaultProfile, settings->_warnings.at(0));
|
||||
|
||||
VERIFY_ARE_EQUAL(static_cast<size_t>(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<size_t>(1), settings->_warnings.size());
|
||||
VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingDefaultProfile, settings->_warnings.at(0));
|
||||
|
||||
VERIFY_ARE_EQUAL(static_cast<size_t>(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<size_t>(0), settings->_warnings.size());
|
||||
VERIFY_ARE_EQUAL(static_cast<size_t>(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<size_t>(1), settings->_warnings.size());
|
||||
VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::DuplicateProfile, settings->_warnings.at(0));
|
||||
|
||||
VERIFY_ARE_EQUAL(static_cast<size_t>(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<size_t>(1), settings->_warnings.size());
|
||||
VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::DuplicateProfile, settings->_warnings.at(0));
|
||||
|
||||
VERIFY_ARE_EQUAL(static_cast<size_t>(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<size_t>(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<size_t>(2), settings->_profiles.size());
|
||||
VERIFY_ARE_EQUAL(settings->_globals.GetDefaultProfile(), settings->_profiles.at(0).GetGuid());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
// 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<Tab>(profileGuid, term);
|
||||
|
||||
VERIFY_IS_NOT_NULL(newTab);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" IgnorableNamespaces="uap">
|
||||
|
||||
<!-- This file is used as the Appxmanifest for tests that _need_ to run in a
|
||||
packaged environment. It will be copied to the test's OutDir as part of the
|
||||
PostBuid step. It's highly similar to the "PackagedCwaFullTrust" manifest that
|
||||
TAEF ships with, with the following modifications:
|
||||
|
||||
1. All of our winrt types are included in this manifest, including types from
|
||||
MUX.dll. Should this list of types ever change, we'll need to manually
|
||||
update this file. The easiest way of doing that is deploying the app from
|
||||
VS, then copying the Extensions from the Appxmanifest.xml that's generated
|
||||
under `src/cascadia/CascadiaPackage/bin/$(platform)/$(configuration)/appx`.
|
||||
|
||||
2. We also _NEED_ the two vclibs listed under the `PackageDependency` block.
|
||||
|
||||
If your test fails for whatever reason, it's likely possible you're testing a
|
||||
type that's _not_ included in this file for some reason. So, here be dragons. -->
|
||||
|
||||
<Identity Name="TerminalApp.LocalTests.Package"
|
||||
ProcessorArchitecture="neutral"
|
||||
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
|
||||
Version="1.0.0.0"
|
||||
ResourceId="en-us" />
|
||||
<Properties>
|
||||
<DisplayName>TerminalApp.LocalTests.Package Host Process</DisplayName>
|
||||
<PublisherDisplayName>Microsoft Corp.</PublisherDisplayName>
|
||||
<Logo>taef.png</Logo>
|
||||
<Description>TAEF Packaged Cwa FullTrust Application Host Process</Description>
|
||||
</Properties>
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.18362.0" MaxVersionTested="10.0.18362.0" />
|
||||
<PackageDependency Name="Microsoft.VCLibs.140.00.Debug" MinVersion="14.0.27023.1" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
|
||||
<PackageDependency Name="Microsoft.VCLibs.140.00.Debug.UWPDesktop" MinVersion="14.0.27027.1" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
|
||||
</Dependencies>
|
||||
<Resources>
|
||||
<Resource Language="en-us" />
|
||||
</Resources>
|
||||
<Applications>
|
||||
<Application Id="TE.ProcessHost" Executable="TE.ProcessHost.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap:VisualElements DisplayName="TAEF Packaged Cwa FullTrust Application Host Process" Square150x150Logo="taef.png" Square44x44Logo="taef.png" Description="TAEF Packaged Cwa Application Host Process" BackgroundColor="#222222">
|
||||
<uap:SplashScreen Image="taef.png" />
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust"/>
|
||||
</Capabilities>
|
||||
|
||||
|
||||
|
||||
</Package>
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
|
||||
<!-- This file is copied into ut_app/TerminalApp.Unit.Tests.manifest as part
|
||||
of the pre-build step for that project. Changes should only be made to the
|
||||
WindowsTerminal version of the file. -->
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 1903 -->
|
||||
<!-- See https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/xaml-islands -->
|
||||
<!-- "maxversiontested" is CASE SENSITIVE. Do not change this.-->
|
||||
<maxversiontested Id="10.0.18362.0"/>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -1,176 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(SolutionDir)\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(OpenConsoleDir)\src\common.build.pre.props" />
|
||||
|
||||
<!-- ========================= Headers ======================== -->
|
||||
<ItemGroup>
|
||||
<ClInclude Include="precomp.h" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- ========================= Cpp Files ======================== -->
|
||||
<ItemGroup>
|
||||
<ClCompile Include="SettingsTests.cpp" />
|
||||
<ClCompile Include="TabTests.cpp" />
|
||||
<ClCompile Include="precomp.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<!-- You _NEED_ to include this file and the jsoncpp IncludePath (below) if
|
||||
you want to use jsoncpp -->
|
||||
<ClCompile Include="$(OpenConsoleDir)\dep\jsoncpp\jsoncpp.cpp">
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- ========================= Project References ======================== -->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(OpenConsoleDir)\src\cascadia\TerminalApp\lib\TerminalAppLib.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)\src\types\lib\types.vcxproj" />
|
||||
|
||||
<!-- If you don't reference these projects here, the
|
||||
_ConsoleGenerateAdditionalWinmdManifests step won't gather the winmd's -->
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalSettings\TerminalSettings.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\TerminalControl.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalConnection\TerminalConnection.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\TerminalApp.vcxproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- ========================= Globals ======================== -->
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{CA5CAD1A-b11c-4ddb-a4fe-c3afae9b5506}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>TerminalAppLocalTests</RootNamespace>
|
||||
<ProjectName>LocalTests_TerminalApp</ProjectName>
|
||||
<TargetName>TerminalApp.LocalTests</TargetName>
|
||||
<WindowsTargetPlatformMinVersion>10.0.18362.0</WindowsTargetPlatformMinVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- ====================== Compiler & Linker Flags ===================== -->
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>..;$(OpenConsoleDir)\dep\jsoncpp\json;$(OpenConsoleDir)src\inc;$(OpenConsoleDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalApp\lib\Generated Files";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
|
||||
|
||||
<!-- Manually disable unreachable code warning, because jconcpp has a ton of that. -->
|
||||
<DisableSpecificWarnings>4702;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>WindowsApp.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<GenerateManifest>true</GenerateManifest>
|
||||
<EmbedManifest>true</EmbedManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
|
||||
<Import Project="$(OpenConsoleDir)src\common.build.dll.props" />
|
||||
<Import Project="$(OpenConsoleDir)src\common.build.post.props" />
|
||||
<Import Project="$(OpenConsoleDir)src\common.build.tests.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Manually change our outdir to be in a subdirectory. We don't really want
|
||||
to put our output in the bin root, because if we do, we'll copy
|
||||
TerminalApp.winmd to the bin root, and then every subsequent mdmerge step
|
||||
(in _any_ cppwinrt project) will automatically try to pick up
|
||||
TerminalApp.winmd as a dependency (which is just wrong). This MUST be done
|
||||
after importing common.build.post.props-->
|
||||
<OutDir>$(OpenConsoleDir)\bin\$(Platform)\$(Configuration)\$(ProjectName)\</OutDir>
|
||||
<IntDir>$(OpenConsoleDir)\obj\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<_CppWinrtBinRoot>"$(OpenConsoleDir)$(Platform)\$(Configuration)\"</_CppWinrtBinRoot>
|
||||
<!-- From Microsoft.UI.Xaml.targets -->
|
||||
<Native-Platform Condition="'$(Platform)' == 'Win32'">x86</Native-Platform>
|
||||
<Native-Platform Condition="'$(Platform)' != 'Win32'">$(Platform)</Native-Platform>
|
||||
<_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.2.190611001-prerelease\runtimes\win10-$(Native-Platform)\native\"</_MUXBinRoot>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- We actually can just straight up reference MUX here, it's fine -->
|
||||
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.2.190611001-prerelease\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.2.190611001-prerelease\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0-preview6.2\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0-preview6.2\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
|
||||
|
||||
<!-- This project will generate individual sxs manifests for each of our winrt libraries -->
|
||||
<Import Project="$(OpenConsoleDir)\build\rules\GenerateSxsManifestsFromWinmds.targets" />
|
||||
|
||||
<!-- This is important: actually add the _LocalTestsGenerateCombinedManifests
|
||||
target to the list of targets to run. -->
|
||||
<PropertyGroup>
|
||||
<BeforeLinkTargets Condition="'$(WindowsTargetPlatformVersion)' >= '10.0.18362.0'">
|
||||
$(BeforeLinkTargets);
|
||||
_LocalTestsGenerateCombinedManifests;
|
||||
_LocalTestsBuildAppxManifest;
|
||||
_LocalTestsCopyDependencies;
|
||||
</BeforeLinkTargets>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Step 1: Combine all our SxS manifests into a single SxS manifest. TAEF
|
||||
needs us to specify a single activation context at runtime, so we need a
|
||||
single file with all our types in it.-->
|
||||
<Target Name="_LocalTestsGenerateCombinedManifests"
|
||||
Inputs="@(_ConsoleWinmdManifest)"
|
||||
Outputs="$(OutDir)$(TargetName).manifest"
|
||||
DependsOnTargets="_ConsoleGenerateAdditionalWinmdManifests">
|
||||
|
||||
<Exec Command="mt.exe -manifest @(_ConsoleWinmdManifest, ' -manifest ') -out:$(OutDir)$(TargetName).manifest" />
|
||||
</Target>
|
||||
|
||||
<!-- Step 2: Take our combined SxS manifest, and use it to build an
|
||||
Appxmanifest.xml. We'll use the Appxmanifest.prototype.xml in this project's
|
||||
directory as a base, and the script will tak all our activatableClasses and
|
||||
turn them into appxmanifest-compatible Extensions -->
|
||||
<Target Name="_LocalTestsBuildAppxManifest"
|
||||
Inputs="$(OutDir)$(TargetName).manifest"
|
||||
Outputs="$(OutDir)$(TargetName).AppxManifest.xml"
|
||||
DependsOnTargets="_LocalTestsGenerateCombinedManifests">
|
||||
|
||||
<Exec Command="powershell.exe –ExecutionPolicy Unrestricted $(OpenConsoleDir)\tools\GenerateAppxFromManifest.ps1 -SxSManifest $(OutDir)$(TargetName).manifest -AppxManifestPrototype $(TargetName).AppxManifest.prototype.xml -OutPath $(OutDir)$(TargetName).AppxManifest.xml" />
|
||||
|
||||
</Target>
|
||||
|
||||
<!-- Step 3: Manually copy all our dependent DLLs into our OutDir. For SxS
|
||||
activation to work, they all need to be in the same directory as our test dll.
|
||||
This is using code that's heavliy cribbed from WindowsTerminal.vcxproj, which
|
||||
is already cribbed from the GetPackagingOutputs in
|
||||
Microsoft.*.AppxPackage.targets. We're filtering this list down to the dlls,
|
||||
pris and xbfs, because this list _can_ contain directories, which will make
|
||||
the Copy task explode. -->
|
||||
|
||||
<PropertyGroup>
|
||||
<_ContinueOnError Condition="'$(BuildingProject)' == 'true'">true</_ContinueOnError>
|
||||
<_ContinueOnError Condition="'$(BuildingProject)' != 'true'">false</_ContinueOnError>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- First gather the files... -->
|
||||
<Target Name="MyGetPackagingOutputs" Returns="@(MyPackagingOutputs)">
|
||||
<MSBuild
|
||||
Projects="@(ProjectReferenceWithConfiguration)"
|
||||
Targets="GetPackagingOutputs"
|
||||
BuildInParallel="$(BuildInParallel)"
|
||||
Properties="%(ProjectReferenceWithConfiguration.SetConfiguration); %(ProjectReferenceWithConfiguration.SetPlatform)"
|
||||
Condition="'@(ProjectReferenceWithConfiguration)' != ''
|
||||
and '%(ProjectReferenceWithConfiguration.BuildReference)' == 'true'
|
||||
and '%(ProjectReferenceWithConfiguration.ReferenceOutputAssembly)' == 'true'"
|
||||
ContinueOnError="$(_ContinueOnError)">
|
||||
<Output TaskParameter="TargetOutputs" ItemName="_PackagingOutputsFromOtherProjects"/>
|
||||
</MSBuild>
|
||||
|
||||
<ItemGroup>
|
||||
<MyPackagingOutputs Include="@(_PackagingOutputsFromOtherProjects)" Condition="'%(Extension)'=='.dll' Or '%(Extension)'=='.pri' Or '%(Extension)'=='.xbf'" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<!-- Then copy the files to our outdir -->
|
||||
<Target Name="_LocalTestsCopyDependencies"
|
||||
DependsOnTargets="MyGetPackagingOutputs">
|
||||
|
||||
<Copy SourceFiles="@(MyPackagingOutputs)"
|
||||
SkipUnchangedFiles="true"
|
||||
DestinationFolder="$(OutDir)"
|
||||
/>
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
@@ -1,4 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
@@ -1,53 +0,0 @@
|
||||
/*++
|
||||
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 <WexTestClass.h>
|
||||
#include <json.h>
|
||||
#include "consoletaeftemplates.hpp"
|
||||
|
||||
// Needed just for XamlIslands to work at all:
|
||||
#include <winrt/Windows.system.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
#include <winrt/Windows.UI.Xaml.Hosting.h>
|
||||
#include <windows.ui.xaml.hosting.desktopwindowxamlsource.h>
|
||||
|
||||
// Common includes for most tests:
|
||||
#include "../../inc/argb.h"
|
||||
#include "../../inc/conattrs.hpp"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
#include "../../inc/DefaultSettings.h"
|
||||
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
#include <winrt/windows.ui.core.h>
|
||||
#include <winrt/Windows.ui.input.h>
|
||||
#include <winrt/Windows.UI.Xaml.Controls.h>
|
||||
#include <winrt/Windows.UI.Xaml.Controls.Primitives.h>
|
||||
#include <winrt/Windows.ui.xaml.media.h>
|
||||
#include <winrt/Windows.ui.xaml.input.h>
|
||||
|
||||
#include <windows.ui.xaml.media.dxinterop.h>
|
||||
@@ -1,13 +0,0 @@
|
||||
// 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"
|
||||
@@ -1,69 +0,0 @@
|
||||
// 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>
|
||||
{
|
||||
ActionEventArgs() = default;
|
||||
ActionEventArgs(const TerminalApp::IActionArgs& args) :
|
||||
_ActionArgs{ args } {};
|
||||
GETSET_PROPERTY(IActionArgs, ActionArgs, nullptr);
|
||||
GETSET_PROPERTY(bool, Handled, false);
|
||||
};
|
||||
|
||||
struct CopyTextArgs : public CopyTextArgsT<CopyTextArgs>
|
||||
{
|
||||
CopyTextArgs() = default;
|
||||
GETSET_PROPERTY(bool, TrimWhitespace, false);
|
||||
};
|
||||
|
||||
struct NewTabWithProfileArgs : public NewTabWithProfileArgsT<NewTabWithProfileArgs>
|
||||
{
|
||||
NewTabWithProfileArgs() = default;
|
||||
GETSET_PROPERTY(int32_t, ProfileIndex, 0);
|
||||
};
|
||||
|
||||
struct SwitchToTabArgs : public SwitchToTabArgsT<SwitchToTabArgs>
|
||||
{
|
||||
SwitchToTabArgs() = default;
|
||||
GETSET_PROPERTY(int32_t, TabIndex, 0);
|
||||
};
|
||||
|
||||
struct ResizePaneArgs : public ResizePaneArgsT<ResizePaneArgs>
|
||||
{
|
||||
ResizePaneArgs() = default;
|
||||
GETSET_PROPERTY(TerminalApp::Direction, Direction, TerminalApp::Direction::Left);
|
||||
};
|
||||
|
||||
struct MoveFocusArgs : public MoveFocusArgsT<MoveFocusArgs>
|
||||
{
|
||||
MoveFocusArgs() = default;
|
||||
GETSET_PROPERTY(TerminalApp::Direction, Direction, TerminalApp::Direction::Left);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(ActionEventArgs);
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// 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; };
|
||||
};
|
||||
|
||||
}
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#include "App.g.cpp"
|
||||
#include "TerminalPage.h"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace winrt::Windows::ApplicationModel::DataTransfer;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
@@ -20,100 +19,20 @@ 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<std::wstring_view, 2> settingsLoadWarningsLabels {
|
||||
L"MissingDefaultProfileText",
|
||||
L"DuplicateProfileText"
|
||||
};
|
||||
static const std::array<std::wstring_view, 1> 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<std::size_t N>
|
||||
static winrt::hstring _GetMessageText(uint32_t index, std::array<std::wstring_view, N> 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<uint32_t>(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<uint32_t>(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<winrt::Windows::UI::Xaml::Media::Brush>();
|
||||
textRun.Foreground(brush);
|
||||
}
|
||||
|
||||
return textRun;
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
App::App() :
|
||||
@@ -121,8 +40,7 @@ namespace winrt::TerminalApp::implementation
|
||||
_tabs{},
|
||||
_loadedInitialSettings{ false },
|
||||
_settingsLoadedResult{ S_OK },
|
||||
_dialogLock{},
|
||||
_resourceLoader{ L"TerminalApp/Resources" }
|
||||
_dialogLock{}
|
||||
{
|
||||
// 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,
|
||||
@@ -143,12 +61,26 @@ namespace winrt::TerminalApp::implementation
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void App::Create()
|
||||
void App::Create(uint64_t hWnd)
|
||||
{
|
||||
// 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);
|
||||
_Create(hWnd);
|
||||
}
|
||||
|
||||
App::~App()
|
||||
{
|
||||
TraceLoggingUnregister(g_hTerminalAppProvider);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Create all of the initial UI elements of the Terminal app.
|
||||
// * Initializes the first terminal control, using the default profile,
|
||||
// and adds it to our list of tabs.
|
||||
void App::_Create(uint64_t parentHwnd)
|
||||
{
|
||||
/* !!! 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
|
||||
@@ -160,21 +92,15 @@ namespace winrt::TerminalApp::implementation
|
||||
_root = terminalPage.as<winrt::Windows::UI::Xaml::Controls::Control>();
|
||||
_tabContent = terminalPage->TabContent();
|
||||
_tabRow = terminalPage->TabRow();
|
||||
_tabView = _tabRow.TabView();
|
||||
_newTabButton = _tabRow.NewTabButton();
|
||||
_tabView = terminalPage->TabView();
|
||||
_newTabButton = terminalPage->NewTabButton();
|
||||
|
||||
if (_settings->GlobalSettings().GetShowTabsInTitlebar())
|
||||
_minMaxCloseControl = terminalPage->MinMaxCloseControl();
|
||||
_minMaxCloseControl.ParentWindowHandle(parentHwnd);
|
||||
|
||||
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);
|
||||
_minMaxCloseControl.Visibility(Visibility::Collapsed);
|
||||
}
|
||||
|
||||
// Event Bindings (Early)
|
||||
@@ -190,16 +116,6 @@ namespace winrt::TerminalApp::implementation
|
||||
_OpenNewTab(std::nullopt);
|
||||
|
||||
_tabContent.SizeChanged({ this, &App::_OnContentSizeChanged });
|
||||
|
||||
_ApplyTheme(_settings->GlobalSettings().GetRequestedTheme());
|
||||
|
||||
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:
|
||||
@@ -235,11 +151,6 @@ namespace winrt::TerminalApp::implementation
|
||||
// xaml tree somehow.
|
||||
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
|
||||
// won't inherit our RequestedTheme automagically.
|
||||
dialog.RequestedTheme(_settings->GlobalSettings().GetRequestedTheme());
|
||||
|
||||
// Display the dialog.
|
||||
Controls::ContentDialogResult result = co_await dialog.ShowAsync(Controls::ContentDialogPlacement::Popup);
|
||||
|
||||
@@ -258,105 +169,33 @@ namespace winrt::TerminalApp::implementation
|
||||
void App::_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");
|
||||
auto resourceLoader = Windows::ApplicationModel::Resources::ResourceLoader::GetForCurrentView();
|
||||
auto title = resourceLoader.GetString(titleKey);
|
||||
auto message = resourceLoader.GetString(contentKey);
|
||||
auto buttonText = resourceLoader.GetString(L"Ok");
|
||||
|
||||
_ShowDialog(winrt::box_value(title), winrt::box_value(message), buttonText);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - 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::_ShowLoadErrorsDialog(const winrt::hstring& titleKey,
|
||||
const winrt::hstring& contentKey)
|
||||
{
|
||||
auto title = _resourceLoader.GetLocalizedString(titleKey);
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
_ShowDialog(winrt::box_value(title), warningsTextBlock, buttonText);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - 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()
|
||||
{
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
_ShowDialog(winrt::box_value(title), warningsTextBlock, buttonText);
|
||||
}
|
||||
|
||||
// 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()
|
||||
{
|
||||
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");
|
||||
auto resourceLoader = Windows::ApplicationModel::Resources::ResourceLoader::GetForCurrentView();
|
||||
const auto title = resourceLoader.GetString(L"AboutTitleText");
|
||||
const auto versionLabel = resourceLoader.GetString(L"VersionLabelText");
|
||||
const auto gettingStartedLabel = resourceLoader.GetString(L"GettingStartedLabelText");
|
||||
const auto documentationLabel = resourceLoader.GetString(L"DocumentationLabelText");
|
||||
const auto releaseNotesLabel = resourceLoader.GetString(L"ReleaseNotesLabelText");
|
||||
const auto gettingStartedUriValue = resourceLoader.GetString(L"GettingStartedUriValue");
|
||||
const auto documentationUriValue = resourceLoader.GetString(L"DocumentationUriValue");
|
||||
const auto releaseNotesUriValue = resourceLoader.GetString(L"ReleaseNotesUriValue");
|
||||
const auto package = winrt::Windows::ApplicationModel::Package::Current();
|
||||
const auto packageName = package.DisplayName();
|
||||
const auto version = package.Id().Version();
|
||||
Windows::UI::Xaml::Media::SolidColorBrush blueBrush{ Windows::UI::ColorHelper::FromArgb(255, 0, 115, 207) };
|
||||
winrt::Windows::UI::Xaml::Documents::Run about;
|
||||
winrt::Windows::UI::Xaml::Documents::Run gettingStarted;
|
||||
winrt::Windows::UI::Xaml::Documents::Run documentation;
|
||||
@@ -397,7 +236,11 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::hstring aboutText{ aboutTextStream.str() };
|
||||
about.Text(aboutText);
|
||||
|
||||
const auto buttonText = _resourceLoader.GetLocalizedString(L"Ok");
|
||||
const auto buttonText = resourceLoader.GetString(L"Ok");
|
||||
|
||||
gettingStartedLink.Foreground(blueBrush);
|
||||
documentationLink.Foreground(blueBrush);
|
||||
releaseNotesLink.Foreground(blueBrush);
|
||||
|
||||
Controls::TextBlock aboutTextBlock;
|
||||
aboutTextBlock.Inlines().Append(about);
|
||||
@@ -424,11 +267,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
const winrt::hstring titleKey = L"InitialJsonParseErrorTitle";
|
||||
const winrt::hstring textKey = L"InitialJsonParseErrorText";
|
||||
_ShowLoadErrorsDialog(titleKey, textKey);
|
||||
}
|
||||
else if (_settingsLoadedResult == S_FALSE)
|
||||
{
|
||||
_ShowLoadWarningsDialog();
|
||||
_ShowOkDialog(titleKey, textKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -481,9 +320,7 @@ namespace winrt::TerminalApp::implementation
|
||||
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<int>(_settings->GetProfiles().size());
|
||||
for (int profileIndex = 0; profileIndex < profileCount; profileIndex++)
|
||||
for (int profileIndex = 0; profileIndex < _settings->GetProfiles().size(); profileIndex++)
|
||||
{
|
||||
const auto& profile = _settings->GetProfiles()[profileIndex];
|
||||
auto profileMenuItem = Controls::MenuFlyoutItem{};
|
||||
@@ -492,8 +329,7 @@ namespace winrt::TerminalApp::implementation
|
||||
if (profileIndex < 9)
|
||||
{
|
||||
// enum value for ShortcutAction::NewTabProfileX; 0==NewTabProfile0
|
||||
const auto action = static_cast<ShortcutAction>(profileIndex + static_cast<int>(ShortcutAction::NewTabProfile0));
|
||||
auto profileKeyChord = keyBindings.GetKeyBinding(action);
|
||||
auto profileKeyChord = keyBindings.GetKeyBinding(static_cast<ShortcutAction>(profileIndex + static_cast<int>(ShortcutAction::NewTabProfile0)));
|
||||
|
||||
// make sure we find one to display
|
||||
if (profileKeyChord)
|
||||
@@ -533,7 +369,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
// Create the settings button.
|
||||
auto settingsItem = Controls::MenuFlyoutItem{};
|
||||
settingsItem.Text(_resourceLoader.GetLocalizedString(L"SettingsMenuItem"));
|
||||
settingsItem.Text(L"Settings");
|
||||
|
||||
Controls::SymbolIcon ico{};
|
||||
ico.Symbol(Controls::Symbol::Setting);
|
||||
@@ -550,7 +386,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
// Create the feedback button.
|
||||
auto feedbackFlyout = Controls::MenuFlyoutItem{};
|
||||
feedbackFlyout.Text(_resourceLoader.GetLocalizedString(L"FeedbackMenuItem"));
|
||||
feedbackFlyout.Text(L"Feedback");
|
||||
|
||||
Controls::FontIcon feedbackIco{};
|
||||
feedbackIco.Glyph(L"\xE939");
|
||||
@@ -562,7 +398,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
// Create the about button.
|
||||
auto aboutFlyout = Controls::MenuFlyoutItem{};
|
||||
aboutFlyout.Text(_resourceLoader.GetLocalizedString(L"AboutMenuItem"));
|
||||
aboutFlyout.Text(L"About");
|
||||
|
||||
Controls::SymbolIcon aboutIco{};
|
||||
aboutIco.Symbol(Controls::Symbol::Help);
|
||||
@@ -575,17 +411,6 @@ namespace winrt::TerminalApp::implementation
|
||||
_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 App::_OpenNewTabDropdown()
|
||||
{
|
||||
Controls::Primitives::FlyoutShowOptions options{};
|
||||
options.Placement(Controls::Primitives::FlyoutPlacementMode::BottomEdgeAlignedLeft);
|
||||
_newTabButton.Flyout().ShowAt(_newTabButton, options);
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -626,11 +451,21 @@ namespace winrt::TerminalApp::implementation
|
||||
void App::_FeedbackButtonOnClick(const IInspectable&,
|
||||
const RoutedEventArgs&)
|
||||
{
|
||||
const auto feedbackUriValue = _resourceLoader.GetLocalizedString(L"FeedbackUriValue");
|
||||
const auto feedbackUriValue = Windows::ApplicationModel::Resources::ResourceLoader::GetForCurrentView().GetString(L"FeedbackUriValue");
|
||||
|
||||
winrt::Windows::System::Launcher::LaunchUriAsync({ feedbackUriValue });
|
||||
}
|
||||
|
||||
Windows::UI::Xaml::Controls::Border App::GetDragBar() noexcept
|
||||
{
|
||||
if (_minMaxCloseControl)
|
||||
{
|
||||
return _minMaxCloseControl.DragBar();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Called when the about button is clicked. See _ShowAboutDialog for more info.
|
||||
// Arguments:
|
||||
@@ -655,55 +490,48 @@ namespace winrt::TerminalApp::implementation
|
||||
// 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, &App::_HandleNewTab });
|
||||
bindings.OpenNewTabDropdown({ this, &App::_HandleOpenNewTabDropdown });
|
||||
bindings.DuplicateTab({ this, &App::_HandleDuplicateTab });
|
||||
bindings.CloseTab({ this, &App::_HandleCloseTab });
|
||||
bindings.ClosePane({ this, &App::_HandleClosePane });
|
||||
bindings.ScrollUp({ this, &App::_HandleScrollUp });
|
||||
bindings.ScrollDown({ this, &App::_HandleScrollDown });
|
||||
bindings.NextTab({ this, &App::_HandleNextTab });
|
||||
bindings.PrevTab({ this, &App::_HandlePrevTab });
|
||||
bindings.SplitVertical({ this, &App::_HandleSplitVertical });
|
||||
bindings.SplitHorizontal({ this, &App::_HandleSplitHorizontal });
|
||||
bindings.ScrollUpPage({ this, &App::_HandleScrollUpPage });
|
||||
bindings.ScrollDownPage({ this, &App::_HandleScrollDownPage });
|
||||
bindings.OpenSettings({ this, &App::_HandleOpenSettings });
|
||||
bindings.PasteText({ this, &App::_HandlePasteText });
|
||||
bindings.NewTabWithProfile({ this, &App::_HandleNewTabWithProfile });
|
||||
bindings.SwitchToTab({ this, &App::_HandleSwitchToTab });
|
||||
bindings.ResizePane({ this, &App::_HandleResizePane });
|
||||
bindings.MoveFocus({ this, &App::_HandleMoveFocus });
|
||||
bindings.CopyText({ this, &App::_HandleCopyText });
|
||||
bindings.NewTab([this]() { _OpenNewTab(std::nullopt); });
|
||||
bindings.DuplicateTab([this]() { _DuplicateTabViewItem(); });
|
||||
bindings.CloseTab([this]() { _CloseFocusedTab(); });
|
||||
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.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() noexcept
|
||||
[[nodiscard]] HRESULT App::_TryLoadSettings(const bool saveOnLoad) noexcept
|
||||
{
|
||||
HRESULT hr = E_FAIL;
|
||||
|
||||
try
|
||||
{
|
||||
auto newSettings = CascadiaSettings::LoadAll();
|
||||
auto newSettings = CascadiaSettings::LoadAll(saveOnLoad);
|
||||
_settings = std::move(newSettings);
|
||||
const auto& warnings = _settings->GetWarnings();
|
||||
hr = warnings.size() == 0 ? S_OK : S_FALSE;
|
||||
hr = S_OK;
|
||||
}
|
||||
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();
|
||||
@@ -729,7 +557,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();
|
||||
_settingsLoadedResult = _TryLoadSettings(true);
|
||||
|
||||
if (FAILED(_settingsLoadedResult))
|
||||
{
|
||||
@@ -809,24 +637,18 @@ 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();
|
||||
_settingsLoadedResult = _TryLoadSettings(false);
|
||||
|
||||
if (FAILED(_settingsLoadedResult))
|
||||
{
|
||||
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() {
|
||||
const winrt::hstring titleKey = L"ReloadJsonParseErrorTitle";
|
||||
const winrt::hstring textKey = L"ReloadJsonParseErrorText";
|
||||
_ShowLoadErrorsDialog(titleKey, textKey);
|
||||
_ShowOkDialog(titleKey, textKey);
|
||||
});
|
||||
|
||||
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.
|
||||
@@ -878,15 +700,16 @@ namespace winrt::TerminalApp::implementation
|
||||
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({});
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -899,25 +722,32 @@ namespace winrt::TerminalApp::implementation
|
||||
void App::_UpdateTitle(std::shared_ptr<Tab> tab)
|
||||
{
|
||||
auto newTabTitle = tab->GetFocusedTitle();
|
||||
tab->SetTabText(newTabTitle);
|
||||
const auto lastFocusedProfile = tab->GetFocusedProfile().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(newTabTitle);
|
||||
_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
|
||||
// root of the application.
|
||||
// - Update the current theme of the application. This will manually update
|
||||
// all of the elements in our UI to match the given theme.
|
||||
// Arguments:
|
||||
// - newTheme: The ElementTheme to apply to our elements.
|
||||
void App::_ApplyTheme(const Windows::UI::Xaml::ElementTheme& newTheme)
|
||||
{
|
||||
// Propagate the event to the host layer, so it can update its own UI
|
||||
_requestedThemeChangedHandlers(*this, newTheme);
|
||||
_root.RequestedTheme(newTheme);
|
||||
}
|
||||
|
||||
UIElement App::GetRoot() noexcept
|
||||
@@ -973,7 +803,7 @@ namespace winrt::TerminalApp::implementation
|
||||
const auto profiles = _settings->GetProfiles();
|
||||
|
||||
// If we don't have that many profiles, then do nothing.
|
||||
if (realIndex >= gsl::narrow<decltype(realIndex)>(profiles.size()))
|
||||
if (realIndex >= profiles.size())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -997,8 +827,6 @@ namespace winrt::TerminalApp::implementation
|
||||
"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));
|
||||
}
|
||||
@@ -1060,7 +888,7 @@ namespace winrt::TerminalApp::implementation
|
||||
_UpdateTitle(tab);
|
||||
});
|
||||
|
||||
term.GotFocus([this, weakTabPtr](auto&&, auto&&) {
|
||||
term.GetControl().GotFocus([this, weakTabPtr](auto&&, auto&&) {
|
||||
auto tab = weakTabPtr.lock();
|
||||
if (!tab)
|
||||
{
|
||||
@@ -1083,12 +911,20 @@ namespace winrt::TerminalApp::implementation
|
||||
// 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)
|
||||
void App::_CreateNewTabFromSettings(GUID profileGuid, TerminalSettings settings, std::optional<uint64_t> serverHandle)
|
||||
{
|
||||
// Initialize the new tab
|
||||
|
||||
// Create a connection based on the values in our settings object.
|
||||
const auto connection = _CreateConnectionFromSettings(profileGuid, settings);
|
||||
TerminalConnection::ITerminalConnection connection = nullptr;
|
||||
// Create a Conhost connection based on the values in our settings object.
|
||||
if (!serverHandle)
|
||||
{
|
||||
connection = TerminalConnection::ConhostConnection(settings.Commandline(), settings.StartingDirectory(), 30, 80, winrt::guid());
|
||||
}
|
||||
else
|
||||
{
|
||||
connection = TerminalConnection::ConhostConnection(serverHandle.value(), 30, 80, winrt::guid());
|
||||
}
|
||||
|
||||
TermControl term{ settings, connection };
|
||||
|
||||
@@ -1106,7 +942,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// Set this profile's tab to the icon the user specified
|
||||
if (profile != nullptr && profile->HasIcon())
|
||||
{
|
||||
newTab->UpdateIcon(profile->GetExpandedIconPath());
|
||||
tabViewItem.Icon(_GetIconFromProfile(*profile));
|
||||
}
|
||||
|
||||
tabViewItem.PointerPressed({ this, &App::_OnTabClick });
|
||||
@@ -1157,17 +993,6 @@ namespace winrt::TerminalApp::implementation
|
||||
_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<Tab> 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
|
||||
@@ -1211,31 +1036,15 @@ namespace winrt::TerminalApp::implementation
|
||||
_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:
|
||||
// - <none>
|
||||
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.
|
||||
// Return Value:
|
||||
// - true iff we we able to copy text (if a selection was active)
|
||||
bool App::_CopyText(const bool trimTrailingWhitespace)
|
||||
void App::_CopyText(const bool trimTrailingWhitespace)
|
||||
{
|
||||
const auto control = _GetFocusedControl();
|
||||
return control.CopySelectionToClipboard(trimTrailingWhitespace);
|
||||
control.CopySelectionToClipboard(trimTrailingWhitespace);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -1260,18 +1069,13 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
|
||||
// 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 App::_SelectTab(const int tabIndex)
|
||||
// - Sets focus to the desired tab.
|
||||
void App::_SelectTab(const int tabIndex)
|
||||
{
|
||||
if (tabIndex >= 0 && tabIndex < gsl::narrow_cast<decltype(tabIndex)>(_tabs.size()))
|
||||
if (tabIndex >= 0 && tabIndex < _tabs.size())
|
||||
{
|
||||
_SetFocusedTabIndex(tabIndex);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -1362,6 +1166,33 @@ namespace winrt::TerminalApp::implementation
|
||||
return { L"Windows Terminal" };
|
||||
}
|
||||
|
||||
void App::IncomingConnection(uint64_t serverHandle)
|
||||
{
|
||||
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this, serverHandle]() {
|
||||
// Getting Guid for default profile
|
||||
const auto globalSettings = _settings->GlobalSettings();
|
||||
auto profileGuid = globalSettings.GetDefaultProfile();
|
||||
TerminalSettings settings = _settings->MakeSettings(profileGuid);
|
||||
|
||||
_CreateNewTabFromSettings(profileGuid, settings, serverHandle);
|
||||
});
|
||||
}
|
||||
|
||||
void App::IncomingConnection(hstring cmdline, hstring workingDir)
|
||||
{
|
||||
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this, cmdline, workingDir]() {
|
||||
// Getting Guid for default profile
|
||||
const auto globalSettings = _settings->GlobalSettings();
|
||||
auto profileGuid = globalSettings.GetDefaultProfile();
|
||||
TerminalSettings settings = _settings->MakeSettings(profileGuid);
|
||||
|
||||
settings.Commandline(cmdline);
|
||||
settings.StartingDirectory(workingDir);
|
||||
|
||||
_CreateNewTabFromSettings(profileGuid, settings);
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Additional responses to clicking on a TabView's item. Currently, just remove tab with middle click
|
||||
// Arguments:
|
||||
@@ -1410,12 +1241,12 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
if (tabIndexFromControl == focusedTabIndex)
|
||||
{
|
||||
auto const tabCount = gsl::narrow_cast<decltype(focusedTabIndex)>(_tabs.size());
|
||||
if (focusedTabIndex >= tabCount)
|
||||
if (focusedTabIndex >= _tabs.size())
|
||||
{
|
||||
focusedTabIndex = tabCount - 1;
|
||||
focusedTabIndex = _tabs.size() - 1;
|
||||
}
|
||||
else if (focusedTabIndex < 0)
|
||||
|
||||
if (focusedTabIndex < 0)
|
||||
{
|
||||
focusedTabIndex = 0;
|
||||
}
|
||||
@@ -1435,7 +1266,25 @@ namespace winrt::TerminalApp::implementation
|
||||
// - an IconElement for the profile's icon, if it has one.
|
||||
Controls::IconElement App::_GetIconFromProfile(const Profile& profile)
|
||||
{
|
||||
return profile.HasIcon() ? GetColoredIcon(profile.GetExpandedIconPath()) : Controls::IconElement{ nullptr };
|
||||
if (profile.HasIcon())
|
||||
{
|
||||
auto path = profile.GetIconPath();
|
||||
winrt::hstring iconPath{ path };
|
||||
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()
|
||||
@@ -1488,21 +1337,14 @@ namespace winrt::TerminalApp::implementation
|
||||
_settings->GlobalSettings().GetDefaultProfile();
|
||||
const auto controlSettings = _settings->MakeSettings(realGuid);
|
||||
|
||||
const auto controlConnection = _CreateConnectionFromSettings(realGuid, controlSettings);
|
||||
// Create a Conhost connection based on the values in our settings object.
|
||||
TerminalConnection::ITerminalConnection controlConnection = TerminalConnection::ConhostConnection(controlSettings.Commandline(), controlSettings.StartingDirectory(), 30, 80, winrt::guid());
|
||||
|
||||
TermControl newControl{ controlSettings, controlConnection };
|
||||
|
||||
const int focusedTabIndex = _GetFocusedTabIndex();
|
||||
auto focusedTab = _tabs[focusedTabIndex];
|
||||
|
||||
const auto canSplit = splitType == Pane::SplitState::Horizontal ? focusedTab->CanAddHorizontalSplit() :
|
||||
focusedTab->CanAddVerticalSplit();
|
||||
|
||||
if (!canSplit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TermControl newControl{ controlSettings, controlConnection };
|
||||
|
||||
// Hookup our event handlers to the new terminal
|
||||
_RegisterTerminalEvents(newControl, focusedTab);
|
||||
|
||||
@@ -1532,25 +1374,16 @@ namespace winrt::TerminalApp::implementation
|
||||
// terminal control raises it's CopyToClipboard event.
|
||||
// Arguments:
|
||||
// - copiedData: the new string content to place on the clipboard.
|
||||
void App::_CopyToClipboardHandler(const IInspectable& /*sender*/,
|
||||
const winrt::Microsoft::Terminal::TerminalControl::CopyToClipboardEventArgs& copiedData)
|
||||
void App::_CopyToClipboardHandler(const winrt::hstring& copiedData)
|
||||
{
|
||||
_root.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);
|
||||
}
|
||||
|
||||
dataPack.SetText(copiedData);
|
||||
Clipboard::SetContent(dataPack);
|
||||
Clipboard::Flush();
|
||||
|
||||
// TODO: MSFT 20642290 and 20642291
|
||||
// rtf copy and html copy
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1568,42 +1401,13 @@ namespace winrt::TerminalApp::implementation
|
||||
});
|
||||
}
|
||||
|
||||
// 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 App::_SetAcceleratorForMenuItem(Controls::MenuFlyoutItem& menuItem,
|
||||
const winrt::Microsoft::Terminal::Settings::KeyChord& keyChord)
|
||||
void App::_SetAcceleratorForMenuItem(Windows::UI::Xaml::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)
|
||||
{
|
||||
@@ -1623,79 +1427,10 @@ namespace winrt::TerminalApp::implementation
|
||||
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<wchar_t>(mappedCh));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
const auto* const profile = _settings->FindProfile(profileGuid);
|
||||
TerminalConnection::ITerminalConnection connection{ nullptr };
|
||||
|
||||
GUID connectionType{ 0 };
|
||||
if (profile->HasConnectionType())
|
||||
{
|
||||
connectionType = profile->GetConnectionType();
|
||||
}
|
||||
|
||||
if (profile->HasConnectionType() &&
|
||||
profile->GetConnectionType() == AzureConnectionType &&
|
||||
TerminalConnection::AzureConnection::IsAzureConnectionAvailable())
|
||||
{
|
||||
connection = TerminalConnection::AzureConnection(settings.InitialRows(),
|
||||
settings.InitialCols());
|
||||
}
|
||||
else
|
||||
{
|
||||
connection = TerminalConnection::ConhostConnection(settings.Commandline(),
|
||||
settings.StartingDirectory(),
|
||||
settings.StartingTitle(),
|
||||
settings.InitialRows(),
|
||||
settings.InitialCols(),
|
||||
winrt::guid());
|
||||
}
|
||||
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalAppProvider,
|
||||
"ConnectionCreated",
|
||||
TraceLoggingDescription("Event emitted upon the creation of a connection"),
|
||||
TraceLoggingGuid(connectionType, "ConnectionTypeGuid", "The type of the connection"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - 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:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void App::TitlebarClicked()
|
||||
{
|
||||
if (_newTabButton && _newTabButton.Flyout())
|
||||
{
|
||||
_newTabButton.Flyout().Hide();
|
||||
auto overrideString = AppKeyBindings::FormatOverrideShortcutText(keyChord.Modifiers());
|
||||
menuItem.KeyboardAcceleratorTextOverride(overrideString + L" ,");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1704,6 +1439,4 @@ namespace winrt::TerminalApp::implementation
|
||||
// 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, UIElement);
|
||||
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(App, RequestedThemeChanged, _requestedThemeChangedHandlers, TerminalApp::App, ElementTheme);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "CascadiaSettings.h"
|
||||
#include "App.g.h"
|
||||
#include "App.base.h"
|
||||
#include "ScopedResourceLoader.h"
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
#include <winrt/Microsoft.Terminal.TerminalControl.h>
|
||||
@@ -28,22 +27,25 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
Windows::UI::Xaml::UIElement GetRoot() noexcept;
|
||||
|
||||
void Create();
|
||||
// Gets the current dragglable area in the non client region of the top level window
|
||||
Windows::UI::Xaml::Controls::Border GetDragBar() noexcept;
|
||||
|
||||
void Create(uint64_t hParentWnd);
|
||||
void LoadSettings();
|
||||
|
||||
Windows::Foundation::Point GetLaunchDimensions(uint32_t dpi);
|
||||
bool GetShowTabsInTitlebar();
|
||||
|
||||
~App() = default;
|
||||
~App();
|
||||
|
||||
hstring GetTitle();
|
||||
void TitlebarClicked();
|
||||
|
||||
void IncomingConnection(uint64_t serverHandle);
|
||||
void IncomingConnection(hstring cmdline, hstring workingDir);
|
||||
|
||||
// -------------------------------- 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(RequestedThemeChanged, _requestedThemeChangedHandlers, TerminalApp::App, winrt::Windows::UI::Xaml::ElementTheme);
|
||||
|
||||
private:
|
||||
// If you add controls here, but forget to null them either here or in
|
||||
@@ -54,38 +56,34 @@ namespace winrt::TerminalApp::implementation
|
||||
// (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 _tabRow{ nullptr };
|
||||
Windows::UI::Xaml::Controls::Grid _tabContent{ nullptr };
|
||||
Windows::UI::Xaml::Controls::SplitButton _newTabButton{ nullptr };
|
||||
winrt::TerminalApp::MinMaxCloseControl _minMaxCloseControl{ nullptr };
|
||||
|
||||
std::vector<std::shared_ptr<Tab>> _tabs;
|
||||
|
||||
std::unique_ptr<::TerminalApp::CascadiaSettings> _settings;
|
||||
|
||||
HRESULT _settingsLoadedResult;
|
||||
winrt::hstring _settingsLoadExceptionText{};
|
||||
|
||||
bool _loadedInitialSettings;
|
||||
std::shared_mutex _dialogLock;
|
||||
|
||||
ScopedResourceLoader _resourceLoader;
|
||||
|
||||
wil::unique_folder_change_reader_nothrow _reader;
|
||||
|
||||
std::atomic<bool> _settingsReloadQueued{ false };
|
||||
|
||||
void _Create(uint64_t parentHWnd);
|
||||
void _CreateNewTabFlyout();
|
||||
void _OpenNewTabDropdown();
|
||||
|
||||
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 _ShowLoadWarningsDialog();
|
||||
void _ShowLoadErrorsDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey);
|
||||
|
||||
[[nodiscard]] HRESULT _TryLoadSettings() noexcept;
|
||||
[[nodiscard]] HRESULT _TryLoadSettings(const bool saveOnLoad) noexcept;
|
||||
void _LoadSettings();
|
||||
void _OpenSettings();
|
||||
|
||||
@@ -105,21 +103,19 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void _RegisterTerminalEvents(Microsoft::Terminal::TerminalControl::TermControl term, std::shared_ptr<Tab> 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 _CreateNewTabFromSettings(GUID profileGuid, winrt::Microsoft::Terminal::Settings::TerminalSettings settings, std::optional<uint64_t> serverHandle = std::nullopt);
|
||||
|
||||
void _OpenNewTab(std::optional<int> profileIndex);
|
||||
void _DuplicateTabViewItem();
|
||||
void _CloseFocusedTab();
|
||||
void _CloseFocusedPane();
|
||||
void _SelectNextTab(const bool bMoveRight);
|
||||
bool _SelectTab(const int tabIndex);
|
||||
void _SelectTab(const int tabIndex);
|
||||
|
||||
void _SetFocusedTabIndex(int tabIndex);
|
||||
int _GetFocusedTabIndex() const;
|
||||
|
||||
void _Scroll(int delta);
|
||||
bool _CopyText(const bool trimTrailingWhitespace);
|
||||
void _CopyText(const bool trimTrailingWhitespace);
|
||||
void _PasteText();
|
||||
void _SplitVertical(const std::optional<GUID>& profileGuid);
|
||||
void _SplitHorizontal(const std::optional<GUID>& profileGuid);
|
||||
@@ -129,7 +125,6 @@ namespace winrt::TerminalApp::implementation
|
||||
// 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);
|
||||
@@ -146,34 +141,10 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
winrt::Microsoft::Terminal::TerminalControl::TermControl _GetFocusedControl();
|
||||
|
||||
void _CopyToClipboardHandler(const IInspectable& sender, const winrt::Microsoft::Terminal::TerminalControl::CopyToClipboardEventArgs& copiedData);
|
||||
void _CopyToClipboardHandler(const winrt::hstring& 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);
|
||||
|
||||
#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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
namespace TerminalApp
|
||||
{
|
||||
delegate void LastTabClosedEventArgs();
|
||||
[default_interface] runtimeclass App : Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication
|
||||
[default_interface]
|
||||
runtimeclass App : Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication
|
||||
{
|
||||
App();
|
||||
|
||||
@@ -15,22 +16,22 @@ namespace TerminalApp
|
||||
// then it might look like TermApp just failed to activate, which will
|
||||
// cause you to chase down the rabbit hole of "why is TermApp not
|
||||
// registered?" when it definitely is.
|
||||
void Create();
|
||||
void Create(UInt64 hParentWnd);
|
||||
|
||||
void LoadSettings();
|
||||
|
||||
Windows.UI.Xaml.UIElement GetRoot();
|
||||
Windows.UI.Xaml.Controls.Border GetDragBar{ get; };
|
||||
|
||||
Windows.Foundation.Point GetLaunchDimensions(UInt32 dpi);
|
||||
Boolean GetShowTabsInTitlebar();
|
||||
|
||||
event Microsoft.Terminal.TerminalControl.TitleChangedEventArgs TitleChanged;
|
||||
event LastTabClosedEventArgs LastTabClosed;
|
||||
event Windows.Foundation.TypedEventHandler<App, Windows.UI.Xaml.UIElement> SetTitleBarContent;
|
||||
event Windows.Foundation.TypedEventHandler<App, Windows.UI.Xaml.ElementTheme> RequestedThemeChanged;
|
||||
|
||||
String GetTitle();
|
||||
|
||||
void TitlebarClicked();
|
||||
void IncomingConnection(UInt64 serverHandle);
|
||||
void IncomingConnection(String cmdline, String workingDir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,11 +44,6 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
<Setter Target="CloseButtonStyle" Value="{StaticResource AccentButtonStyle}" />
|
||||
</Style>
|
||||
|
||||
<!-- We need to manually create the error text brush as a
|
||||
theme-dependent brush. SystemControlErrorTextForegroundBrush
|
||||
is unfortunately static. -->
|
||||
<SolidColorBrush x:Name="ErrorTextBrush" Color="{ThemeResource SystemErrorTextColor}" />
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<!-- Define resources for Dark mode here -->
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
// 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 App::_HandleNewTab(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
_OpenNewTab(std::nullopt);
|
||||
args.Handled(true);
|
||||
}
|
||||
void App::_HandleOpenNewTabDropdown(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
_OpenNewTabDropdown();
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void App::_HandleDuplicateTab(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
_DuplicateTabViewItem();
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void App::_HandleCloseTab(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
_CloseFocusedTab();
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void App::_HandleClosePane(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
_CloseFocusedPane();
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void App::_HandleScrollUp(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
_Scroll(-1);
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void App::_HandleScrollDown(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
_Scroll(1);
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void App::_HandleNextTab(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
_SelectNextTab(true);
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void App::_HandlePrevTab(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
_SelectNextTab(false);
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void App::_HandleSplitVertical(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
_SplitVertical(std::nullopt);
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void App::_HandleSplitHorizontal(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
_SplitHorizontal(std::nullopt);
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void App::_HandleScrollUpPage(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
_ScrollPage(-1);
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void App::_HandleScrollDownPage(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
_ScrollPage(1);
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void App::_HandleOpenSettings(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
_OpenSettings();
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void App::_HandlePasteText(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
_PasteText();
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void App::_HandleNewTabWithProfile(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::NewTabWithProfileArgs>())
|
||||
{
|
||||
_OpenNewTab({ realArgs.ProfileIndex() });
|
||||
args.Handled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void App::_HandleSwitchToTab(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::SwitchToTabArgs>())
|
||||
{
|
||||
const auto handled = _SelectTab({ realArgs.TabIndex() });
|
||||
args.Handled(handled);
|
||||
}
|
||||
}
|
||||
|
||||
void App::_HandleResizePane(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::ResizePaneArgs>())
|
||||
{
|
||||
_ResizePane(realArgs.Direction());
|
||||
args.Handled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void App::_HandleMoveFocus(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::MoveFocusArgs>())
|
||||
{
|
||||
_MoveFocus(realArgs.Direction());
|
||||
args.Handled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void App::_HandleCopyText(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::CopyTextArgs>())
|
||||
{
|
||||
const auto handled = _CopyText(realArgs.TrimWhitespace());
|
||||
args.Handled(handled);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
using namespace winrt::Microsoft::Terminal;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace winrt::Windows::Data::Json;
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
@@ -46,337 +47,129 @@ namespace winrt::TerminalApp::implementation
|
||||
switch (action)
|
||||
{
|
||||
case ShortcutAction::CopyText:
|
||||
{
|
||||
auto args = winrt::make_self<CopyTextArgs>();
|
||||
args->TrimWhitespace(true);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_CopyTextHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_CopyTextHandlers(true);
|
||||
return true;
|
||||
case ShortcutAction::CopyTextWithoutNewlines:
|
||||
{
|
||||
auto args = winrt::make_self<CopyTextArgs>();
|
||||
args->TrimWhitespace(false);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_CopyTextHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_CopyTextHandlers(false);
|
||||
return true;
|
||||
case ShortcutAction::PasteText:
|
||||
{
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>();
|
||||
_PasteTextHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_PasteTextHandlers();
|
||||
return true;
|
||||
case ShortcutAction::NewTab:
|
||||
{
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>();
|
||||
_NewTabHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
case ShortcutAction::OpenNewTabDropdown:
|
||||
{
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>();
|
||||
_OpenNewTabDropdownHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_NewTabHandlers();
|
||||
return true;
|
||||
case ShortcutAction::DuplicateTab:
|
||||
{
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>();
|
||||
_DuplicateTabHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_DuplicateTabHandlers();
|
||||
return true;
|
||||
case ShortcutAction::OpenSettings:
|
||||
{
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>();
|
||||
_OpenSettingsHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_OpenSettingsHandlers();
|
||||
return true;
|
||||
|
||||
case ShortcutAction::NewTabProfile0:
|
||||
{
|
||||
auto args = winrt::make_self<NewTabWithProfileArgs>();
|
||||
args->ProfileIndex(0);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_NewTabWithProfileHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_NewTabWithProfileHandlers(0);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile1:
|
||||
{
|
||||
auto args = winrt::make_self<NewTabWithProfileArgs>();
|
||||
args->ProfileIndex(1);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_NewTabWithProfileHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_NewTabWithProfileHandlers(1);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile2:
|
||||
{
|
||||
auto args = winrt::make_self<NewTabWithProfileArgs>();
|
||||
args->ProfileIndex(2);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_NewTabWithProfileHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_NewTabWithProfileHandlers(2);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile3:
|
||||
{
|
||||
auto args = winrt::make_self<NewTabWithProfileArgs>();
|
||||
args->ProfileIndex(3);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_NewTabWithProfileHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_NewTabWithProfileHandlers(3);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile4:
|
||||
{
|
||||
auto args = winrt::make_self<NewTabWithProfileArgs>();
|
||||
args->ProfileIndex(4);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_NewTabWithProfileHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_NewTabWithProfileHandlers(4);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile5:
|
||||
{
|
||||
auto args = winrt::make_self<NewTabWithProfileArgs>();
|
||||
args->ProfileIndex(5);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_NewTabWithProfileHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_NewTabWithProfileHandlers(5);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile6:
|
||||
{
|
||||
auto args = winrt::make_self<NewTabWithProfileArgs>();
|
||||
args->ProfileIndex(6);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_NewTabWithProfileHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_NewTabWithProfileHandlers(6);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile7:
|
||||
{
|
||||
auto args = winrt::make_self<NewTabWithProfileArgs>();
|
||||
args->ProfileIndex(7);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_NewTabWithProfileHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_NewTabWithProfileHandlers(7);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile8:
|
||||
{
|
||||
auto args = winrt::make_self<NewTabWithProfileArgs>();
|
||||
args->ProfileIndex(8);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_NewTabWithProfileHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_NewTabWithProfileHandlers(8);
|
||||
return true;
|
||||
|
||||
case ShortcutAction::NewWindow:
|
||||
{
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>();
|
||||
_NewWindowHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_NewWindowHandlers();
|
||||
return true;
|
||||
case ShortcutAction::CloseWindow:
|
||||
{
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>();
|
||||
_CloseWindowHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_CloseWindowHandlers();
|
||||
return true;
|
||||
case ShortcutAction::CloseTab:
|
||||
{
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>();
|
||||
_CloseTabHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
case ShortcutAction::ClosePane:
|
||||
{
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>();
|
||||
_ClosePaneHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_CloseTabHandlers();
|
||||
return true;
|
||||
|
||||
case ShortcutAction::ScrollUp:
|
||||
{
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>();
|
||||
_ScrollUpHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_ScrollUpHandlers();
|
||||
return true;
|
||||
case ShortcutAction::ScrollDown:
|
||||
{
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>();
|
||||
_ScrollDownHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_ScrollDownHandlers();
|
||||
return true;
|
||||
case ShortcutAction::ScrollUpPage:
|
||||
{
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>();
|
||||
_ScrollUpPageHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_ScrollUpPageHandlers();
|
||||
return true;
|
||||
case ShortcutAction::ScrollDownPage:
|
||||
{
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>();
|
||||
_ScrollDownPageHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_ScrollDownPageHandlers();
|
||||
return true;
|
||||
|
||||
case ShortcutAction::NextTab:
|
||||
{
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>();
|
||||
_NextTabHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_NextTabHandlers();
|
||||
return true;
|
||||
case ShortcutAction::PrevTab:
|
||||
{
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>();
|
||||
_PrevTabHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_PrevTabHandlers();
|
||||
return true;
|
||||
|
||||
case ShortcutAction::SplitVertical:
|
||||
{
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>();
|
||||
_SplitVerticalHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_SplitVerticalHandlers();
|
||||
return true;
|
||||
case ShortcutAction::SplitHorizontal:
|
||||
{
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>();
|
||||
_SplitHorizontalHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_SplitHorizontalHandlers();
|
||||
return true;
|
||||
|
||||
case ShortcutAction::SwitchToTab0:
|
||||
{
|
||||
auto args = winrt::make_self<SwitchToTabArgs>();
|
||||
args->TabIndex(0);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_SwitchToTabHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_SwitchToTabHandlers(0);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab1:
|
||||
{
|
||||
auto args = winrt::make_self<SwitchToTabArgs>();
|
||||
args->TabIndex(1);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_SwitchToTabHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_SwitchToTabHandlers(1);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab2:
|
||||
{
|
||||
auto args = winrt::make_self<SwitchToTabArgs>();
|
||||
args->TabIndex(2);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_SwitchToTabHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_SwitchToTabHandlers(2);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab3:
|
||||
{
|
||||
auto args = winrt::make_self<SwitchToTabArgs>();
|
||||
args->TabIndex(3);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_SwitchToTabHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_SwitchToTabHandlers(3);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab4:
|
||||
{
|
||||
auto args = winrt::make_self<SwitchToTabArgs>();
|
||||
args->TabIndex(4);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_SwitchToTabHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_SwitchToTabHandlers(4);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab5:
|
||||
{
|
||||
auto args = winrt::make_self<SwitchToTabArgs>();
|
||||
args->TabIndex(5);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_SwitchToTabHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_SwitchToTabHandlers(5);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab6:
|
||||
{
|
||||
auto args = winrt::make_self<SwitchToTabArgs>();
|
||||
args->TabIndex(6);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_SwitchToTabHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_SwitchToTabHandlers(6);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab7:
|
||||
{
|
||||
auto args = winrt::make_self<SwitchToTabArgs>();
|
||||
args->TabIndex(7);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_SwitchToTabHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_SwitchToTabHandlers(7);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab8:
|
||||
{
|
||||
auto args = winrt::make_self<SwitchToTabArgs>();
|
||||
args->TabIndex(8);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_SwitchToTabHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_SwitchToTabHandlers(8);
|
||||
return true;
|
||||
case ShortcutAction::ResizePaneLeft:
|
||||
{
|
||||
auto args = winrt::make_self<ResizePaneArgs>();
|
||||
args->Direction(Direction::Left);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_ResizePaneHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_ResizePaneHandlers(Direction::Left);
|
||||
return true;
|
||||
case ShortcutAction::ResizePaneRight:
|
||||
{
|
||||
auto args = winrt::make_self<ResizePaneArgs>();
|
||||
args->Direction(Direction::Right);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_ResizePaneHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_ResizePaneHandlers(Direction::Right);
|
||||
return true;
|
||||
case ShortcutAction::ResizePaneUp:
|
||||
{
|
||||
auto args = winrt::make_self<ResizePaneArgs>();
|
||||
args->Direction(Direction::Up);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_ResizePaneHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_ResizePaneHandlers(Direction::Up);
|
||||
return true;
|
||||
case ShortcutAction::ResizePaneDown:
|
||||
{
|
||||
auto args = winrt::make_self<ResizePaneArgs>();
|
||||
args->Direction(Direction::Down);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_ResizePaneHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
case ShortcutAction::MoveFocusLeft:
|
||||
{
|
||||
auto args = winrt::make_self<MoveFocusArgs>();
|
||||
args->Direction(Direction::Left);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_MoveFocusHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
case ShortcutAction::MoveFocusRight:
|
||||
{
|
||||
auto args = winrt::make_self<MoveFocusArgs>();
|
||||
args->Direction(Direction::Right);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_MoveFocusHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
case ShortcutAction::MoveFocusUp:
|
||||
{
|
||||
auto args = winrt::make_self<MoveFocusArgs>();
|
||||
args->Direction(Direction::Up);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_MoveFocusHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
case ShortcutAction::MoveFocusDown:
|
||||
{
|
||||
auto args = winrt::make_self<MoveFocusArgs>();
|
||||
args->Direction(Direction::Down);
|
||||
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
|
||||
_MoveFocusHandlers(*this, *eventArgs);
|
||||
return eventArgs->Handled();
|
||||
}
|
||||
_ResizePaneHandlers(Direction::Down);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -407,4 +200,56 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
return keyModifiers;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Handles the special case of providing a text override for the UI shortcut due to VK_OEM_COMMA 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 WinRT hstring 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
|
||||
winrt::hstring AppKeyBindings::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 winrt::hstring{ buffer };
|
||||
}
|
||||
|
||||
// -------------------------------- 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, 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);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppKeyBindings.g.h"
|
||||
#include "ActionArgs.h"
|
||||
#include "..\inc\cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
@@ -38,32 +37,30 @@ namespace winrt::TerminalApp::implementation
|
||||
Microsoft::Terminal::Settings::KeyChord GetKeyBinding(TerminalApp::ShortcutAction const& action);
|
||||
|
||||
static Windows::System::VirtualKeyModifiers ConvertVKModifiers(winrt::Microsoft::Terminal::Settings::KeyModifiers modifiers);
|
||||
static winrt::hstring FormatOverrideShortcutText(winrt::Microsoft::Terminal::Settings::KeyModifiers modifiers);
|
||||
|
||||
// clang-format off
|
||||
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);
|
||||
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(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);
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
// 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,
|
||||
@@ -24,7 +30,6 @@ namespace TerminalApp
|
||||
NewWindow,
|
||||
CloseWindow,
|
||||
CloseTab,
|
||||
ClosePane,
|
||||
NextTab,
|
||||
PrevTab,
|
||||
SplitVertical,
|
||||
@@ -48,43 +53,59 @@ namespace TerminalApp
|
||||
ResizePaneRight,
|
||||
ResizePaneUp,
|
||||
ResizePaneDown,
|
||||
MoveFocusLeft,
|
||||
MoveFocusRight,
|
||||
MoveFocusUp,
|
||||
MoveFocusDown,
|
||||
OpenSettings
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass AppKeyBindings : Microsoft.Terminal.Settings.IKeyBindings
|
||||
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 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);
|
||||
|
||||
[default_interface]
|
||||
runtimeclass AppKeyBindings : Microsoft.Terminal.Settings.IKeyBindings
|
||||
{
|
||||
AppKeyBindings();
|
||||
|
||||
void SetKeyBinding(ShortcutAction action, Microsoft.Terminal.Settings.KeyChord chord);
|
||||
Microsoft.Terminal.Settings.KeyChord GetKeyBinding(ShortcutAction action);
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> CopyText;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> PasteText;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> NewTab;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> OpenNewTabDropdown;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> DuplicateTab;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> NewTabWithProfile;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> NewWindow;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> CloseWindow;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> CloseTab;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> ClosePane;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> SwitchToTab;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> NextTab;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> PrevTab;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> SplitVertical;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> SplitHorizontal;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> IncreaseFontSize;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> DecreaseFontSize;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> ScrollUp;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> ScrollDown;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> ScrollUpPage;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> ScrollDownPage;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> OpenSettings;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> ResizePane;
|
||||
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> MoveFocus;
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ 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" };
|
||||
@@ -31,7 +30,6 @@ static constexpr std::string_view NewTabWithProfile8Key{ "newTabProfile8" };
|
||||
static constexpr std::string_view NewWindowKey{ "newWindow" };
|
||||
static constexpr std::string_view CloseWindowKey{ "closeWindow" };
|
||||
static constexpr std::string_view CloseTabKey{ "closeTab" };
|
||||
static constexpr std::string_view ClosePaneKey{ "closePane" };
|
||||
static constexpr std::string_view SwitchtoTabKey{ "switchToTab" };
|
||||
static constexpr std::string_view NextTabKey{ "nextTab" };
|
||||
static constexpr std::string_view PrevTabKey{ "prevTab" };
|
||||
@@ -57,10 +55,6 @@ static constexpr std::string_view ResizePaneLeftKey{ "resizePaneLeft" };
|
||||
static constexpr std::string_view ResizePaneRightKey{ "resizePaneRight" };
|
||||
static constexpr std::string_view ResizePaneUpKey{ "resizePaneUp" };
|
||||
static constexpr std::string_view ResizePaneDownKey{ "resizePaneDown" };
|
||||
static constexpr std::string_view MoveFocusLeftKey{ "moveFocusLeft" };
|
||||
static constexpr std::string_view MoveFocusRightKey{ "moveFocusRight" };
|
||||
static constexpr std::string_view MoveFocusUpKey{ "moveFocusUp" };
|
||||
static constexpr std::string_view MoveFocusDownKey{ "moveFocusDown" };
|
||||
|
||||
// Specifically use a map here over an unordered_map. We want to be able to
|
||||
// iterate over these entries in-order when we're serializing the keybindings.
|
||||
@@ -75,7 +69,6 @@ static const std::map<std::string_view, ShortcutAction, std::less<>> commandName
|
||||
{ CopyTextWithoutNewlinesKey, ShortcutAction::CopyTextWithoutNewlines },
|
||||
{ PasteTextKey, ShortcutAction::PasteText },
|
||||
{ NewTabKey, ShortcutAction::NewTab },
|
||||
{ OpenNewTabDropdownKey, ShortcutAction::OpenNewTabDropdown },
|
||||
{ DuplicateTabKey, ShortcutAction::DuplicateTab },
|
||||
{ NewTabWithProfile0Key, ShortcutAction::NewTabProfile0 },
|
||||
{ NewTabWithProfile1Key, ShortcutAction::NewTabProfile1 },
|
||||
@@ -89,7 +82,6 @@ static const std::map<std::string_view, ShortcutAction, std::less<>> commandName
|
||||
{ NewWindowKey, ShortcutAction::NewWindow },
|
||||
{ CloseWindowKey, ShortcutAction::CloseWindow },
|
||||
{ CloseTabKey, ShortcutAction::CloseTab },
|
||||
{ ClosePaneKey, ShortcutAction::ClosePane },
|
||||
{ NextTabKey, ShortcutAction::NextTab },
|
||||
{ PrevTabKey, ShortcutAction::PrevTab },
|
||||
{ IncreaseFontSizeKey, ShortcutAction::IncreaseFontSize },
|
||||
@@ -113,10 +105,6 @@ static const std::map<std::string_view, ShortcutAction, std::less<>> commandName
|
||||
{ ResizePaneRightKey, ShortcutAction::ResizePaneRight },
|
||||
{ ResizePaneUpKey, ShortcutAction::ResizePaneUp },
|
||||
{ ResizePaneDownKey, ShortcutAction::ResizePaneDown },
|
||||
{ MoveFocusLeftKey, ShortcutAction::MoveFocusLeft },
|
||||
{ MoveFocusRightKey, ShortcutAction::MoveFocusRight },
|
||||
{ MoveFocusUpKey, ShortcutAction::MoveFocusUp },
|
||||
{ MoveFocusDownKey, ShortcutAction::MoveFocusDown },
|
||||
{ OpenSettingsKey, ShortcutAction::OpenSettings },
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#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;
|
||||
@@ -246,7 +245,7 @@ void CascadiaSettings::_CreateDefaultProfiles()
|
||||
if (_isPowerShellCoreInstalled(psCoreCmdline))
|
||||
{
|
||||
auto pwshProfile{ _CreateDefaultProfile(L"PowerShell Core") };
|
||||
pwshProfile.SetCommandline(std::move(psCoreCmdline));
|
||||
pwshProfile.SetCommandline(psCoreCmdline);
|
||||
pwshProfile.SetStartingDirectory(DEFAULT_STARTING_DIRECTORY);
|
||||
pwshProfile.SetColorScheme({ L"Campbell" });
|
||||
|
||||
@@ -262,20 +261,6 @@ void CascadiaSettings::_CreateDefaultProfiles()
|
||||
|
||||
_profiles.emplace_back(powershellProfile);
|
||||
_profiles.emplace_back(cmdProfile);
|
||||
|
||||
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);
|
||||
@@ -292,21 +277,18 @@ void CascadiaSettings::_CreateDefaultProfiles()
|
||||
void CascadiaSettings::_CreateDefaultKeybindings()
|
||||
{
|
||||
AppKeyBindings keyBindings = _globals.GetKeybindings();
|
||||
// Set up some basic default keybindings
|
||||
// Set up spme basic default keybindings
|
||||
// TODO:MSFT:20700157 read our settings from some source, and configure
|
||||
// keychord,action pairings from that file
|
||||
keyBindings.SetKeyBinding(ShortcutAction::NewTab,
|
||||
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
|
||||
KeyChord{ KeyModifiers::Ctrl,
|
||||
static_cast<int>('T') });
|
||||
|
||||
keyBindings.SetKeyBinding(ShortcutAction::OpenNewTabDropdown,
|
||||
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
|
||||
static_cast<int>(' ') });
|
||||
|
||||
keyBindings.SetKeyBinding(ShortcutAction::DuplicateTab,
|
||||
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
|
||||
static_cast<int>('D') });
|
||||
|
||||
keyBindings.SetKeyBinding(ShortcutAction::ClosePane,
|
||||
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
|
||||
keyBindings.SetKeyBinding(ShortcutAction::CloseTab,
|
||||
KeyChord{ KeyModifiers::Ctrl,
|
||||
static_cast<int>('W') });
|
||||
|
||||
keyBindings.SetKeyBinding(ShortcutAction::CopyText,
|
||||
@@ -372,31 +354,31 @@ void CascadiaSettings::_CreateDefaultKeybindings()
|
||||
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
|
||||
VK_PRIOR });
|
||||
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab0,
|
||||
KeyChord{ KeyModifiers::Alt | KeyModifiers::Ctrl,
|
||||
KeyChord{ KeyModifiers::Alt,
|
||||
static_cast<int>('1') });
|
||||
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab1,
|
||||
KeyChord{ KeyModifiers::Alt | KeyModifiers::Ctrl,
|
||||
KeyChord{ KeyModifiers::Alt,
|
||||
static_cast<int>('2') });
|
||||
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab2,
|
||||
KeyChord{ KeyModifiers::Alt | KeyModifiers::Ctrl,
|
||||
KeyChord{ KeyModifiers::Alt,
|
||||
static_cast<int>('3') });
|
||||
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab3,
|
||||
KeyChord{ KeyModifiers::Alt | KeyModifiers::Ctrl,
|
||||
KeyChord{ KeyModifiers::Alt,
|
||||
static_cast<int>('4') });
|
||||
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab4,
|
||||
KeyChord{ KeyModifiers::Alt | KeyModifiers::Ctrl,
|
||||
KeyChord{ KeyModifiers::Alt,
|
||||
static_cast<int>('5') });
|
||||
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab5,
|
||||
KeyChord{ KeyModifiers::Alt | KeyModifiers::Ctrl,
|
||||
KeyChord{ KeyModifiers::Alt,
|
||||
static_cast<int>('6') });
|
||||
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab6,
|
||||
KeyChord{ KeyModifiers::Alt | KeyModifiers::Ctrl,
|
||||
KeyChord{ KeyModifiers::Alt,
|
||||
static_cast<int>('7') });
|
||||
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab7,
|
||||
KeyChord{ KeyModifiers::Alt | KeyModifiers::Ctrl,
|
||||
KeyChord{ KeyModifiers::Alt,
|
||||
static_cast<int>('8') });
|
||||
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab8,
|
||||
KeyChord{ KeyModifiers::Alt | KeyModifiers::Ctrl,
|
||||
KeyChord{ KeyModifiers::Alt,
|
||||
static_cast<int>('9') });
|
||||
}
|
||||
|
||||
@@ -518,8 +500,7 @@ bool CascadiaSettings::_isPowerShellCoreInstalled(std::filesystem::path& cmdline
|
||||
// - true iff powershell core (pwsh.exe) is present in the given path
|
||||
bool CascadiaSettings::_isPowerShellCoreInstalledInPath(const std::wstring_view programFileEnv, std::filesystem::path& cmdline)
|
||||
{
|
||||
std::wstring programFileEnvNulTerm{ programFileEnv };
|
||||
std::filesystem::path psCorePath{ wil::ExpandEnvironmentStringsW<std::wstring>(programFileEnvNulTerm.data()) };
|
||||
std::filesystem::path psCorePath = ExpandEnvironmentVariableString(programFileEnv.data());
|
||||
psCorePath /= L"PowerShell";
|
||||
if (std::filesystem::exists(psCorePath))
|
||||
{
|
||||
@@ -616,7 +597,6 @@ void CascadiaSettings::_AppendWslProfiles(std::vector<TerminalApp::Profile>& pro
|
||||
auto WSLDistro{ _CreateDefaultProfile(distName) };
|
||||
WSLDistro.SetCommandline(L"wsl.exe -d " + distName);
|
||||
WSLDistro.SetColorScheme({ L"Campbell" });
|
||||
WSLDistro.SetStartingDirectory(DEFAULT_STARTING_DIRECTORY);
|
||||
std::wstring iconPath{ PACKAGED_PROFILE_ICON_PATH };
|
||||
iconPath.append(DEFAULT_LINUX_ICON_GUID);
|
||||
iconPath.append(PACKAGED_PROFILE_ICON_EXTENSION);
|
||||
@@ -626,6 +606,27 @@ void CascadiaSettings::_AppendWslProfiles(std::vector<TerminalApp::Profile>& pro
|
||||
}
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Get a environment variable string.
|
||||
// Arguments:
|
||||
// - A string that contains an environment-variable string in the form: %variableName%.
|
||||
// Return Value:
|
||||
// - a string of the expending environment-variable string.
|
||||
std::wstring CascadiaSettings::ExpandEnvironmentVariableString(std::wstring_view source)
|
||||
{
|
||||
std::wstring result{};
|
||||
DWORD requiredSize = 0;
|
||||
do
|
||||
{
|
||||
result.resize(requiredSize);
|
||||
requiredSize = ::ExpandEnvironmentStringsW(source.data(), result.data(), gsl::narrow<DWORD>(result.size()));
|
||||
} while (requiredSize != result.size());
|
||||
|
||||
// Trim the terminating null character
|
||||
result.resize(requiredSize - 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper function for creating a skeleton default profile with a pre-populated
|
||||
// guid and name.
|
||||
@@ -648,132 +649,3 @@ 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<TerminalApp::SettingsLoadWarnings>& 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:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
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<size_t> 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<GUID, GuidEquality> 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 (int 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- CascadiaSettings.h
|
||||
- CascadiaSettings.hpp
|
||||
|
||||
Abstract:
|
||||
- This class acts as the container for all app settings. It's composed of two
|
||||
@@ -18,17 +18,8 @@ Author(s):
|
||||
#pragma once
|
||||
#include <winrt/Microsoft.Terminal.TerminalControl.h>
|
||||
#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;
|
||||
@@ -40,7 +31,7 @@ public:
|
||||
CascadiaSettings();
|
||||
~CascadiaSettings();
|
||||
|
||||
static std::unique_ptr<CascadiaSettings> LoadAll();
|
||||
static std::unique_ptr<CascadiaSettings> LoadAll(const bool saveOnLoad = true);
|
||||
void SaveAll() const;
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::TerminalSettings MakeSettings(std::optional<GUID> profileGuid) const;
|
||||
@@ -54,18 +45,15 @@ public:
|
||||
Json::Value ToJson() const;
|
||||
static std::unique_ptr<CascadiaSettings> FromJson(const Json::Value& json);
|
||||
|
||||
static std::wstring GetSettingsPath(const bool useRoamingPath = false);
|
||||
static std::wstring GetSettingsPath();
|
||||
|
||||
const Profile* FindProfile(GUID profileGuid) const noexcept;
|
||||
|
||||
void CreateDefaults();
|
||||
|
||||
std::vector<TerminalApp::SettingsLoadWarnings>& GetWarnings();
|
||||
|
||||
private:
|
||||
GlobalAppSettings _globals;
|
||||
std::vector<Profile> _profiles;
|
||||
std::vector<TerminalApp::SettingsLoadWarnings> _warnings{};
|
||||
|
||||
void _CreateDefaultKeybindings();
|
||||
void _CreateDefaultSchemes();
|
||||
@@ -75,15 +63,9 @@ private:
|
||||
static void _WriteSettings(const std::string_view content);
|
||||
static std::optional<std::string> _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<TerminalApp::Profile>& profileStorage);
|
||||
static std::wstring ExpandEnvironmentVariableString(std::wstring_view source);
|
||||
static Profile _CreateDefaultProfile(const std::wstring_view name);
|
||||
|
||||
friend class TerminalAppLocalTests::SettingsTests;
|
||||
};
|
||||
|
||||
@@ -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> CascadiaSettings::LoadAll()
|
||||
std::unique_ptr<CascadiaSettings> CascadiaSettings::LoadAll(const bool saveOnLoad)
|
||||
{
|
||||
std::unique_ptr<CascadiaSettings> resultPtr;
|
||||
std::optional<std::string> fileData = _ReadSettings();
|
||||
|
||||
const bool foundFile = fileData.has_value();
|
||||
// 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)
|
||||
if (foundFile)
|
||||
{
|
||||
const auto actualData = fileData.value();
|
||||
|
||||
@@ -59,14 +59,28 @@ std::unique_ptr<CascadiaSettings> CascadiaSettings::LoadAll()
|
||||
// `parse` will return false if it fails.
|
||||
if (!reader->parse(actualDataStart, actualData.c_str() + actualData.size(), &root, &errs))
|
||||
{
|
||||
// This will be caught by App::_TryLoadSettings, who will display
|
||||
// the text to the user.
|
||||
// TODO:GH#990 display this exception text to the user, in a
|
||||
// copy-pasteable way.
|
||||
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
|
||||
}
|
||||
resultPtr = FromJson(root);
|
||||
|
||||
// If this throws, the app will catch it and use the default settings (temporarily)
|
||||
resultPtr->_ValidateSettings();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -247,77 +261,25 @@ void CascadiaSettings::_WriteSettings(const std::string_view content)
|
||||
// from reading the file
|
||||
std::optional<std::string> CascadiaSettings::_ReadSettings()
|
||||
{
|
||||
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)
|
||||
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)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
// fileSize is in bytes
|
||||
const auto fileSize = GetFileSize(hFile.get(), nullptr);
|
||||
const auto fileSize = GetFileSize(hFile, nullptr);
|
||||
THROW_LAST_ERROR_IF(fileSize == INVALID_FILE_SIZE);
|
||||
|
||||
auto utf8buffer = std::make_unique<char[]>(fileSize);
|
||||
|
||||
DWORD bytesRead = 0;
|
||||
THROW_LAST_ERROR_IF(!ReadFile(hFile.get(), utf8buffer.get(), fileSize, &bytesRead, nullptr));
|
||||
THROW_LAST_ERROR_IF(!ReadFile(hFile, utf8buffer.get(), fileSize, &bytesRead, nullptr));
|
||||
CloseHandle(hFile);
|
||||
|
||||
// convert buffer to UTF-8 string
|
||||
std::string utf8string(utf8buffer.get(), fileSize);
|
||||
@@ -327,27 +289,25 @@ std::optional<std::string> CascadiaSettings::_ReadSettings()
|
||||
|
||||
// function Description:
|
||||
// - Returns the full path to the settings file, either within the application
|
||||
// package, or in its unpackaged location. This path is under the "Local
|
||||
// AppData" folder, so it _doesn't_ roam to other machines.
|
||||
// package, or in its unpackaged location.
|
||||
// - If the application is unpackaged,
|
||||
// the file will end up under e.g. C:\Users\admin\AppData\Local\Microsoft\Windows Terminal\profiles.json
|
||||
// the file will end up under e.g. C:\Users\admin\AppData\Roaming\Microsoft\Windows Terminal\profiles.json
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the full path to the settings file
|
||||
std::wstring CascadiaSettings::GetSettingsPath(const bool useRoamingPath)
|
||||
std::wstring CascadiaSettings::GetSettingsPath()
|
||||
{
|
||||
wil::unique_cotaskmem_string localAppDataFolder;
|
||||
wil::unique_cotaskmem_string roamingAppDataFolder;
|
||||
// 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.
|
||||
const auto knowFolderId = useRoamingPath ? FOLDERID_RoamingAppData : FOLDERID_LocalAppData;
|
||||
if (FAILED(SHGetKnownFolderPath(knowFolderId, KF_FLAG_FORCE_APP_DATA_REDIRECTION, 0, &localAppDataFolder)))
|
||||
if (FAILED(SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_FORCE_APP_DATA_REDIRECTION, 0, &roamingAppDataFolder)))
|
||||
{
|
||||
THROW_LAST_ERROR();
|
||||
}
|
||||
|
||||
std::filesystem::path parentDirectoryForSettingsFile{ localAppDataFolder.get() };
|
||||
std::filesystem::path parentDirectoryForSettingsFile{ roamingAppDataFolder.get() };
|
||||
|
||||
if (!_IsPackaged())
|
||||
{
|
||||
|
||||
@@ -66,8 +66,7 @@ void ColorScheme::ApplyScheme(TerminalSettings terminalSettings) const
|
||||
terminalSettings.DefaultForeground(_defaultForeground);
|
||||
terminalSettings.DefaultBackground(_defaultBackground);
|
||||
|
||||
auto const tableCount = gsl::narrow_cast<int>(_table.size());
|
||||
for (int i = 0; i < tableCount; i++)
|
||||
for (int i = 0; i < _table.size(); i++)
|
||||
{
|
||||
terminalSettings.SetColorTableEntry(i, _table[i]);
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<!--
|
||||
This is a terrible, terrible rule. There exists a bug in Visual Studio 2019 16.2 and 16.3 previews
|
||||
where ResolveAssemblyReferences will try and fail to parse a .lib when it produces a .winmd.
|
||||
To fix that, we have to _temporarily_ replace the %(Implementation) on any winmd-producing
|
||||
static library references with the empty string so as to make ResolveAssemblyReferences
|
||||
not try to read it.
|
||||
|
||||
Upstream problem report:
|
||||
https://developercommunity.visualstudio.com/content/problem/629524/static-library-reference-causes-there-was-a-proble.html
|
||||
-->
|
||||
<Target Name="_RemoveTerminalAppLibImplementationFromReference" BeforeTargets="ResolveAssemblyReferences">
|
||||
<ItemGroup>
|
||||
<_TerminalAppLibProjectReference Include="@(_ResolvedProjectReferencePaths)" Condition="'%(Filename)' == 'TerminalApp'" />
|
||||
<_ResolvedProjectReferencePaths Remove="@(_TerminalAppLibProjectReference)" />
|
||||
<_ResolvedProjectReferencePaths Include="@(_TerminalAppLibProjectReference)">
|
||||
<Implementation />
|
||||
</_ResolvedProjectReferencePaths>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="_RestoreTerminalAppLibImplementationFromReference" AfterTargets="ResolveAssemblyReferences">
|
||||
<ItemGroup>
|
||||
<_ResolvedProjectReferencePaths Remove="@(_TerminalAppLibProjectReference)" />
|
||||
<_ResolvedProjectReferencePaths Include="@(_TerminalAppLibProjectReference)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
<!-- End "terrible, terrible rule" -->
|
||||
</Project>
|
||||
@@ -24,7 +24,6 @@ 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" };
|
||||
@@ -40,8 +39,7 @@ GlobalAppSettings::GlobalAppSettings() :
|
||||
_showTitleInTitlebar{ true },
|
||||
_showTabsInTitlebar{ true },
|
||||
_requestedTheme{ ElementTheme::Default },
|
||||
_wordDelimiters{ DEFAULT_WORD_DELIMITERS },
|
||||
_copyOnSelect{ false }
|
||||
_wordDelimiters{ DEFAULT_WORD_DELIMITERS }
|
||||
{
|
||||
}
|
||||
|
||||
@@ -119,16 +117,6 @@ 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
|
||||
{
|
||||
@@ -153,7 +141,6 @@ void GlobalAppSettings::ApplyToSettings(TerminalSettings& settings) const noexce
|
||||
settings.InitialRows(_initialRows);
|
||||
settings.InitialCols(_initialCols);
|
||||
settings.WordDelimiters(_wordDelimiters);
|
||||
settings.CopyOnSelect(_copyOnSelect);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -173,7 +160,6 @@ 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);
|
||||
|
||||
@@ -224,11 +210,6 @@ 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));
|
||||
|
||||
@@ -50,9 +50,6 @@ public:
|
||||
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;
|
||||
@@ -75,7 +72,6 @@ private:
|
||||
|
||||
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;
|
||||
|
||||
@@ -68,7 +68,21 @@ static const std::unordered_map<int32_t, std::wstring_view> vkeyNamePairs {
|
||||
{ VK_F22 , L"f22" },
|
||||
{ VK_F23 , L"f23" },
|
||||
{ VK_F24 , L"f24" },
|
||||
{ VK_OEM_PLUS , L"plus" }
|
||||
{ VK_OEM_PLUS , L"plus" },
|
||||
{ VK_OEM_COMMA , L"," },
|
||||
{ VK_OEM_MINUS , L"-" },
|
||||
{ VK_OEM_PERIOD , L"." }
|
||||
// TODO:
|
||||
// These all look like they'd be good keybindings, but change based on keyboard
|
||||
// layout. How do we deal with that?
|
||||
// #define VK_OEM_NEC_EQUAL 0x92 // '=' key on numpad
|
||||
// #define VK_OEM_1 0xBA // ';:' for US
|
||||
// #define VK_OEM_2 0xBF // '/?' for US
|
||||
// #define VK_OEM_3 0xC0 // '`~' for US
|
||||
// #define VK_OEM_4 0xDB // '[{' for US
|
||||
// #define VK_OEM_5 0xDC // '\|' for US
|
||||
// #define VK_OEM_6 0xDD // ']}' for US
|
||||
// #define VK_OEM_7 0xDE // ''"' for US
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@@ -164,25 +178,6 @@ winrt::Microsoft::Terminal::Settings::KeyChord KeyChordSerialization::FromString
|
||||
}
|
||||
}
|
||||
|
||||
// If we haven't found a key, attempt a keyboard mapping
|
||||
if (!foundKey && part.size() == 1)
|
||||
{
|
||||
auto oemVk = VkKeyScanW(part[0]);
|
||||
if (oemVk != -1)
|
||||
{
|
||||
vkey = oemVk & 0xFF;
|
||||
auto oemModifiers = (oemVk & 0xFF00) >> 8;
|
||||
// We're using WI_SetFlagIf instead of WI_UpdateFlag because we want to be strictly additive
|
||||
// to the user's specified modifiers. ctrl+| should be the same as ctrl+shift+\,
|
||||
// but if we used WI_UpdateFlag, ctrl+shift+\ would turn _off_ Shift because \ doesn't
|
||||
// require it.
|
||||
WI_SetFlagIf(modifiers, KeyModifiers::Shift, WI_IsFlagSet(oemModifiers, 1U));
|
||||
WI_SetFlagIf(modifiers, KeyModifiers::Ctrl, WI_IsFlagSet(oemModifiers, 2U));
|
||||
WI_SetFlagIf(modifiers, KeyModifiers::Alt, WI_IsFlagSet(oemModifiers, 4U));
|
||||
foundKey = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If we weren't able to find a match, throw an exception.
|
||||
if (!foundKey)
|
||||
{
|
||||
@@ -245,16 +240,6 @@ winrt::hstring KeyChordSerialization::ToString(const KeyChord& chord)
|
||||
buffer += vkeyNamePairs.at(vkey);
|
||||
serializedSuccessfully = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto mappedChar = MapVirtualKeyW(vkey, MAPVK_VK_TO_CHAR);
|
||||
if (mappedChar != 0)
|
||||
{
|
||||
wchar_t mappedWch = gsl::narrow_cast<wchar_t>(mappedChar);
|
||||
buffer += std::wstring_view{ &mappedWch, 1 };
|
||||
serializedSuccessfully = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!serializedSuccessfully)
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
//
|
||||
// MinMaxCloseControl.xaml.cpp
|
||||
// Implementation of the MinMaxCloseControl class
|
||||
//
|
||||
@@ -10,7 +8,6 @@
|
||||
#include "MinMaxCloseControl.h"
|
||||
|
||||
#include "MinMaxCloseControl.g.cpp"
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
@@ -19,37 +16,59 @@ namespace winrt::TerminalApp::implementation
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
void MinMaxCloseControl::Maximize()
|
||||
uint64_t MinMaxCloseControl::ParentWindowHandle() const
|
||||
{
|
||||
VisualStateManager::GoToState(MaximizeButton(), L"WindowStateMaximized", false);
|
||||
return reinterpret_cast<uint64_t>(_window);
|
||||
}
|
||||
|
||||
void MinMaxCloseControl::RestoreDown()
|
||||
void MinMaxCloseControl::ParentWindowHandle(uint64_t handle)
|
||||
{
|
||||
VisualStateManager::GoToState(MaximizeButton(), L"WindowStateNormal", false);
|
||||
_window = reinterpret_cast<HWND>(handle);
|
||||
}
|
||||
|
||||
// These event handlers simply forward each buttons click events up to the
|
||||
// events we've exposed.
|
||||
void MinMaxCloseControl::_MinimizeClick(winrt::Windows::Foundation::IInspectable const& sender,
|
||||
RoutedEventArgs const& e)
|
||||
void MinMaxCloseControl::_OnMaximize(byte flag)
|
||||
{
|
||||
_minimizeClickHandlers(*this, e);
|
||||
if (_window)
|
||||
{
|
||||
POINT point1 = {};
|
||||
::GetCursorPos(&point1);
|
||||
const LPARAM lParam = MAKELPARAM(point1.x, point1.y);
|
||||
WINDOWPLACEMENT placement = { sizeof(placement) };
|
||||
::GetWindowPlacement(_window, &placement);
|
||||
if (placement.showCmd == SW_SHOWNORMAL)
|
||||
{
|
||||
winrt::Windows::UI::Xaml::VisualStateManager::GoToState(this->Maximize(), L"WindowStateMaximized", false);
|
||||
::PostMessage(_window, WM_SYSCOMMAND, SC_MAXIMIZE | flag, lParam);
|
||||
}
|
||||
else if (placement.showCmd == SW_SHOWMAXIMIZED)
|
||||
{
|
||||
winrt::Windows::UI::Xaml::VisualStateManager::GoToState(this->Maximize(), L"WindowStateNormal", false);
|
||||
::PostMessage(_window, WM_SYSCOMMAND, SC_RESTORE | flag, lParam);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MinMaxCloseControl::_MaximizeClick(winrt::Windows::Foundation::IInspectable const& sender,
|
||||
RoutedEventArgs const& e)
|
||||
void MinMaxCloseControl::Maximize_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e)
|
||||
{
|
||||
_maximizeClickHandlers(*this, e);
|
||||
}
|
||||
void MinMaxCloseControl::_CloseClick(winrt::Windows::Foundation::IInspectable const& sender,
|
||||
RoutedEventArgs const& e)
|
||||
{
|
||||
_closeClickHandlers(*this, e);
|
||||
_OnMaximize(HTMAXBUTTON);
|
||||
}
|
||||
|
||||
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(MinMaxCloseControl, MinimizeClick, _minimizeClickHandlers, TerminalApp::MinMaxCloseControl, RoutedEventArgs);
|
||||
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(MinMaxCloseControl, MaximizeClick, _maximizeClickHandlers, TerminalApp::MinMaxCloseControl, RoutedEventArgs);
|
||||
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(MinMaxCloseControl, CloseClick, _closeClickHandlers, TerminalApp::MinMaxCloseControl, RoutedEventArgs);
|
||||
void MinMaxCloseControl::DragBar_DoubleTapped(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Input::DoubleTappedRoutedEventArgs const& e)
|
||||
{
|
||||
_OnMaximize(HTCAPTION);
|
||||
}
|
||||
|
||||
void MinMaxCloseControl::Minimize_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e)
|
||||
{
|
||||
if (_window)
|
||||
{
|
||||
::PostMessage(_window, WM_SYSCOMMAND, SC_MINIMIZE | HTMINBUTTON, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void MinMaxCloseControl::Close_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e)
|
||||
{
|
||||
::PostQuitMessage(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
//
|
||||
// Declaration of the MainUserControl class.
|
||||
//
|
||||
|
||||
@@ -10,7 +8,6 @@
|
||||
#include "winrt/Windows.UI.Xaml.Markup.h"
|
||||
#include "winrt/Windows.UI.Xaml.Interop.h"
|
||||
#include "MinMaxCloseControl.g.h"
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
@@ -18,19 +15,17 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
MinMaxCloseControl();
|
||||
|
||||
void Maximize();
|
||||
void RestoreDown();
|
||||
void Minimize_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
void Maximize_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
void Close_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
void DragBar_DoubleTapped(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Input::DoubleTappedRoutedEventArgs const& e);
|
||||
|
||||
void _MinimizeClick(winrt::Windows::Foundation::IInspectable const& sender,
|
||||
winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
void _MaximizeClick(winrt::Windows::Foundation::IInspectable const& sender,
|
||||
winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
void _CloseClick(winrt::Windows::Foundation::IInspectable const& sender,
|
||||
winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
uint64_t ParentWindowHandle() const;
|
||||
void ParentWindowHandle(uint64_t handle);
|
||||
|
||||
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(MinimizeClick, _minimizeClickHandlers, TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs);
|
||||
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(MaximizeClick, _maximizeClickHandlers, TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs);
|
||||
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(CloseClick, _closeClickHandlers, TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs);
|
||||
private:
|
||||
void _OnMaximize(byte flag);
|
||||
HWND _window{ nullptr }; // non-owning handle; should not be freed in the dtor.
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace TerminalApp
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass MinMaxCloseControl : Windows.UI.Xaml.Controls.StackPanel
|
||||
[default_interface]
|
||||
runtimeclass MinMaxCloseControl : Windows.UI.Xaml.Controls.StackPanel
|
||||
{
|
||||
MinMaxCloseControl();
|
||||
|
||||
void Maximize();
|
||||
void RestoreDown();
|
||||
Windows.UI.Xaml.Controls.Grid Content{ get; };
|
||||
Windows.UI.Xaml.Controls.Border DragBar{ get; };
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<MinMaxCloseControl, Windows.UI.Xaml.RoutedEventArgs> MinimizeClick;
|
||||
event Windows.Foundation.TypedEventHandler<MinMaxCloseControl, Windows.UI.Xaml.RoutedEventArgs> MaximizeClick;
|
||||
event Windows.Foundation.TypedEventHandler<MinMaxCloseControl, Windows.UI.Xaml.RoutedEventArgs> CloseClick;
|
||||
UInt64 ParentWindowHandle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
|
||||
the MIT License. See LICENSE in the project root for license information. -->
|
||||
<StackPanel
|
||||
<StackPanel
|
||||
x:Class="TerminalApp.MinMaxCloseControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
@@ -131,7 +129,13 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
</ResourceDictionary>
|
||||
</StackPanel.Resources>
|
||||
|
||||
<Button Height="36.0" MinWidth="45.0" Width="45.0" x:Name="MinimizeButton" Style="{StaticResource CaptionButton}" Click="_MinimizeClick"
|
||||
<Grid x:Name="Content"></Grid>
|
||||
<Border Height="36.0"
|
||||
MinWidth="160.0"
|
||||
x:Name="DragBar"
|
||||
Background="{ThemeResource SystemChromeLowColor}"
|
||||
DoubleTapped="DragBar_DoubleTapped"/>
|
||||
<Button Height="36.0" Width="45.0" x:Name="Minimize" Style="{StaticResource CaptionButton}" Click="Minimize_Click"
|
||||
AutomationProperties.Name="Minimize">
|
||||
<Button.Resources>
|
||||
<ResourceDictionary>
|
||||
@@ -139,7 +143,7 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
</ResourceDictionary>
|
||||
</Button.Resources>
|
||||
</Button>
|
||||
<Button Height="36.0" MinWidth="45.0" Width="45.0" x:Name="MaximizeButton" Style="{StaticResource CaptionButton}" Click="_MaximizeClick"
|
||||
<Button Height="36.0" Width="45.0" x:Name="Maximize" Style="{StaticResource CaptionButton}" Click="Maximize_Click"
|
||||
AutomationProperties.Name="Maximize">
|
||||
<Button.Resources>
|
||||
<ResourceDictionary>
|
||||
@@ -148,7 +152,7 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
</ResourceDictionary>
|
||||
</Button.Resources>
|
||||
</Button>
|
||||
<Button Height="36.0" MinWidth="45.0" Width="45.0" x:Name="CloseButton" Style="{StaticResource CaptionButton}" Click="_CloseClick"
|
||||
<Button Height="36.0" Width="45.0" x:Name="Close" Style="{StaticResource CaptionButton}" Click="Close_Click"
|
||||
AutomationProperties.Name="Close">
|
||||
<Button.Resources>
|
||||
<ResourceDictionary>
|
||||
|
||||
@@ -19,7 +19,7 @@ Pane::Pane(const GUID& profile, const TermControl& control, const bool lastFocus
|
||||
_lastFocused{ lastFocused },
|
||||
_profile{ profile }
|
||||
{
|
||||
_root.Children().Append(_control);
|
||||
_root.Children().Append(_control.GetControl());
|
||||
_connectionClosedToken = _control.ConnectionClosed({ this, &Pane::_ControlClosedHandler });
|
||||
|
||||
// Set the background of the pane to match that of the theme's default grid
|
||||
@@ -187,95 +187,6 @@ bool Pane::ResizePane(const Direction& direction)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempts to handle moving focus to one of our children. If our split
|
||||
// direction isn't appropriate for the move direction, then we'll return
|
||||
// false, to try and let our parent handle the move. If our child we'd move
|
||||
// focus to is already focused, we'll also return false, to again let our
|
||||
// parent try and handle the focus movement.
|
||||
// Arguments:
|
||||
// - direction: The direction to move the focus in.
|
||||
// Return Value:
|
||||
// - true if we handled this focus move request.
|
||||
bool Pane::_NavigateFocus(const Direction& direction)
|
||||
{
|
||||
if (!DirectionMatchesSplit(direction, _splitState))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool focusSecond = (direction == Direction::Right) || (direction == Direction::Down);
|
||||
|
||||
const auto newlyFocusedChild = focusSecond ? _secondChild : _firstChild;
|
||||
|
||||
// If the child we want to move focus to is _already_ focused, return false,
|
||||
// to try and let our parent figure it out.
|
||||
if (newlyFocusedChild->WasLastFocused())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Transfer focus to our child, and update the focus of our tree.
|
||||
newlyFocusedChild->_FocusFirstChild();
|
||||
UpdateFocus();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempts to move focus to one of our children. If we have a focused child,
|
||||
// we'll try to move the focus in the direction requested.
|
||||
// - If there isn't a pane that exists as a child of this pane in the correct
|
||||
// direction, we'll return false. This will indicate to our parent that they
|
||||
// should try and move the focus themselves. In this way, the focus can move
|
||||
// up and down the tree to the correct pane.
|
||||
// - This method is _very_ similar to ResizePane. Both are trying to find the
|
||||
// right separator to move (focus) in a direction.
|
||||
// Arguments:
|
||||
// - direction: The direction to move the focus in.
|
||||
// Return Value:
|
||||
// - true if we or a child handled this focus move request.
|
||||
bool Pane::NavigateFocus(const Direction& direction)
|
||||
{
|
||||
// If we're a leaf, do nothing. We can't possibly have a descendant with a
|
||||
// separator the correct direction.
|
||||
if (_IsLeaf())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if either our first or second child is the currently focused leaf.
|
||||
// If it is, and the requested move direction matches our separator, then
|
||||
// we're the pane that needs to handle this focus move.
|
||||
const bool firstIsFocused = _firstChild->_IsLeaf() && _firstChild->_lastFocused;
|
||||
const bool secondIsFocused = _secondChild->_IsLeaf() && _secondChild->_lastFocused;
|
||||
if (firstIsFocused || secondIsFocused)
|
||||
{
|
||||
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())
|
||||
{
|
||||
return _firstChild->NavigateFocus(direction) || _NavigateFocus(direction);
|
||||
}
|
||||
else if ((!_secondChild->_IsLeaf()) && _secondChild->_HasFocusedChild())
|
||||
{
|
||||
return _secondChild->NavigateFocus(direction) || _NavigateFocus(direction);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Called when our attached control is closed. Triggers listeners to our close
|
||||
// event, if we're a leaf pane.
|
||||
@@ -308,18 +219,6 @@ void Pane::_ControlClosedHandler()
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Fire our Closed event to tell our parent that we should be removed.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Pane::Close()
|
||||
{
|
||||
// Fire our Closed event to tell our parent that we should be removed.
|
||||
_closedHandlers();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the root UIElement of this pane. There may be a single TermControl as a
|
||||
// child, or an entire tree of grids and panes as children of this element.
|
||||
@@ -426,7 +325,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.FocusState() != FocusState::Unfocused) ||
|
||||
return (_control && _control.GetControl().FocusState() != FocusState::Unfocused) ||
|
||||
(_firstChild && _firstChild->_HasFocusedChild()) ||
|
||||
(_secondChild && _secondChild->_HasFocusedChild());
|
||||
}
|
||||
@@ -445,7 +344,7 @@ void Pane::UpdateFocus()
|
||||
if (_IsLeaf())
|
||||
{
|
||||
const auto controlFocused = _control &&
|
||||
_control.FocusState() != FocusState::Unfocused;
|
||||
_control.GetControl().FocusState() != FocusState::Unfocused;
|
||||
|
||||
_lastFocused = controlFocused;
|
||||
}
|
||||
@@ -468,7 +367,7 @@ void Pane::_FocusFirstChild()
|
||||
{
|
||||
if (_IsLeaf())
|
||||
{
|
||||
_control.Focus(FocusState::Programmatic);
|
||||
_control.GetControl().Focus(FocusState::Programmatic);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -564,11 +463,11 @@ void Pane::_CloseChild(const bool closeFirst)
|
||||
_separatorRoot = { nullptr };
|
||||
|
||||
// Reattach the TermControl to our grid.
|
||||
_root.Children().Append(_control);
|
||||
_root.Children().Append(_control.GetControl());
|
||||
|
||||
if (_lastFocused)
|
||||
{
|
||||
_control.Focus(FocusState::Programmatic);
|
||||
_control.GetControl().Focus(FocusState::Programmatic);
|
||||
}
|
||||
|
||||
_splitState = SplitState::None;
|
||||
@@ -777,31 +676,6 @@ void Pane::_ApplySplitDefinitions()
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Determines whether the pane can be split vertically
|
||||
// Arguments:
|
||||
// - splitType: what type of split we want to create.
|
||||
// Return Value:
|
||||
// - True if the pane can be split vertically. False otherwise.
|
||||
bool Pane::CanSplitVertical()
|
||||
{
|
||||
if (!_IsLeaf())
|
||||
{
|
||||
if (_firstChild->_HasFocusedChild())
|
||||
{
|
||||
return _firstChild->CanSplitVertical();
|
||||
}
|
||||
else if (_secondChild->_HasFocusedChild())
|
||||
{
|
||||
return _secondChild->CanSplitVertical();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return _CanSplit(SplitState::Vertical);
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -831,31 +705,6 @@ void Pane::SplitVertical(const GUID& profile, const TermControl& control)
|
||||
_Split(SplitState::Vertical, profile, control);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Determines whether the pane can be split horizontally
|
||||
// Arguments:
|
||||
// - splitType: what type of split we want to create.
|
||||
// Return Value:
|
||||
// - True if the pane can be split horizontally. False otherwise.
|
||||
bool Pane::CanSplitHorizontal()
|
||||
{
|
||||
if (!_IsLeaf())
|
||||
{
|
||||
if (_firstChild->_HasFocusedChild())
|
||||
{
|
||||
return _firstChild->CanSplitHorizontal();
|
||||
}
|
||||
else if (_secondChild->_HasFocusedChild())
|
||||
{
|
||||
return _secondChild->CanSplitHorizontal();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return _CanSplit(SplitState::Horizontal);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Horizontally 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
|
||||
@@ -884,40 +733,6 @@ void Pane::SplitHorizontal(const GUID& profile, const TermControl& control)
|
||||
_Split(SplitState::Horizontal, 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 bool changeWidth = _splitState == SplitState::Vertical;
|
||||
|
||||
const Size actualSize{ gsl::narrow_cast<float>(_root.ActualWidth()),
|
||||
gsl::narrow_cast<float>(_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:
|
||||
// - Does the bulk of the work of creating a new split. Initializes our UI,
|
||||
// creates a new Pane to host the control, registers event handlers.
|
||||
|
||||
@@ -47,16 +47,10 @@ public:
|
||||
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, const GUID& profile);
|
||||
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
|
||||
bool ResizePane(const winrt::TerminalApp::Direction& direction);
|
||||
bool NavigateFocus(const winrt::TerminalApp::Direction& direction);
|
||||
|
||||
bool CanSplitHorizontal();
|
||||
void SplitHorizontal(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
|
||||
bool CanSplitVertical();
|
||||
void SplitVertical(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
|
||||
void Close();
|
||||
|
||||
DECLARE_EVENT(Closed, _closedHandlers, winrt::Microsoft::Terminal::TerminalControl::ConnectionClosedEventArgs);
|
||||
|
||||
private:
|
||||
@@ -82,14 +76,11 @@ private:
|
||||
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();
|
||||
void _ApplySplitDefinitions();
|
||||
|
||||
bool _Resize(const winrt::TerminalApp::Direction& direction);
|
||||
bool _NavigateFocus(const winrt::TerminalApp::Direction& direction);
|
||||
|
||||
void _CloseChild(const bool closeFirst);
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ static constexpr std::string_view CursorColorKey{ "cursorColor" };
|
||||
static constexpr std::string_view CursorShapeKey{ "cursorShape" };
|
||||
static constexpr std::string_view CursorHeightKey{ "cursorHeight" };
|
||||
|
||||
static constexpr std::string_view ConnectionTypeKey{ "connectionType" };
|
||||
static constexpr std::string_view CommandlineKey{ "commandline" };
|
||||
static constexpr std::string_view FontFaceKey{ "fontFace" };
|
||||
static constexpr std::string_view FontSizeKey{ "fontSize" };
|
||||
@@ -39,8 +38,7 @@ static constexpr std::string_view StartingDirectoryKey{ "startingDirectory" };
|
||||
static constexpr std::string_view IconKey{ "icon" };
|
||||
static constexpr std::string_view BackgroundImageKey{ "backgroundImage" };
|
||||
static constexpr std::string_view BackgroundImageOpacityKey{ "backgroundImageOpacity" };
|
||||
static constexpr std::string_view BackgroundImageStretchModeKey{ "backgroundImageStretchMode" };
|
||||
static constexpr std::string_view BackgroundImageAlignmentKey{ "backgroundImageAlignment" };
|
||||
static constexpr std::string_view BackgroundimageStretchModeKey{ "backgroundImageStretchMode" };
|
||||
|
||||
// Possible values for Scrollbar state
|
||||
static constexpr std::wstring_view AlwaysVisible{ L"visible" };
|
||||
@@ -59,17 +57,6 @@ static constexpr std::string_view ImageStretchModeFill{ "fill" };
|
||||
static constexpr std::string_view ImageStretchModeUniform{ "uniform" };
|
||||
static constexpr std::string_view ImageStretchModeUniformTofill{ "uniformToFill" };
|
||||
|
||||
// Possible values for Image Alignment
|
||||
static constexpr std::string_view ImageAlignmentCenter{ "center" };
|
||||
static constexpr std::string_view ImageAlignmentLeft{ "left" };
|
||||
static constexpr std::string_view ImageAlignmentTop{ "top" };
|
||||
static constexpr std::string_view ImageAlignmentRight{ "right" };
|
||||
static constexpr std::string_view ImageAlignmentBottom{ "bottom" };
|
||||
static constexpr std::string_view ImageAlignmentTopLeft{ "topLeft" };
|
||||
static constexpr std::string_view ImageAlignmentTopRight{ "topRight" };
|
||||
static constexpr std::string_view ImageAlignmentBottomLeft{ "bottomLeft" };
|
||||
static constexpr std::string_view ImageAlignmentBottomRight{ "bottomRight" };
|
||||
|
||||
Profile::Profile() :
|
||||
Profile(Utils::CreateGuid())
|
||||
{
|
||||
@@ -90,7 +77,6 @@ Profile::Profile(const winrt::guid& guid) :
|
||||
_cursorShape{ CursorStyle::Bar },
|
||||
_cursorHeight{ DEFAULT_CURSOR_HEIGHT },
|
||||
|
||||
_connectionType{},
|
||||
_commandline{ L"cmd.exe" },
|
||||
_startingDirectory{},
|
||||
_fontFace{ DEFAULT_FONT_FACE },
|
||||
@@ -103,8 +89,7 @@ Profile::Profile(const winrt::guid& guid) :
|
||||
_icon{},
|
||||
_backgroundImage{},
|
||||
_backgroundImageOpacity{},
|
||||
_backgroundImageStretchMode{},
|
||||
_backgroundImageAlignment{}
|
||||
_backgroundImageStretchMode{}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -150,8 +135,7 @@ TerminalSettings Profile::CreateTerminalSettings(const std::vector<ColorScheme>&
|
||||
TerminalSettings terminalSettings{};
|
||||
|
||||
// Fill in the Terminal Setting's CoreSettings from the profile
|
||||
auto const colorTableCount = gsl::narrow_cast<int>(_colorTable.size());
|
||||
for (int i = 0; i < colorTableCount; i++)
|
||||
for (int i = 0; i < _colorTable.size(); i++)
|
||||
{
|
||||
terminalSettings.SetColorTableEntry(i, _colorTable[i]);
|
||||
}
|
||||
@@ -178,10 +162,6 @@ TerminalSettings Profile::CreateTerminalSettings(const std::vector<ColorScheme>&
|
||||
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());
|
||||
@@ -220,14 +200,6 @@ TerminalSettings Profile::CreateTerminalSettings(const std::vector<ColorScheme>&
|
||||
terminalSettings.BackgroundImageStretchMode(_backgroundImageStretchMode.value());
|
||||
}
|
||||
|
||||
if (_backgroundImageAlignment)
|
||||
{
|
||||
const auto imageHorizontalAlignment = std::get<winrt::Windows::UI::Xaml::HorizontalAlignment>(_backgroundImageAlignment.value());
|
||||
const auto imageVerticalAlignment = std::get<winrt::Windows::UI::Xaml::VerticalAlignment>(_backgroundImageAlignment.value());
|
||||
terminalSettings.BackgroundImageHorizontalAlignment(imageHorizontalAlignment);
|
||||
terminalSettings.BackgroundImageVerticalAlignment(imageVerticalAlignment);
|
||||
}
|
||||
|
||||
return terminalSettings;
|
||||
}
|
||||
|
||||
@@ -287,10 +259,6 @@ Json::Value Profile::ToJson() const
|
||||
root[JsonKey(CloseOnExitKey)] = _closeOnExit;
|
||||
root[JsonKey(PaddingKey)] = winrt::to_string(_padding);
|
||||
|
||||
if (_connectionType)
|
||||
{
|
||||
root[JsonKey(ConnectionTypeKey)] = winrt::to_string(Utils::GuidToString(_connectionType.value()));
|
||||
}
|
||||
if (_scrollbarState)
|
||||
{
|
||||
const auto scrollbarState = winrt::to_string(_scrollbarState.value());
|
||||
@@ -325,12 +293,7 @@ Json::Value Profile::ToJson() const
|
||||
|
||||
if (_backgroundImageStretchMode)
|
||||
{
|
||||
root[JsonKey(BackgroundImageStretchModeKey)] = SerializeImageStretchMode(_backgroundImageStretchMode.value()).data();
|
||||
}
|
||||
|
||||
if (_backgroundImageAlignment)
|
||||
{
|
||||
root[JsonKey(BackgroundImageAlignmentKey)] = SerializeImageAlignment(_backgroundImageAlignment.value()).data();
|
||||
root[JsonKey(BackgroundimageStretchModeKey)] = SerializeImageStretchMode(_backgroundImageStretchMode.value()).data();
|
||||
}
|
||||
|
||||
return root;
|
||||
@@ -355,20 +318,6 @@ Profile Profile::FromJson(const Json::Value& json)
|
||||
{
|
||||
result._guid = Utils::GuidFromString(GetWstringFromJson(guid));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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
|
||||
if (auto foreground{ json[JsonKey(ForegroundKey)] })
|
||||
@@ -431,10 +380,6 @@ Profile Profile::FromJson(const Json::Value& json)
|
||||
}
|
||||
|
||||
// Control Settings
|
||||
if (auto connectionType{ json[JsonKey(ConnectionTypeKey)] })
|
||||
{
|
||||
result._connectionType = Utils::GuidFromString(GetWstringFromJson(connectionType));
|
||||
}
|
||||
if (auto commandline{ json[JsonKey(CommandlineKey)] })
|
||||
{
|
||||
result._commandline = GetWstringFromJson(commandline);
|
||||
@@ -483,26 +428,22 @@ Profile Profile::FromJson(const Json::Value& json)
|
||||
{
|
||||
result._backgroundImageOpacity = backgroundImageOpacity.asFloat();
|
||||
}
|
||||
if (auto backgroundImageStretchMode{ json[JsonKey(BackgroundImageStretchModeKey)] })
|
||||
if (auto backgroundImageStretchMode{ json[JsonKey(BackgroundimageStretchModeKey)] })
|
||||
{
|
||||
result._backgroundImageStretchMode = ParseImageStretchMode(backgroundImageStretchMode.asString());
|
||||
}
|
||||
if (auto backgroundImageAlignment{ json[JsonKey(BackgroundImageAlignmentKey)] })
|
||||
{
|
||||
result._backgroundImageAlignment = ParseImageAlignment(backgroundImageAlignment.asString());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Profile::SetFontFace(std::wstring fontFace) noexcept
|
||||
{
|
||||
_fontFace = std::move(fontFace);
|
||||
_fontFace = fontFace;
|
||||
}
|
||||
|
||||
void Profile::SetColorScheme(std::optional<std::wstring> schemeName) noexcept
|
||||
{
|
||||
_schemeName = std::move(schemeName);
|
||||
_schemeName = schemeName;
|
||||
}
|
||||
|
||||
void Profile::SetAcrylicOpacity(double opacity) noexcept
|
||||
@@ -512,17 +453,17 @@ void Profile::SetAcrylicOpacity(double opacity) noexcept
|
||||
|
||||
void Profile::SetCommandline(std::wstring cmdline) noexcept
|
||||
{
|
||||
_commandline = std::move(cmdline);
|
||||
_commandline = cmdline;
|
||||
}
|
||||
|
||||
void Profile::SetStartingDirectory(std::wstring startingDirectory) noexcept
|
||||
{
|
||||
_startingDirectory = std::move(startingDirectory);
|
||||
_startingDirectory = startingDirectory;
|
||||
}
|
||||
|
||||
void Profile::SetName(std::wstring name) noexcept
|
||||
{
|
||||
_name = std::move(name);
|
||||
_name = name;
|
||||
}
|
||||
|
||||
void Profile::SetUseAcrylic(bool useAcrylic) noexcept
|
||||
@@ -540,19 +481,9 @@ void Profile::SetDefaultBackground(COLORREF defaultBackground) noexcept
|
||||
_defaultBackground = defaultBackground;
|
||||
}
|
||||
|
||||
void Profile::SetCloseOnExit(bool defaultClose) noexcept
|
||||
{
|
||||
_closeOnExit = defaultClose;
|
||||
}
|
||||
|
||||
void Profile::SetConnectionType(GUID connectionType) noexcept
|
||||
{
|
||||
_connectionType = connectionType;
|
||||
}
|
||||
|
||||
bool Profile::HasIcon() const noexcept
|
||||
{
|
||||
return _icon.has_value() && !_icon.value().empty();
|
||||
return _icon.has_value();
|
||||
}
|
||||
|
||||
// Method Description
|
||||
@@ -561,33 +492,27 @@ bool Profile::HasIcon() const noexcept
|
||||
// - tabTitle: the tab title
|
||||
void Profile::SetTabTitle(std::wstring tabTitle) noexcept
|
||||
{
|
||||
_tabTitle = std::move(tabTitle);
|
||||
_tabTitle = tabTitle;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets this profile's icon path.
|
||||
// Arguments:
|
||||
// - path: the path
|
||||
void Profile::SetIconPath(std::wstring_view path)
|
||||
void Profile::SetIconPath(std::wstring_view path) noexcept
|
||||
{
|
||||
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. This method will expand any environment variables in the
|
||||
// path, if there are any.
|
||||
// - Returns this profile's icon path, if one is set. Otherwise returns the empty string.
|
||||
// Return Value:
|
||||
// - this profile's icon path, if one is set. Otherwise returns the empty string.
|
||||
winrt::hstring Profile::GetExpandedIconPath() const
|
||||
std::wstring_view Profile::GetIconPath() const noexcept
|
||||
{
|
||||
if (!HasIcon())
|
||||
{
|
||||
return { L"" };
|
||||
}
|
||||
winrt::hstring envExpandedPath{ wil::ExpandEnvironmentStringsW<std::wstring>(_icon.value().data()) };
|
||||
return envExpandedPath;
|
||||
return HasIcon() ?
|
||||
std::wstring_view{ _icon.value().c_str(), _icon.value().size() } :
|
||||
std::wstring_view{ L"", 0 };
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -601,16 +526,24 @@ std::wstring_view Profile::GetName() const noexcept
|
||||
return _name;
|
||||
}
|
||||
|
||||
bool Profile::HasConnectionType() const noexcept
|
||||
// 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 _connectionType.has_value();
|
||||
return _tabTitle.has_value();
|
||||
}
|
||||
|
||||
GUID Profile::GetConnectionType() const noexcept
|
||||
// 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 HasConnectionType() ?
|
||||
_connectionType.value() :
|
||||
_GUID{};
|
||||
return HasTabTitle() ?
|
||||
std::wstring_view{ _tabTitle.value().c_str(), _tabTitle.value().size() } :
|
||||
std::wstring_view{ L"", 0 };
|
||||
}
|
||||
|
||||
bool Profile::GetCloseOnExit() const noexcept
|
||||
@@ -721,114 +654,6 @@ std::string_view Profile::SerializeImageStretchMode(const winrt::Windows::UI::Xa
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper function for converting a user-specified image horizontal and vertical
|
||||
// alignment to the appropriate enum values tuple
|
||||
// Arguments:
|
||||
// - The value from the profiles.json file
|
||||
// Return Value:
|
||||
// - The corresponding enum values tuple which maps to the string provided by the user
|
||||
std::tuple<winrt::Windows::UI::Xaml::HorizontalAlignment, winrt::Windows::UI::Xaml::VerticalAlignment> Profile::ParseImageAlignment(const std::string_view imageAlignment)
|
||||
{
|
||||
if (imageAlignment == ImageAlignmentTopLeft)
|
||||
{
|
||||
return std::make_tuple(winrt::Windows::UI::Xaml::HorizontalAlignment::Left,
|
||||
winrt::Windows::UI::Xaml::VerticalAlignment::Top);
|
||||
}
|
||||
else if (imageAlignment == ImageAlignmentBottomLeft)
|
||||
{
|
||||
return std::make_tuple(winrt::Windows::UI::Xaml::HorizontalAlignment::Left,
|
||||
winrt::Windows::UI::Xaml::VerticalAlignment::Bottom);
|
||||
}
|
||||
else if (imageAlignment == ImageAlignmentLeft)
|
||||
{
|
||||
return std::make_tuple(winrt::Windows::UI::Xaml::HorizontalAlignment::Left,
|
||||
winrt::Windows::UI::Xaml::VerticalAlignment::Center);
|
||||
}
|
||||
else if (imageAlignment == ImageAlignmentTopRight)
|
||||
{
|
||||
return std::make_tuple(winrt::Windows::UI::Xaml::HorizontalAlignment::Right,
|
||||
winrt::Windows::UI::Xaml::VerticalAlignment::Top);
|
||||
}
|
||||
else if (imageAlignment == ImageAlignmentBottomRight)
|
||||
{
|
||||
return std::make_tuple(winrt::Windows::UI::Xaml::HorizontalAlignment::Right,
|
||||
winrt::Windows::UI::Xaml::VerticalAlignment::Bottom);
|
||||
}
|
||||
else if (imageAlignment == ImageAlignmentRight)
|
||||
{
|
||||
return std::make_tuple(winrt::Windows::UI::Xaml::HorizontalAlignment::Right,
|
||||
winrt::Windows::UI::Xaml::VerticalAlignment::Center);
|
||||
}
|
||||
else if (imageAlignment == ImageAlignmentTop)
|
||||
{
|
||||
return std::make_tuple(winrt::Windows::UI::Xaml::HorizontalAlignment::Center,
|
||||
winrt::Windows::UI::Xaml::VerticalAlignment::Top);
|
||||
}
|
||||
else if (imageAlignment == ImageAlignmentBottom)
|
||||
{
|
||||
return std::make_tuple(winrt::Windows::UI::Xaml::HorizontalAlignment::Center,
|
||||
winrt::Windows::UI::Xaml::VerticalAlignment::Bottom);
|
||||
}
|
||||
else // Fall through to default alignment
|
||||
{
|
||||
return std::make_tuple(winrt::Windows::UI::Xaml::HorizontalAlignment::Center,
|
||||
winrt::Windows::UI::Xaml::VerticalAlignment::Center);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper function for converting the HorizontalAlignment+VerticalAlignment tuple
|
||||
// to the correct string value.
|
||||
// Arguments:
|
||||
// - imageAlignment: The enum values tuple to convert to a string.
|
||||
// Return Value:
|
||||
// - The string value for the given ImageAlignment
|
||||
std::string_view Profile::SerializeImageAlignment(const std::tuple<winrt::Windows::UI::Xaml::HorizontalAlignment, winrt::Windows::UI::Xaml::VerticalAlignment> imageAlignment)
|
||||
{
|
||||
const auto imageHorizontalAlignment = std::get<winrt::Windows::UI::Xaml::HorizontalAlignment>(imageAlignment);
|
||||
const auto imageVerticalAlignment = std::get<winrt::Windows::UI::Xaml::VerticalAlignment>(imageAlignment);
|
||||
switch (imageHorizontalAlignment)
|
||||
{
|
||||
case winrt::Windows::UI::Xaml::HorizontalAlignment::Left:
|
||||
switch (imageVerticalAlignment)
|
||||
{
|
||||
case winrt::Windows::UI::Xaml::VerticalAlignment::Top:
|
||||
return ImageAlignmentTopLeft;
|
||||
case winrt::Windows::UI::Xaml::VerticalAlignment::Bottom:
|
||||
return ImageAlignmentBottomLeft;
|
||||
default:
|
||||
case winrt::Windows::UI::Xaml::VerticalAlignment::Center:
|
||||
return ImageAlignmentLeft;
|
||||
}
|
||||
|
||||
case winrt::Windows::UI::Xaml::HorizontalAlignment::Right:
|
||||
switch (imageVerticalAlignment)
|
||||
{
|
||||
case winrt::Windows::UI::Xaml::VerticalAlignment::Top:
|
||||
return ImageAlignmentTopRight;
|
||||
case winrt::Windows::UI::Xaml::VerticalAlignment::Bottom:
|
||||
return ImageAlignmentBottomRight;
|
||||
default:
|
||||
case winrt::Windows::UI::Xaml::VerticalAlignment::Center:
|
||||
return ImageAlignmentRight;
|
||||
}
|
||||
|
||||
default:
|
||||
case winrt::Windows::UI::Xaml::HorizontalAlignment::Center:
|
||||
switch (imageVerticalAlignment)
|
||||
{
|
||||
case winrt::Windows::UI::Xaml::VerticalAlignment::Top:
|
||||
return ImageAlignmentTop;
|
||||
case winrt::Windows::UI::Xaml::VerticalAlignment::Bottom:
|
||||
return ImageAlignmentBottom;
|
||||
default:
|
||||
case winrt::Windows::UI::Xaml::VerticalAlignment::Center:
|
||||
return ImageAlignmentCenter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper function for converting a user-specified cursor style corresponding
|
||||
// CursorStyle enum value
|
||||
|
||||
@@ -16,10 +16,6 @@ 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;
|
||||
@@ -40,8 +36,8 @@ public:
|
||||
|
||||
GUID GetGuid() const noexcept;
|
||||
std::wstring_view GetName() const noexcept;
|
||||
bool HasConnectionType() const noexcept;
|
||||
GUID GetConnectionType() const noexcept;
|
||||
bool HasTabTitle() const noexcept;
|
||||
std::wstring_view GetTabTitle() const noexcept;
|
||||
|
||||
void SetFontFace(std::wstring fontFace) noexcept;
|
||||
void SetColorScheme(std::optional<std::wstring> schemeName) noexcept;
|
||||
@@ -53,12 +49,10 @@ public:
|
||||
void SetUseAcrylic(bool useAcrylic) noexcept;
|
||||
void SetDefaultForeground(COLORREF defaultForeground) noexcept;
|
||||
void SetDefaultBackground(COLORREF defaultBackground) noexcept;
|
||||
void SetCloseOnExit(bool defaultClose) noexcept;
|
||||
void SetConnectionType(GUID connectionType) noexcept;
|
||||
|
||||
bool HasIcon() const noexcept;
|
||||
winrt::hstring GetExpandedIconPath() const;
|
||||
void SetIconPath(std::wstring_view path);
|
||||
std::wstring_view GetIconPath() const noexcept;
|
||||
void SetIconPath(std::wstring_view path) noexcept;
|
||||
|
||||
bool GetCloseOnExit() const noexcept;
|
||||
|
||||
@@ -68,14 +62,11 @@ private:
|
||||
static winrt::Microsoft::Terminal::Settings::ScrollbarState ParseScrollbarState(const std::wstring& scrollbarState);
|
||||
static winrt::Windows::UI::Xaml::Media::Stretch ParseImageStretchMode(const std::string_view imageStretchMode);
|
||||
static std::string_view SerializeImageStretchMode(const winrt::Windows::UI::Xaml::Media::Stretch imageStretchMode);
|
||||
static std::tuple<winrt::Windows::UI::Xaml::HorizontalAlignment, winrt::Windows::UI::Xaml::VerticalAlignment> ParseImageAlignment(const std::string_view imageAlignment);
|
||||
static std::string_view SerializeImageAlignment(const std::tuple<winrt::Windows::UI::Xaml::HorizontalAlignment, winrt::Windows::UI::Xaml::VerticalAlignment> imageAlignment);
|
||||
static winrt::Microsoft::Terminal::Settings::CursorStyle _ParseCursorShape(const std::wstring& cursorShapeString);
|
||||
static std::wstring_view _SerializeCursorStyle(const winrt::Microsoft::Terminal::Settings::CursorStyle cursorShape);
|
||||
|
||||
GUID _guid;
|
||||
std::wstring _name;
|
||||
std::optional<GUID> _connectionType;
|
||||
|
||||
// If this is set, then our colors should come from the associated color scheme
|
||||
std::optional<std::wstring> _schemeName;
|
||||
@@ -100,7 +91,6 @@ private:
|
||||
std::optional<std::wstring> _backgroundImage;
|
||||
std::optional<double> _backgroundImageOpacity;
|
||||
std::optional<winrt::Windows::UI::Xaml::Media::Stretch> _backgroundImageStretchMode;
|
||||
std::optional<std::tuple<winrt::Windows::UI::Xaml::HorizontalAlignment, winrt::Windows::UI::Xaml::VerticalAlignment>> _backgroundImageAlignment;
|
||||
|
||||
std::optional<std::wstring> _scrollbarState;
|
||||
bool _closeOnExit;
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="InitialJsonParseErrorText" xml:space="preserve">
|
||||
<value>Settings could not be loaded from file. Check for syntax errors, including trailing commas.
|
||||
</value>
|
||||
</data>
|
||||
<data name="MissingDefaultProfileText" xml:space="preserve">
|
||||
<value>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.
|
||||
</value>
|
||||
</data>
|
||||
<data name="DuplicateProfileText" xml:space="preserve">
|
||||
<value>Found multiple profiles with the same GUID in your settings file - ignoring duplicates. Make sure each profile's GUID is unique.
|
||||
</value>
|
||||
</data>
|
||||
<data name="NoProfilesText" xml:space="preserve">
|
||||
<value>No profiles were found in your settings.
|
||||
</value>
|
||||
</data>
|
||||
<data name="ReloadJsonParseErrorText" xml:space="preserve">
|
||||
<value>Settings could not be reloaded from file. Check for syntax errors, including trailing commas.
|
||||
</value>
|
||||
</data>
|
||||
<data name="UsingDefaultSettingsText" xml:space="preserve">
|
||||
<value>
|
||||
Temporarily using the Windows Terminal default settings.
|
||||
</value>
|
||||
</data>
|
||||
<data name="InitialJsonParseErrorTitle" xml:space="preserve">
|
||||
<value>Failed to load settings</value>
|
||||
</data>
|
||||
<data name="SettingsValidateErrorTitle" xml:space="preserve">
|
||||
<value>Encountered errors while loading user settings</value>
|
||||
</data>
|
||||
<data name="Ok" xml:space="preserve">
|
||||
<value>Ok</value>
|
||||
</data>
|
||||
<data name="ReloadJsonParseErrorTitle" xml:space="preserve">
|
||||
<value>Failed to reload settings</value>
|
||||
</data>
|
||||
<data name="AboutTitleText" xml:space="preserve">
|
||||
<value>About</value>
|
||||
</data>
|
||||
<data name="VersionLabelText" xml:space="preserve">
|
||||
<value>Version:</value>
|
||||
</data>
|
||||
<data name="DocumentationLabelText" xml:space="preserve">
|
||||
<value>Documentation
|
||||
</value>
|
||||
</data>
|
||||
<data name="GettingStartedLabelText" xml:space="preserve">
|
||||
<value>Getting Started
|
||||
</value>
|
||||
</data>
|
||||
<data name="ReleaseNotesLabelText" xml:space="preserve">
|
||||
<value>Release Notes
|
||||
</value>
|
||||
</data>
|
||||
<data name="DocumentationUriValue" xml:space="preserve">
|
||||
<value>https://aka.ms/terminal-documentation</value>
|
||||
</data>
|
||||
<data name="GettingStartedUriValue" xml:space="preserve">
|
||||
<value>https://aka.ms/terminal-getting-started</value>
|
||||
</data>
|
||||
<data name="ReleaseNotesUriValue" xml:space="preserve">
|
||||
<value>https://aka.ms/terminal-release-notes</value>
|
||||
</data>
|
||||
<data name="FeedbackUriValue" xml:space="preserve">
|
||||
<value>https://aka.ms/terminal-feedback</value>
|
||||
</data>
|
||||
<data name="AboutMenuItem" xml:space="preserve">
|
||||
<value>About</value>
|
||||
</data>
|
||||
<data name="FeedbackMenuItem" xml:space="preserve">
|
||||
<value>Feedback</value>
|
||||
</data>
|
||||
<data name="SettingsMenuItem" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1,37 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include "ScopedResourceLoader.h"
|
||||
|
||||
using namespace ::winrt::Windows::ApplicationModel::Resources::Core;
|
||||
|
||||
ScopedResourceLoader::ScopedResourceLoader(const std::wstring_view resourceLocatorBase) :
|
||||
_resourceMap{ ResourceManager::Current().MainResourceMap().GetSubtree(resourceLocatorBase) },
|
||||
_resourceContext{ ResourceContext::GetForViewIndependentUse() }
|
||||
{
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Gets the resource map associated with the scoped resource subcompartment.
|
||||
// Return Value:
|
||||
// - the resource map associated with the scoped resource subcompartment.
|
||||
ResourceMap ScopedResourceLoader::GetResourceMap()
|
||||
{
|
||||
return _resourceMap;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Loads the localized string resource with the given key from the scoped
|
||||
// resource subcompartment.
|
||||
// - This resource loader is view-independent; it cannot take into account scale
|
||||
// factors and view themes. Strings should not vary based on those things.
|
||||
// Arguments:
|
||||
// - resourceName: the key up by which to look the resource
|
||||
// Return Value:
|
||||
// - The final localized string for the given key.
|
||||
winrt::hstring ScopedResourceLoader::GetLocalizedString(const std::wstring_view resourceName)
|
||||
{
|
||||
return _resourceMap.GetValue(resourceName, _resourceContext).ValueAsString();
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
class ScopedResourceLoader
|
||||
{
|
||||
public:
|
||||
ScopedResourceLoader(const std::wstring_view resourceLocatorBase);
|
||||
winrt::Windows::ApplicationModel::Resources::Core::ResourceMap GetResourceMap();
|
||||
winrt::hstring GetLocalizedString(const std::wstring_view resourceName);
|
||||
|
||||
private:
|
||||
winrt::Windows::ApplicationModel::Resources::Core::ResourceMap _resourceMap;
|
||||
winrt::Windows::ApplicationModel::Resources::Core::ResourceContext _resourceContext;
|
||||
};
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include "Tab.h"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Core;
|
||||
@@ -125,7 +124,7 @@ void Tab::_Focus()
|
||||
auto lastFocusedControl = _rootPane->GetFocusedTerminalControl();
|
||||
if (lastFocusedControl)
|
||||
{
|
||||
lastFocusedControl.Focus(FocusState::Programmatic);
|
||||
lastFocusedControl.GetControl().Focus(FocusState::Programmatic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,21 +142,6 @@ 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.
|
||||
@@ -197,21 +181,12 @@ void Tab::SetTabText(const winrt::hstring& text)
|
||||
void Tab::Scroll(const int delta)
|
||||
{
|
||||
auto control = GetFocusedTerminalControl();
|
||||
control.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [control, delta]() {
|
||||
control.GetControl().Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [control, delta]() {
|
||||
const auto currentOffset = control.GetScrollOffset();
|
||||
control.KeyboardScrollViewport(currentOffset + delta);
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Determines whether the focused pane has sufficient space to be split vertically.
|
||||
// Return Value:
|
||||
// - True if the focused pane can be split horizontally. False otherwise.
|
||||
bool Tab::CanAddVerticalSplit()
|
||||
{
|
||||
return _rootPane->CanSplitVertical();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Vertically split the focused pane in our tree of panes, and place the
|
||||
// given TermControl into the newly created pane.
|
||||
@@ -225,15 +200,6 @@ void Tab::AddVerticalSplit(const GUID& profile, TermControl& control)
|
||||
_rootPane->SplitVertical(profile, control);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Determines whether the focused pane has sufficient space to be split horizontally.
|
||||
// Return Value:
|
||||
// - True if the focused pane can be split horizontally. False otherwise.
|
||||
bool Tab::CanAddHorizontalSplit()
|
||||
{
|
||||
return _rootPane->CanSplitHorizontal();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Horizontally split the focused pane in our tree of panes, and place the
|
||||
// given TermControl into the newly created pane.
|
||||
@@ -271,30 +237,4 @@ void Tab::ResizePane(const winrt::TerminalApp::Direction& direction)
|
||||
_rootPane->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.
|
||||
// Arguments:
|
||||
// - direction: The direction to move the focus in.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Tab::NavigateFocus(const winrt::TerminalApp::Direction& direction)
|
||||
{
|
||||
_rootPane->NavigateFocus(direction);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Closes the currently focused pane in this tab. If it's the last pane in
|
||||
// this tab, our Closed event will be fired (at a later time) for anyone
|
||||
// registered as a handler of our close event.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Tab::ClosePane()
|
||||
{
|
||||
auto focused = _rootPane->GetFocusedPane();
|
||||
focused->Close();
|
||||
}
|
||||
|
||||
DEFINE_EVENT(Tab, Closed, _closedHandlers, ConnectionClosedEventArgs);
|
||||
|
||||
@@ -19,29 +19,21 @@ public:
|
||||
void SetFocused(const bool focused);
|
||||
|
||||
void Scroll(const int delta);
|
||||
bool CanAddVerticalSplit();
|
||||
void AddVerticalSplit(const GUID& profile, winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
bool CanAddHorizontalSplit();
|
||||
void AddHorizontalSplit(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);
|
||||
|
||||
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, const GUID& profile);
|
||||
winrt::hstring GetFocusedTitle() const;
|
||||
void SetTabText(const winrt::hstring& text);
|
||||
|
||||
void ClosePane();
|
||||
|
||||
DECLARE_EVENT(Closed, _closedHandlers, winrt::Microsoft::Terminal::TerminalControl::ConnectionClosedEventArgs);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Pane> _rootPane{ nullptr };
|
||||
winrt::hstring _lastIconPath{};
|
||||
|
||||
bool _focused{ false };
|
||||
winrt::Microsoft::UI::Xaml::Controls::TabViewItem _tabViewItem{ nullptr };
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "TabRowControl.h"
|
||||
|
||||
#include "TabRowControl.g.cpp"
|
||||
|
||||
using namespace winrt;
|
||||
using namespace Windows::UI::Xaml;
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
TabRowControl::TabRowControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Bound in the Xaml editor to the [+] button.
|
||||
// Arguments:
|
||||
// <unused>
|
||||
void TabRowControl::OnNewTabButtonClick(IInspectable const&, Controls::SplitButtonClickEventArgs const&)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "winrt/Microsoft.UI.Xaml.Controls.h"
|
||||
|
||||
#include "TabRowControl.g.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct TabRowControl : TabRowControlT<TabRowControl>
|
||||
{
|
||||
TabRowControl();
|
||||
|
||||
void OnNewTabButtonClick(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Controls::SplitButtonClickEventArgs const& args);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
struct TabRowControl : TabRowControlT<TabRowControl, implementation::TabRowControl>
|
||||
{
|
||||
};
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass TabRowControl : Windows.UI.Xaml.Controls.Grid
|
||||
{
|
||||
TabRowControl();
|
||||
Windows.UI.Xaml.Controls.SplitButton NewTabButton { get; };
|
||||
Microsoft.UI.Xaml.Controls.TabView TabView { get; };
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
|
||||
the MIT License. See LICENSE in the project root for license information. -->
|
||||
<Grid
|
||||
x:Class="TerminalApp.TabRowControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:TerminalApp"
|
||||
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<mux:TabView x:Name="TabView" VerticalAlignment="Bottom">
|
||||
|
||||
<mux:TabView.RightCustomContent>
|
||||
<SplitButton
|
||||
x:Name="NewTabButton"
|
||||
Click="OnNewTabButtonClick"
|
||||
Background="{ThemeResource SystemChromeLowColor}"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Left">
|
||||
<Viewbox MaxHeight="13"
|
||||
MaxWidth="13">
|
||||
<SymbolIcon Symbol="Add" />
|
||||
</Viewbox>
|
||||
</SplitButton>
|
||||
</mux:TabView.RightCustomContent>
|
||||
|
||||
</mux:TabView>
|
||||
|
||||
</Grid>
|
||||
@@ -30,8 +30,6 @@
|
||||
<ClInclude Include="TerminalPage.h" />
|
||||
<ClInclude Include="MinMaxCloseControl.h" />
|
||||
<ClInclude Include="AppKeyBindings.h" />
|
||||
<ClInclude Include="TitlebarControl.h" />
|
||||
<ClInclude Include="TabRowControl.h" />
|
||||
<ClInclude Include="App.h" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= Cpp Files ======================== -->
|
||||
@@ -99,18 +97,10 @@
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>User32.lib;WindowsApp.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
|
||||
<!-- TerminalAppLib contains a DllMain that we need to force the use of. -->
|
||||
<AdditionalOptions Condition="'$(Platform)'=='Win32'">/INCLUDE:_DllMain@12</AdditionalOptions>
|
||||
<AdditionalOptions Condition="'$(Platform)'!='Win32'">/INCLUDE:DllMain</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="$(OpenConsoleDir)src\common.build.post.props" />
|
||||
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
|
||||
|
||||
<!-- Import this set of targets that fixes a VS bug that manifests when using
|
||||
the TerminalAppLib project -->
|
||||
<Import Project="FixVisualStudioBug.targets" />
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -15,4 +15,13 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Bound in the Xaml editor to the [+] button.
|
||||
// Arguments:
|
||||
// - sender
|
||||
// - event arguments
|
||||
void TerminalPage::OnNewTabButtonClick(IInspectable const&, Controls::SplitButtonClickEventArgs const&)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace winrt::TerminalApp::implementation
|
||||
struct TerminalPage : TerminalPageT<TerminalPage>
|
||||
{
|
||||
TerminalPage();
|
||||
|
||||
void OnNewTabButtonClick(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Controls::SplitButtonClickEventArgs const& args);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass TerminalPage : Windows.UI.Xaml.Controls.Page
|
||||
[default_interface]
|
||||
runtimeclass TerminalPage : Windows.UI.Xaml.Controls.Page
|
||||
{
|
||||
TerminalPage();
|
||||
}
|
||||
|
||||
@@ -10,13 +10,39 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid x:Name="Root" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<local:TabRowControl x:Name="TabRow" Grid.Row="0" />
|
||||
<Grid x:Name="TabRow" Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<mux:TabView x:Name="TabView" Grid.Column="0" />
|
||||
|
||||
<SplitButton
|
||||
x:Name="NewTabButton"
|
||||
Grid.Column="1"
|
||||
Click="OnNewTabButtonClick"
|
||||
Background="{ThemeResource SystemChromeLowColor}"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Left">
|
||||
<Viewbox MaxHeight="15"
|
||||
MaxWidth="15">
|
||||
<SymbolIcon Symbol="Add" />
|
||||
</Viewbox>
|
||||
</SplitButton>
|
||||
|
||||
<local:MinMaxCloseControl
|
||||
x:Name="MinMaxCloseControl"
|
||||
Grid.Column="3"
|
||||
HorizontalAlignment="Right" />
|
||||
</Grid>
|
||||
|
||||
<Grid x:Name="TabContent" Grid.Row="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
|
||||
</Grid>
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/*++
|
||||
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;
|
||||
};
|
||||
};
|
||||
@@ -1,95 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// TitlebarControl.xaml.cpp
|
||||
// Implementation of the TitlebarControl class
|
||||
//
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include "TitlebarControl.h"
|
||||
|
||||
#include "TitlebarControl.g.cpp"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
TitlebarControl::TitlebarControl(uint64_t handle) :
|
||||
_window{ reinterpret_cast<HWND>(handle) }
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Register our event handlers on the MMC buttons.
|
||||
MinMaxCloseControl().MinimizeClick({ this, &TitlebarControl::Minimize_Click });
|
||||
MinMaxCloseControl().MaximizeClick({ this, &TitlebarControl::Maximize_Click });
|
||||
MinMaxCloseControl().CloseClick({ this, &TitlebarControl::Close_Click });
|
||||
}
|
||||
|
||||
Windows::UI::Xaml::UIElement TitlebarControl::Content()
|
||||
{
|
||||
return ContentRoot().Children().Size() > 0 ? ContentRoot().Children().GetAt(0) : nullptr;
|
||||
}
|
||||
|
||||
void TitlebarControl::Content(Windows::UI::Xaml::UIElement content)
|
||||
{
|
||||
ContentRoot().Children().Clear();
|
||||
ContentRoot().Children().Append(content);
|
||||
}
|
||||
|
||||
void TitlebarControl::Root_SizeChanged(const IInspectable& sender,
|
||||
const Windows::UI::Xaml::SizeChangedEventArgs& e)
|
||||
{
|
||||
const auto windowWidth = ActualWidth();
|
||||
const auto minMaxCloseWidth = MinMaxCloseControl().ActualWidth();
|
||||
const auto dragBarMinWidth = DragBar().MinWidth();
|
||||
const auto maxWidth = windowWidth - minMaxCloseWidth - dragBarMinWidth;
|
||||
// Only set our MaxWidth if it's greater than 0. Setting it to a
|
||||
// negative value will cause a crash.
|
||||
if (maxWidth >= 0)
|
||||
{
|
||||
ContentRoot().MaxWidth(maxWidth);
|
||||
}
|
||||
}
|
||||
|
||||
void TitlebarControl::_OnMaximizeOrRestore(byte flag)
|
||||
{
|
||||
POINT point1 = {};
|
||||
::GetCursorPos(&point1);
|
||||
const LPARAM lParam = MAKELPARAM(point1.x, point1.y);
|
||||
WINDOWPLACEMENT placement = { sizeof(placement) };
|
||||
::GetWindowPlacement(_window, &placement);
|
||||
if (placement.showCmd == SW_SHOWNORMAL)
|
||||
{
|
||||
MinMaxCloseControl().Maximize();
|
||||
::PostMessage(_window, WM_SYSCOMMAND, SC_MAXIMIZE | flag, lParam);
|
||||
}
|
||||
else if (placement.showCmd == SW_SHOWMAXIMIZED)
|
||||
{
|
||||
MinMaxCloseControl().RestoreDown();
|
||||
::PostMessage(_window, WM_SYSCOMMAND, SC_RESTORE | flag, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
void TitlebarControl::Maximize_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e)
|
||||
{
|
||||
_OnMaximizeOrRestore(HTMAXBUTTON);
|
||||
}
|
||||
|
||||
void TitlebarControl::DragBar_DoubleTapped(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Input::DoubleTappedRoutedEventArgs const& e)
|
||||
{
|
||||
_OnMaximizeOrRestore(HTCAPTION);
|
||||
}
|
||||
|
||||
void TitlebarControl::Minimize_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e)
|
||||
{
|
||||
if (_window)
|
||||
{
|
||||
::PostMessage(_window, WM_SYSCOMMAND, SC_MINIMIZE | HTMINBUTTON, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void TitlebarControl::Close_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e)
|
||||
{
|
||||
::PostQuitMessage(0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Declaration of the MainUserControl class.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "winrt/Windows.UI.Xaml.h"
|
||||
#include "winrt/Windows.UI.Xaml.Markup.h"
|
||||
#include "winrt/Windows.UI.Xaml.Interop.h"
|
||||
#include "TitlebarControl.g.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct TitlebarControl : TitlebarControlT<TitlebarControl>
|
||||
{
|
||||
TitlebarControl(uint64_t handle);
|
||||
|
||||
Windows::UI::Xaml::UIElement Content();
|
||||
void Content(Windows::UI::Xaml::UIElement content);
|
||||
|
||||
void Root_SizeChanged(const IInspectable& sender, Windows::UI::Xaml::SizeChangedEventArgs const& e);
|
||||
|
||||
void Minimize_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
void Maximize_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
void Close_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
void DragBar_DoubleTapped(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Input::DoubleTappedRoutedEventArgs const& e);
|
||||
|
||||
private:
|
||||
void _OnMaximizeOrRestore(byte flag);
|
||||
HWND _window{ nullptr }; // non-owning handle; should not be freed in the dtor.
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
struct TitlebarControl : TitlebarControlT<TitlebarControl, implementation::TitlebarControl>
|
||||
{
|
||||
};
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass TitlebarControl : Windows.UI.Xaml.Controls.Grid
|
||||
{
|
||||
TitlebarControl(UInt64 parentWindowHandle);
|
||||
|
||||
Windows.UI.Xaml.UIElement Content;
|
||||
Windows.UI.Xaml.Controls.Border DragBar { get; };
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
|
||||
the MIT License. See LICENSE in the project root for license information. -->
|
||||
<Grid
|
||||
x:Class="TerminalApp.TitlebarControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:TerminalApp"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
x:Name="Root"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
SizeChanged="Root_SizeChanged"
|
||||
d:DesignHeight="36"
|
||||
Background="{ThemeResource SystemChromeLowColor}"
|
||||
d:DesignWidth="400">
|
||||
|
||||
<!-- TODO:GH#1988
|
||||
This xaml should probably be a template thing, where the background is a
|
||||
resource that the app hosting this control can override. Then, it App.xaml,
|
||||
we'd make sure to set the resource for our background to the appropriate
|
||||
color. SystemControlForegroundAccentBrush also works nicely, to use the
|
||||
accent color. (which is GH#1963)-->
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid x:Name="ContentRoot" Grid.Column="0" />
|
||||
|
||||
<Border
|
||||
x:Name="DragBar"
|
||||
Grid.Column="1"
|
||||
MinWidth="45.0"
|
||||
DoubleTapped="DragBar_DoubleTapped"/>
|
||||
|
||||
<local:MinMaxCloseControl
|
||||
Grid.Column="2"
|
||||
x:Name="MinMaxCloseControl"
|
||||
HorizontalAlignment="Right" />
|
||||
</Grid>
|
||||
|
||||
@@ -16,33 +16,3 @@ 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;
|
||||
}
|
||||
|
||||
@@ -28,5 +28,3 @@ inline std::string JsonKey(const std::string_view key)
|
||||
{
|
||||
return static_cast<std::string>(key);
|
||||
}
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::IconElement GetColoredIcon(const winrt::hstring& path);
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -35,12 +35,6 @@
|
||||
<Page Include="../TerminalPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="../TitlebarControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="../TabRowControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- ========================= Headers ======================== -->
|
||||
@@ -53,12 +47,6 @@
|
||||
<DependentUpon>../TerminalPage.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../TitlebarControl.h">
|
||||
<DependentUpon>../TitlebarControl.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../TabRowControl.h">
|
||||
<DependentUpon>../TabRowControl.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../Tab.h" />
|
||||
<ClInclude Include="../Pane.h" />
|
||||
<ClInclude Include="../ColorScheme.h" />
|
||||
@@ -68,14 +56,8 @@
|
||||
<ClInclude Include="../AppKeyBindingsSerialization.h" />
|
||||
<ClInclude Include="../KeyChordSerialization.h" />
|
||||
<ClInclude Include="../Utils.h" />
|
||||
<ClInclude Include="../TerminalWarnings.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="../ActionArgs.h" >
|
||||
<DependentUpon>../ActionArgs.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../AppKeyBindings.h" >
|
||||
<DependentUpon>../AppKeyBindings.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../AppKeyBindings.h" />
|
||||
<ClInclude Include="../App.h" >
|
||||
<DependentUpon>../App.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
@@ -83,7 +65,6 @@
|
||||
|
||||
<!-- ========================= Cpp Files ======================== -->
|
||||
<ItemGroup>
|
||||
<ClCompile Include="../init.cpp" />
|
||||
<ClCompile Include="../MinMaxCloseControl.cpp">
|
||||
<DependentUpon>../MinMaxCloseControl.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
@@ -91,12 +72,6 @@
|
||||
<DependentUpon>../TerminalPage.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../TitlebarControl.cpp">
|
||||
<DependentUpon>../TitlebarControl.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../TabRowControl.cpp">
|
||||
<DependentUpon>../TabRowControl.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../Tab.cpp" />
|
||||
<ClCompile Include="../Pane.cpp" />
|
||||
<ClCompile Include="../ColorScheme.cpp" />
|
||||
@@ -107,22 +82,15 @@
|
||||
<ClCompile Include="../AppKeyBindingsSerialization.cpp" />
|
||||
<ClCompile Include="../KeyChordSerialization.cpp" />
|
||||
<ClCompile Include="../Utils.cpp" />
|
||||
<ClCompile Include="../ScopedResourceLoader.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../AppKeyBindings.cpp" >
|
||||
<DependentUpon>../AppKeyBindings.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../ActionArgs.cpp" >
|
||||
<DependentUpon>../ActionArgs.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../App.cpp" >
|
||||
<DependentUpon>../App.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../AppActionHandlers.cpp" >
|
||||
<DependentUpon>../App.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
|
||||
<!-- You _NEED_ to include this file and the jsoncpp IncludePath (below) if
|
||||
you want to use jsoncpp -->
|
||||
@@ -140,7 +108,6 @@
|
||||
<DependentUpon>../App.xaml</DependentUpon>
|
||||
</Midl>
|
||||
<Midl Include="../AppKeyBindings.idl" />
|
||||
<Midl Include="../ActionArgs.idl" />
|
||||
<Midl Include="../MinMaxCloseControl.idl">
|
||||
<DependentUpon>../MinMaxCloseControl.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
@@ -149,19 +116,10 @@
|
||||
<DependentUpon>../TerminalPage.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="../TitlebarControl.idl">
|
||||
<DependentUpon>../TitlebarControl.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="../TabRowControl.idl">
|
||||
<DependentUpon>../TabRowControl.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- ========================= Misc Files ======================== -->
|
||||
<ItemGroup>
|
||||
<PRIResource Include="../Resources/en-US/Resources.resw" />
|
||||
<None Include="../packages.config" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -266,7 +224,7 @@
|
||||
<Native-Platform Condition="'$(Platform)' == 'Win32'">x86</Native-Platform>
|
||||
<Native-Platform Condition="'$(Platform)' != 'Win32'">$(Platform)</Native-Platform>
|
||||
<_MUXRoot>$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.2.190611001-prerelease\</_MUXRoot>
|
||||
<_MUXAppRoot>$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0-preview7\</_MUXAppRoot>
|
||||
<_MUXAppRoot>$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0-preview6.2\</_MUXAppRoot>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- Microsoft.UI.XAML -->
|
||||
@@ -274,7 +232,7 @@
|
||||
<Implementation>Microsoft.UI.Xaml.dll</Implementation>
|
||||
<IsWinMDFile>true</IsWinMDFile>
|
||||
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
|
||||
<Private>true</Private>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<ReferenceCopyLocalPaths Include="$(_MUXRoot)runtimes\win10-$(Native-Platform)\native\Microsoft.UI.Xaml.dll" />
|
||||
<ReferenceCopyLocalPaths Include="$(_MUXRoot)runtimes\win10-$(Native-Platform)\native\Microsoft.UI.Xaml.pri" />
|
||||
@@ -296,7 +254,7 @@
|
||||
<ErrorText>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}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.2.190611001-prerelease\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.2.190611001-prerelease\build\native\Microsoft.UI.Xaml.targets'))" />
|
||||
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0-preview7\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0-preview7\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
|
||||
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0-preview6.2\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0-preview6.2\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
|
||||
</Target>
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
#include <hstring.h>
|
||||
|
||||
#include <winrt/Windows.ApplicationModel.Resources.Core.h>
|
||||
#include <winrt/Windows.ApplicationModel.Resources.h>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
#include <winrt/windows.ui.core.h>
|
||||
@@ -45,7 +45,7 @@
|
||||
// Including TraceLogging essentials for the binary
|
||||
#include <TraceLoggingProvider.h>
|
||||
#include <winmeta.h>
|
||||
TRACELOGGING_DECLARE_PROVIDER(g_hTerminalAppProvider);
|
||||
TRACELOGGING_DECLARE_PROVIDER(g_hTerminalWin32Provider);
|
||||
#include <telemetry\ProjectTelemetry.h>
|
||||
#include <TraceLoggingActivity.h>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.0.0-preview7" targetFramework="native" />
|
||||
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.0.0-preview6.2" targetFramework="native" />
|
||||
<package id="Microsoft.UI.Xaml" version="2.2.190611001-prerelease" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.190730.2" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.190605.7" targetFramework="native" />
|
||||
</packages>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cpprest/details/web_utilities.h>
|
||||
|
||||
const utility::string_t AzureClientID = U("0");
|
||||
@@ -1,50 +0,0 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// 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>
|
||||
{
|
||||
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<AzureConnection, implementation::AzureConnection>
|
||||
{
|
||||
};
|
||||
}
|
||||
@@ -1,943 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "AzureConnection.h"
|
||||
#include "AzureClientID.h"
|
||||
#include "AzureConnectionStrings.h"
|
||||
#include <sstream>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "AzureConnection.g.cpp"
|
||||
|
||||
#include "../../types/inc/Utils.hpp"
|
||||
|
||||
using namespace ::Microsoft::Console;
|
||||
|
||||
using namespace utility;
|
||||
using namespace web;
|
||||
using namespace web::json;
|
||||
using namespace web::http;
|
||||
using namespace web::http::client;
|
||||
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 }
|
||||
{
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - ascribes to the ITerminalConnection interface
|
||||
// - registers an output event handler
|
||||
// Arguments:
|
||||
// - the handler
|
||||
// Return value:
|
||||
// - the event token for the handler
|
||||
winrt::event_token AzureConnection::TerminalOutput(Microsoft::Terminal::TerminalConnection::TerminalOutputEventArgs const& handler)
|
||||
{
|
||||
return _outputHandlers.add(handler);
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - ascribes to the ITerminalConnection interface
|
||||
// - revokes an output event handler
|
||||
// Arguments:
|
||||
// - the event token for the handler
|
||||
void AzureConnection::TerminalOutput(winrt::event_token const& token) noexcept
|
||||
{
|
||||
_outputHandlers.remove(token);
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - ascribes to the ITerminalConnection interface
|
||||
// - registers a terminal-disconnected event handler
|
||||
// Arguments:
|
||||
// - the handler
|
||||
// Return value:
|
||||
// - the event token for the handler
|
||||
winrt::event_token AzureConnection::TerminalDisconnected(Microsoft::Terminal::TerminalConnection::TerminalDisconnectedEventArgs const& handler)
|
||||
{
|
||||
return _disconnectHandlers.add(handler);
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - ascribes to the ITerminalConnection interface
|
||||
// - revokes a terminal-disconnected event handler
|
||||
// Arguments:
|
||||
// - the event token for the handler
|
||||
void AzureConnection::TerminalDisconnected(winrt::event_token const& token) noexcept
|
||||
{
|
||||
_disconnectHandlers.remove(token);
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - ascribes to the ITerminalConnection interface
|
||||
// - creates the output thread (where we will do the authentication and actually connect to Azure)
|
||||
void AzureConnection::Start()
|
||||
{
|
||||
// Create our own output handling thread
|
||||
// Each connection needs to make sure to drain the output from its backing host.
|
||||
_hOutputThread.reset(CreateThread(nullptr,
|
||||
0,
|
||||
StaticOutputThreadProc,
|
||||
this,
|
||||
0,
|
||||
nullptr));
|
||||
|
||||
THROW_LAST_ERROR_IF_NULL(_hOutputThread);
|
||||
|
||||
_connected = true;
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - ascribes to the ITerminalConnection interface
|
||||
// - handles the different possible inputs in the different states
|
||||
// Arguments:
|
||||
// the user's input
|
||||
void AzureConnection::WriteInput(hstring const& data)
|
||||
{
|
||||
if (!_connected || _closing.load())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the input differently depending on which state we're in
|
||||
switch (_state)
|
||||
{
|
||||
// The user has stored connection settings, let them choose one of them, create a new one or remove all stored ones
|
||||
case State::AccessStored:
|
||||
{
|
||||
const auto s = winrt::to_string(data);
|
||||
int storeNum = -1;
|
||||
try
|
||||
{
|
||||
storeNum = std::stoi(s);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
if (s != "n" && s != "r")
|
||||
{
|
||||
_outputHandlers(winrt::to_hstring(invalidAccessInput));
|
||||
return;
|
||||
}
|
||||
else if (s == "r")
|
||||
{
|
||||
std::lock_guard<std::mutex> lg{ _commonMutex };
|
||||
_removeOrNew = true;
|
||||
_canProceed.notify_one();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::lock_guard<std::mutex> lg{ _commonMutex };
|
||||
_removeOrNew = false;
|
||||
_canProceed.notify_one();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (storeNum >= _maxStored)
|
||||
{
|
||||
_outputHandlers(winrt::to_hstring(numOutOfBoundsError));
|
||||
return;
|
||||
}
|
||||
std::lock_guard<std::mutex> lg{ _commonMutex };
|
||||
_storedNumber = storeNum;
|
||||
_canProceed.notify_one();
|
||||
return;
|
||||
}
|
||||
// The user has multiple tenants in their Azure account, let them choose one of them
|
||||
case State::TenantChoice:
|
||||
{
|
||||
int tenantNum = -1;
|
||||
try
|
||||
{
|
||||
tenantNum = std::stoi(winrt::to_string(data));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
_outputHandlers(winrt::to_hstring(nonNumberError));
|
||||
return;
|
||||
}
|
||||
if (tenantNum >= _maxSize)
|
||||
{
|
||||
_outputHandlers(winrt::to_hstring(numOutOfBoundsError));
|
||||
return;
|
||||
}
|
||||
std::lock_guard<std::mutex> lg{ _commonMutex };
|
||||
_tenantNumber = tenantNum;
|
||||
_canProceed.notify_one();
|
||||
return;
|
||||
}
|
||||
// User has the option to save their connection settings for future logins
|
||||
case State::StoreTokens:
|
||||
{
|
||||
std::string s = winrt::to_string(data);
|
||||
if (s != "y" && s != "n")
|
||||
{
|
||||
_outputHandlers(winrt::to_hstring(invalidStoreInput));
|
||||
}
|
||||
else if (s == "y")
|
||||
{
|
||||
std::lock_guard<std::mutex> lg{ _commonMutex };
|
||||
_store = true;
|
||||
_canProceed.notify_one();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::lock_guard<std::mutex> lg{ _commonMutex };
|
||||
_store = false;
|
||||
_canProceed.notify_one();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// We are connected, send user's input over the websocket
|
||||
case State::TermConnected:
|
||||
{
|
||||
websocket_outgoing_message msg;
|
||||
const auto str = winrt::to_string(data);
|
||||
msg.set_utf8_message(str);
|
||||
|
||||
_cloudShellSocket.send(msg);
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - ascribes to the ITerminalConnection interface
|
||||
// - resizes the terminal
|
||||
// Arguments:
|
||||
// - the new rows/cols values
|
||||
void AzureConnection::Resize(uint32_t rows, uint32_t columns)
|
||||
{
|
||||
if (!_connected || !(_state == State::TermConnected))
|
||||
{
|
||||
_initialRows = rows;
|
||||
_initialCols = columns;
|
||||
}
|
||||
else if (!_closing.load())
|
||||
{
|
||||
// Initialize client
|
||||
http_client terminalClient(_cloudShellUri);
|
||||
|
||||
// Initialize the request
|
||||
http_request terminalRequest(L"POST");
|
||||
terminalRequest.set_request_uri(L"terminals/" + _terminalID + L"/size?cols=" + std::to_wstring(columns) + L"&rows=" + std::to_wstring(rows) + L"&version=2019-01-01");
|
||||
_HeaderHelper(terminalRequest);
|
||||
terminalRequest.set_body(json::value(L""));
|
||||
|
||||
// Send the request
|
||||
_RequestHelper(terminalClient, terminalRequest);
|
||||
}
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - ascribes to the ITerminalConnection interface
|
||||
// - closes the websocket connection and the output thread
|
||||
void AzureConnection::Close()
|
||||
{
|
||||
if (!_connected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_closing.exchange(true))
|
||||
{
|
||||
_canProceed.notify_all();
|
||||
if (_state == State::TermConnected)
|
||||
{
|
||||
// Close the websocket connection
|
||||
auto closedTask = _cloudShellSocket.close();
|
||||
closedTask.wait();
|
||||
}
|
||||
|
||||
// Tear down our output thread
|
||||
WaitForSingleObject(_hOutputThread.get(), INFINITE);
|
||||
_hOutputThread.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// 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<utility::string_t, utility::string_t> _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:
|
||||
// - lpParameter: the Azure connection parameter
|
||||
// Return value:
|
||||
// - the exit code of the thread
|
||||
DWORD WINAPI AzureConnection::StaticOutputThreadProc(LPVOID lpParameter)
|
||||
{
|
||||
AzureConnection* const pInstance = (AzureConnection*)lpParameter;
|
||||
return pInstance->_OutputThread();
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - this is the output thread, where we initiate the connection to Azure
|
||||
// and establish a websocket connection
|
||||
// Return value:
|
||||
// - return status
|
||||
DWORD AzureConnection::_OutputThread()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (_state)
|
||||
{
|
||||
// Initial state, check if the user has any stored connection settings and allow them to login with those
|
||||
// or allow them to login with a different account or allow them to remove the saved settings
|
||||
case State::AccessStored:
|
||||
{
|
||||
RETURN_IF_FAILED(_AccessHelper());
|
||||
break;
|
||||
}
|
||||
// User has no saved connection settings or has opted to login with a different account
|
||||
// Azure authentication happens here
|
||||
case State::DeviceFlow:
|
||||
{
|
||||
RETURN_IF_FAILED(_DeviceFlowHelper());
|
||||
break;
|
||||
}
|
||||
// User has multiple tenants in their Azure account, they need to choose which one to connect to
|
||||
case State::TenantChoice:
|
||||
{
|
||||
RETURN_IF_FAILED(_TenantChoiceHelper());
|
||||
break;
|
||||
}
|
||||
// Ask the user if they want to save these connection settings for future logins
|
||||
case State::StoreTokens:
|
||||
{
|
||||
RETURN_IF_FAILED(_StoreHelper());
|
||||
break;
|
||||
}
|
||||
// Connect to Azure, we only get here once we have everything we need (tenantID, accessToken, refreshToken)
|
||||
case State::TermConnecting:
|
||||
{
|
||||
RETURN_IF_FAILED(_ConnectHelper());
|
||||
break;
|
||||
}
|
||||
// We are connected, continuously read from the websocket until its closed
|
||||
case State::TermConnected:
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// Read from websocket
|
||||
pplx::task<websocket_incoming_message> msgT;
|
||||
try
|
||||
{
|
||||
msgT = _cloudShellSocket.receive();
|
||||
msgT.wait();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Websocket has been closed
|
||||
if (!_closing.load())
|
||||
{
|
||||
_state = State::NoConnect;
|
||||
_disconnectHandlers();
|
||||
return S_FALSE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
auto msg = msgT.get();
|
||||
auto msgStringTask = msg.extract_string();
|
||||
auto msgString = msgStringTask.get();
|
||||
|
||||
// Convert to hstring
|
||||
const auto hstr = winrt::to_hstring(msgString);
|
||||
|
||||
// Pass the output to our registered event handlers
|
||||
_outputHandlers(hstr);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
case State::NoConnect:
|
||||
{
|
||||
_outputHandlers(winrt::to_hstring(internetOrServerIssue));
|
||||
_disconnectHandlers();
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
_state = State::NoConnect;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to get the stored credentials (if any) and let the user choose what to do next
|
||||
// Return value:
|
||||
// - S_FALSE if there are no stored credentials
|
||||
// - S_OK if the user opts to login with a stored set of credentials or login with a different account
|
||||
// - E_FAIL if the user closes the tab
|
||||
HRESULT AzureConnection::_AccessHelper()
|
||||
{
|
||||
bool oldVersionEncountered = false;
|
||||
auto vault = PasswordVault();
|
||||
winrt::Windows::Foundation::Collections::IVectorView<PasswordCredential> credList;
|
||||
// FindAllByResource throws an exception if there are no credentials stored under the given resource so we wrap it in a try-catch block
|
||||
try
|
||||
{
|
||||
credList = vault.FindAllByResource(resource);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// No credentials are stored, so start the device flow
|
||||
_state = State::DeviceFlow;
|
||||
return S_FALSE;
|
||||
}
|
||||
_maxStored = 0;
|
||||
for (const auto& entry : credList)
|
||||
{
|
||||
auto nameJson = json::value::parse(entry.UserName().c_str());
|
||||
std::optional<int> 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));
|
||||
_outputHandlers(winrt::to_hstring(newLogin));
|
||||
_outputHandlers(winrt::to_hstring(removeStored));
|
||||
|
||||
std::unique_lock<std::mutex> storedLock{ _commonMutex };
|
||||
_canProceed.wait(storedLock, [=]() {
|
||||
return (_storedNumber >= 0 && _storedNumber < _maxStored) || _removeOrNew.has_value() || _closing.load();
|
||||
});
|
||||
// User might have closed the tab while we waited for input
|
||||
if (_closing.load())
|
||||
{
|
||||
return E_FAIL;
|
||||
}
|
||||
else if (_removeOrNew.has_value() && _removeOrNew.value())
|
||||
{
|
||||
// User wants to remove the stored settings
|
||||
_RemoveCredentials();
|
||||
_state = State::DeviceFlow;
|
||||
return S_OK;
|
||||
}
|
||||
else if (_removeOrNew.has_value() && !_removeOrNew.value())
|
||||
{
|
||||
// User wants to login with a different account
|
||||
_state = State::DeviceFlow;
|
||||
return S_OK;
|
||||
}
|
||||
// User wants to login with one of the saved connection settings
|
||||
auto desiredCredential = credList.GetAt(_storedNumber);
|
||||
desiredCredential.RetrievePassword();
|
||||
auto nameJson = json::value::parse(desiredCredential.UserName().c_str());
|
||||
auto passWordJson = json::value::parse(desiredCredential.Password().c_str());
|
||||
_displayName = nameJson.at(L"displayName").as_string();
|
||||
_tenantID = nameJson.at(L"tenantID").as_string();
|
||||
_accessToken = passWordJson.at(L"accessToken").as_string();
|
||||
_refreshToken = passWordJson.at(L"refreshToken").as_string();
|
||||
_expiry = std::stoi(passWordJson.at(L"expiry").as_string());
|
||||
|
||||
const auto t1 = std::chrono::system_clock::now();
|
||||
const auto timeNow = std::chrono::duration_cast<std::chrono::seconds>(t1.time_since_epoch()).count();
|
||||
|
||||
// Check if the token is close to expiring and refresh if so
|
||||
if (timeNow + _expireLimit > _expiry)
|
||||
{
|
||||
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());
|
||||
// Store the updated tokens under the same username
|
||||
_StoreCredential();
|
||||
}
|
||||
|
||||
// We have everything we need, so go ahead and connect
|
||||
_state = State::TermConnecting;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to start the device code flow (required for authentication to Azure)
|
||||
// Return value:
|
||||
// - E_FAIL if the user closes the tab, does not authenticate in time or has no tenants in their Azure account
|
||||
// - S_OK otherwise
|
||||
HRESULT AzureConnection::_DeviceFlowHelper()
|
||||
{
|
||||
// Initiate device code flow
|
||||
const auto deviceCodeResponse = _GetDeviceCode();
|
||||
|
||||
// Print the message and store the device code, polling interval and expiry
|
||||
const auto message = deviceCodeResponse.at(L"message").as_string();
|
||||
_outputHandlers(message + codeExpiry);
|
||||
const auto devCode = deviceCodeResponse.at(L"device_code").as_string();
|
||||
const auto pollInterval = std::stoi(deviceCodeResponse.at(L"interval").as_string());
|
||||
const auto expiresIn = std::stoi(deviceCodeResponse.at(L"expires_in").as_string());
|
||||
|
||||
// Wait for user authentication and obtain the access/refresh tokens
|
||||
json::value authenticatedResponse;
|
||||
try
|
||||
{
|
||||
authenticatedResponse = _WaitForUser(devCode, pollInterval, expiresIn);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
_outputHandlers(winrt::to_hstring(exitStr));
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
_accessToken = authenticatedResponse.at(L"access_token").as_string();
|
||||
_refreshToken = authenticatedResponse.at(L"refresh_token").as_string();
|
||||
|
||||
// Get the tenants and the required tenant id
|
||||
const auto tenantsResponse = _GetTenants();
|
||||
_tenantList = tenantsResponse.at(L"value");
|
||||
const auto tenantListAsArray = _tenantList.as_array();
|
||||
if (tenantListAsArray.size() == 0)
|
||||
{
|
||||
_outputHandlers(winrt::to_hstring(noTenants));
|
||||
return E_FAIL;
|
||||
}
|
||||
else if (_tenantList.size() == 1)
|
||||
{
|
||||
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();
|
||||
_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;
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = State::TenantChoice;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to list the user's tenants and let them decide which tenant they wish to connect to
|
||||
// Return value:
|
||||
// - E_FAIL if the user closes the tab
|
||||
// - S_OK otherwise
|
||||
HRESULT AzureConnection::_TenantChoiceHelper()
|
||||
{
|
||||
const auto tenantListAsArray = _tenantList.as_array();
|
||||
_maxSize = 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<std::mutex> 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;
|
||||
}
|
||||
|
||||
const auto& chosenTenant = tenantListAsArray.at(_tenantNumber);
|
||||
std::tie(_tenantID, _displayName) = _crackTenant(chosenTenant);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to ask the user if they wish to store their credentials
|
||||
// Return value:
|
||||
// - E_FAIL if the user closes the tab
|
||||
// - S_OK otherwise
|
||||
HRESULT AzureConnection::_StoreHelper()
|
||||
{
|
||||
_outputHandlers(winrt::to_hstring(storePrompt));
|
||||
// Wait for user input
|
||||
std::unique_lock<std::mutex> storeLock{ _commonMutex };
|
||||
_canProceed.wait(storeLock, [=]() {
|
||||
return _store.has_value() || _closing.load();
|
||||
});
|
||||
// User might have closed the tab while we waited for input
|
||||
if (_closing.load())
|
||||
{
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
if (_store.value())
|
||||
{
|
||||
// User has opted to store the connection settings
|
||||
_StoreCredential();
|
||||
_outputHandlers(winrt::to_hstring(tokensStored));
|
||||
}
|
||||
|
||||
_state = State::TermConnecting;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to connect the user to the Azure cloud shell
|
||||
// Return value:
|
||||
// - E_FAIL if the user has not set up their cloud shell yet
|
||||
// - S_OK after successful connection
|
||||
HRESULT AzureConnection::_ConnectHelper()
|
||||
{
|
||||
// Get user's cloud shell settings
|
||||
const auto settingsResponse = _GetCloudShellUserSettings();
|
||||
if (settingsResponse.has_field(L"error"))
|
||||
{
|
||||
_outputHandlers(winrt::to_hstring(noCloudAccount));
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// Request for a cloud shell
|
||||
_outputHandlers(winrt::to_hstring(requestingCloud));
|
||||
_cloudShellUri = _GetCloudShell();
|
||||
_outputHandlers(winrt::to_hstring(success));
|
||||
|
||||
// Request for a terminal for said cloud shell
|
||||
// We only support bash for now, so don't bother with the user's preferred shell
|
||||
// fyi: we can't call powershell yet because it sends VT sequences we don't support yet
|
||||
// TODO: GitHub #1883
|
||||
//const auto shellType = settingsResponse.at(L"properties").at(L"preferredShellType").as_string();
|
||||
const auto shellType = L"bash";
|
||||
_outputHandlers(winrt::to_hstring(requestingTerminal));
|
||||
const auto socketUri = _GetTerminal(shellType);
|
||||
_outputHandlers(winrt::to_hstring("\r\n"));
|
||||
|
||||
// Step 8: connecting to said terminal
|
||||
const auto connReqTask = _cloudShellSocket.connect(socketUri);
|
||||
connReqTask.wait();
|
||||
|
||||
_state = State::TermConnected;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to send requests and extract responses as json values
|
||||
// Arguments:
|
||||
// - a http_client
|
||||
// - a http_request for the client to send
|
||||
// Return value:
|
||||
// - the response from the server as a json value
|
||||
json::value AzureConnection::_RequestHelper(http_client theClient, http_request theRequest)
|
||||
{
|
||||
json::value jsonResult;
|
||||
try
|
||||
{
|
||||
const auto responseTask = theClient.request(theRequest);
|
||||
responseTask.wait();
|
||||
const auto response = responseTask.get();
|
||||
const auto responseJsonTask = response.extract_json();
|
||||
responseJsonTask.wait();
|
||||
jsonResult = responseJsonTask.get();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
_outputHandlers(winrt::to_hstring(internetOrServerIssue));
|
||||
}
|
||||
return jsonResult;
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to start the device code flow
|
||||
// Return value:
|
||||
// - the response to the device code flow initiation
|
||||
json::value AzureConnection::_GetDeviceCode()
|
||||
{
|
||||
// Initialize the client
|
||||
http_client loginClient(_loginUri);
|
||||
|
||||
// Initialize the request
|
||||
http_request commonRequest(L"POST");
|
||||
commonRequest.set_request_uri(L"common/oauth2/devicecode");
|
||||
const auto body = L"client_id=" + AzureClientID + L"&resource=" + _wantedResource;
|
||||
commonRequest.set_body(body, L"application/x-www-form-urlencoded");
|
||||
|
||||
// Send the request and receive the response as a json value
|
||||
return _RequestHelper(loginClient, commonRequest);
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to wait for the user to authenticate using their web browser
|
||||
// Arguments:
|
||||
// - the device code that would have been received when authentication was initiated
|
||||
// - the polling interval duration
|
||||
// - the duration the code is still valid for
|
||||
// Return value:
|
||||
// - if authentication is done successfully, then return the response from the server
|
||||
// - else, throw an exception
|
||||
json::value AzureConnection::_WaitForUser(const utility::string_t deviceCode, int pollInterval, int expiresIn)
|
||||
{
|
||||
// Initialize the client
|
||||
http_client pollingClient(_loginUri);
|
||||
|
||||
// Continuously send a poll request until the user authenticates
|
||||
const auto body = L"grant_type=device_code&resource=" + _wantedResource + L"&client_id=" + AzureClientID + L"&code=" + deviceCode;
|
||||
const auto requestUri = L"common/oauth2/token";
|
||||
json::value responseJson;
|
||||
for (int count = 0; count < expiresIn / pollInterval; count++)
|
||||
{
|
||||
// User might close the tab while we wait for them to authenticate, this case handles that
|
||||
if (_closing.load())
|
||||
{
|
||||
throw "Tab closed.";
|
||||
}
|
||||
http_request pollRequest(L"POST");
|
||||
pollRequest.set_request_uri(requestUri);
|
||||
pollRequest.set_body(body, L"application/x-www-form-urlencoded");
|
||||
|
||||
responseJson = _RequestHelper(pollingClient, pollRequest);
|
||||
|
||||
if (responseJson.has_field(L"error"))
|
||||
{
|
||||
Sleep(pollInterval * 1000); // Sleep takes arguments in milliseconds
|
||||
continue; // Still waiting for authentication
|
||||
}
|
||||
else
|
||||
{
|
||||
_outputHandlers(winrt::to_hstring("Authenticated.\r\n"));
|
||||
break; // Authentication is done, break from loop
|
||||
}
|
||||
}
|
||||
if (responseJson.has_field(L"error"))
|
||||
{
|
||||
throw "Time out.";
|
||||
}
|
||||
return responseJson;
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to acquire the user's Azure tenants
|
||||
// Return value:
|
||||
// - the response which contains a list of the user's Azure tenants
|
||||
json::value AzureConnection::_GetTenants()
|
||||
{
|
||||
// Initialize the client
|
||||
http_client tenantClient(_resourceUri);
|
||||
|
||||
// Initialize the request
|
||||
http_request tenantRequest(L"GET");
|
||||
tenantRequest.set_request_uri(L"tenants?api-version=2018-01-01");
|
||||
_HeaderHelper(tenantRequest);
|
||||
|
||||
// Send the request and return the response as a json value
|
||||
return _RequestHelper(tenantClient, tenantRequest);
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to refresh the access/refresh tokens
|
||||
// Return value:
|
||||
// - the response with the new tokens
|
||||
json::value AzureConnection::_RefreshTokens()
|
||||
{
|
||||
// Initialize the client
|
||||
http_client refreshClient(_loginUri);
|
||||
|
||||
// Initialize the request
|
||||
http_request refreshRequest(L"POST");
|
||||
refreshRequest.set_request_uri(_tenantID + L"/oauth2/token");
|
||||
const auto body = L"client_id=" + AzureClientID + L"&resource=" + _wantedResource + L"&grant_type=refresh_token" + L"&refresh_token=" + _refreshToken;
|
||||
refreshRequest.set_body(body, L"application/x-www-form-urlencoded");
|
||||
refreshRequest.headers().add(L"User-Agent", userAgent);
|
||||
|
||||
// Send the request and return the response as a json value
|
||||
return _RequestHelper(refreshClient, refreshRequest);
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to get the user's cloud shell settings
|
||||
// Return value:
|
||||
// - the user's cloud shell settings
|
||||
json::value AzureConnection::_GetCloudShellUserSettings()
|
||||
{
|
||||
// Initialize client
|
||||
http_client settingsClient(_resourceUri);
|
||||
|
||||
// Initialize request
|
||||
http_request settingsRequest(L"GET");
|
||||
settingsRequest.set_request_uri(L"providers/Microsoft.Portal/userSettings/cloudconsole?api-version=2018-10-01");
|
||||
_HeaderHelper(settingsRequest);
|
||||
|
||||
return _RequestHelper(settingsClient, settingsRequest);
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to request for a cloud shell
|
||||
// Return value:
|
||||
// - the uri for the cloud shell
|
||||
utility::string_t AzureConnection::_GetCloudShell()
|
||||
{
|
||||
// Initialize client
|
||||
http_client cloudShellClient(_resourceUri);
|
||||
|
||||
// Initialize request
|
||||
http_request shellRequest(L"PUT");
|
||||
shellRequest.set_request_uri(L"providers/Microsoft.Portal/consoles/default?api-version=2018-10-01");
|
||||
_HeaderHelper(shellRequest);
|
||||
const auto innerBody = json::value::parse(U("{ \"osType\" : \"linux\" }"));
|
||||
json::value body;
|
||||
body[U("properties")] = innerBody;
|
||||
shellRequest.set_body(body);
|
||||
|
||||
// Send the request and get the response as a json value
|
||||
const auto cloudShell = _RequestHelper(cloudShellClient, shellRequest);
|
||||
|
||||
// Return the uri
|
||||
return cloudShell.at(L"properties").at(L"uri").as_string() + L"/";
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to request for a terminal
|
||||
// Return value:
|
||||
// - the uri for the terminal
|
||||
utility::string_t AzureConnection::_GetTerminal(utility::string_t shellType)
|
||||
{
|
||||
// Initialize client
|
||||
http_client terminalClient(_cloudShellUri);
|
||||
|
||||
// Initialize the request
|
||||
http_request terminalRequest(L"POST");
|
||||
terminalRequest.set_request_uri(L"terminals?cols=" + std::to_wstring(_initialCols) + L"&rows=" + std::to_wstring(_initialRows) + L"&version=2019-01-01&shell=" + shellType);
|
||||
_HeaderHelper(terminalRequest);
|
||||
|
||||
// Send the request and get the response as a json value
|
||||
const auto terminalResponse = _RequestHelper(terminalClient, terminalRequest);
|
||||
_terminalID = terminalResponse.at(L"id").as_string();
|
||||
|
||||
// Return the uri
|
||||
return terminalResponse.at(L"socketUri").as_string();
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to set the headers of a http_request
|
||||
// Arguments:
|
||||
// - the http_request
|
||||
void AzureConnection::_HeaderHelper(http_request theRequest)
|
||||
{
|
||||
theRequest.headers().add(L"Accept", L"application/json");
|
||||
theRequest.headers().add(L"Content-Type", L"application/json");
|
||||
theRequest.headers().add(L"Authorization", L"Bearer " + _accessToken);
|
||||
theRequest.headers().add(L"User-Agent", userAgent);
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to store the credentials
|
||||
// - we store the display name, tenant ID, access/refresh tokens, and token expiry
|
||||
void AzureConnection::_StoreCredential()
|
||||
{
|
||||
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;
|
||||
passWord[U("accessToken")] = json::value::string(_accessToken);
|
||||
passWord[U("refreshToken")] = json::value::string(_refreshToken);
|
||||
passWord[U("expiry")] = json::value::string(std::to_wstring(_expiry));
|
||||
auto newCredential = PasswordCredential(resource, userName.serialize(), passWord.serialize());
|
||||
vault.Add(newCredential);
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - helper function to remove all stored credentials
|
||||
void AzureConnection::_RemoveCredentials()
|
||||
{
|
||||
auto vault = PasswordVault();
|
||||
winrt::Windows::Foundation::Collections::IVectorView<PasswordCredential> credList;
|
||||
// FindAllByResource throws an exception if there are no credentials stored under the given resource so we wrap it in a try-catch block
|
||||
try
|
||||
{
|
||||
credList = vault.FindAllByResource(resource);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// No credentials are stored, so just return
|
||||
_outputHandlers(winrt::to_hstring(noTokens));
|
||||
return;
|
||||
}
|
||||
while (credList.Size() > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
vault.Remove(credList.GetAt(0));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
_outputHandlers(winrt::to_hstring(tokensRemoved));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring AzureConnection::_StrFormatHelper(const wchar_t* const format, int i, const wchar_t* name, const wchar_t* ID)
|
||||
{
|
||||
const auto lengthRequired = _scwprintf(ithTenant, i, name, ID);
|
||||
std::wstring buffer;
|
||||
buffer.resize(lengthRequired + 1);
|
||||
swprintf_s(buffer.data(), buffer.size(), ithTenant, i, name, ID);
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AzureConnection.g.h"
|
||||
|
||||
#include <cpprest/http_client.h>
|
||||
#include <cpprest/http_listener.h>
|
||||
#include <cpprest/ws_client.h>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
struct AzureConnection : AzureConnectionT<AzureConnection>
|
||||
{
|
||||
static bool IsAzureConnectionAvailable();
|
||||
AzureConnection(const uint32_t rows, const uint32_t cols);
|
||||
|
||||
winrt::event_token TerminalOutput(TerminalConnection::TerminalOutputEventArgs const& handler);
|
||||
void TerminalOutput(winrt::event_token const& token) noexcept;
|
||||
winrt::event_token TerminalDisconnected(TerminalConnection::TerminalDisconnectedEventArgs const& handler);
|
||||
void TerminalDisconnected(winrt::event_token const& token) noexcept;
|
||||
void Start();
|
||||
void WriteInput(hstring const& data);
|
||||
void Resize(uint32_t rows, uint32_t columns);
|
||||
void Close();
|
||||
|
||||
private:
|
||||
winrt::event<TerminalConnection::TerminalOutputEventArgs> _outputHandlers;
|
||||
winrt::event<TerminalConnection::TerminalDisconnectedEventArgs> _disconnectHandlers;
|
||||
|
||||
uint32_t _initialRows{};
|
||||
uint32_t _initialCols{};
|
||||
int _storedNumber{ -1 };
|
||||
int _maxStored;
|
||||
int _tenantNumber{ -1 };
|
||||
int _maxSize;
|
||||
std::condition_variable _canProceed;
|
||||
std::mutex _commonMutex;
|
||||
|
||||
enum class State
|
||||
{
|
||||
AccessStored,
|
||||
DeviceFlow,
|
||||
TenantChoice,
|
||||
StoreTokens,
|
||||
TermConnecting,
|
||||
TermConnected,
|
||||
NoConnect
|
||||
};
|
||||
|
||||
State _state{ State::AccessStored };
|
||||
|
||||
std::optional<bool> _store;
|
||||
std::optional<bool> _removeOrNew;
|
||||
|
||||
bool _connected{};
|
||||
std::atomic<bool> _closing{ false };
|
||||
|
||||
wil::unique_handle _hOutputThread;
|
||||
|
||||
static DWORD WINAPI StaticOutputThreadProc(LPVOID lpParameter);
|
||||
DWORD _OutputThread();
|
||||
HRESULT _AccessHelper();
|
||||
HRESULT _DeviceFlowHelper();
|
||||
HRESULT _TenantChoiceHelper();
|
||||
HRESULT _StoreHelper();
|
||||
HRESULT _ConnectHelper();
|
||||
|
||||
const utility::string_t _loginUri{ U("https://login.microsoftonline.com/") };
|
||||
const utility::string_t _resourceUri{ U("https://management.azure.com/") };
|
||||
const utility::string_t _wantedResource{ U("https://management.core.windows.net/") };
|
||||
int _expireLimit{ 2700 };
|
||||
web::json::value _tenantList;
|
||||
utility::string_t _displayName;
|
||||
utility::string_t _tenantID;
|
||||
utility::string_t _accessToken;
|
||||
utility::string_t _refreshToken;
|
||||
int _expiry;
|
||||
utility::string_t _cloudShellUri;
|
||||
utility::string_t _terminalID;
|
||||
|
||||
web::json::value _RequestHelper(web::http::client::http_client theClient, web::http::http_request theRequest);
|
||||
web::json::value _GetDeviceCode();
|
||||
web::json::value _WaitForUser(utility::string_t deviceCode, int pollInterval, int expiresIn);
|
||||
web::json::value _GetTenants();
|
||||
web::json::value _RefreshTokens();
|
||||
web::json::value _GetCloudShellUserSettings();
|
||||
utility::string_t _GetCloudShell();
|
||||
utility::string_t _GetTerminal(utility::string_t shellType);
|
||||
void _HeaderHelper(web::http::http_request theRequest);
|
||||
void _StoreCredential();
|
||||
void _RemoveCredentials();
|
||||
std::wstring _StrFormatHelper(const wchar_t* const format, int i, const wchar_t* name, const wchar_t* ID);
|
||||
|
||||
web::websockets::client::websocket_client _cloudShellSocket;
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::factory_implementation
|
||||
{
|
||||
struct AzureConnection : AzureConnectionT<AzureConnection, implementation::AzureConnection>
|
||||
{
|
||||
};
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "ITerminalConnection.idl";
|
||||
|
||||
namespace Microsoft.Terminal.TerminalConnection
|
||||
{
|
||||
[default_interface] runtimeclass AzureConnection : ITerminalConnection
|
||||
{
|
||||
static Boolean IsAzureConnectionAvailable();
|
||||
|
||||
AzureConnection(UInt32 rows, UInt32 columns);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
// Messages to the user
|
||||
const auto resource = L"Terminal";
|
||||
const auto userAgent = L"Terminal/0.0";
|
||||
const auto codeExpiry = L"\r\nThis code will expire in 15 minutes.\r\n";
|
||||
const auto enterTenant = L"Please enter the desired tenant number\r\n";
|
||||
const auto newLogin = L"or enter n to login with a different account\r\n";
|
||||
const auto removeStored = L"or enter r to remove the above saved connection settings.\r\n";
|
||||
const auto invalidAccessInput = L"Please enter a valid number to access the stored connection settings, n to make a new one, or r to remove the stored ones.\r\n";
|
||||
const auto nonNumberError = L"Please enter a number.\r\n";
|
||||
const auto numOutOfBoundsError = L"Number out of bounds. Please enter a valid number. \r\n";
|
||||
const auto noTenants = L"Could not find any tenants.\r\n";
|
||||
const auto noCloudAccount = L"You have not set up your cloud shell account yet. Please go to https://shell.azure.com to set it up.\r\n";
|
||||
const auto storePrompt = L"Do you want to save these connection settings for future logins? [y/n]\r\n";
|
||||
const auto invalidStoreInput = L"Please enter y or n\r\n";
|
||||
const auto requestingCloud = L"Requesting for a cloud shell...";
|
||||
const auto success = L"Succeeded.\r\n";
|
||||
const auto requestingTerminal = L"Requesting for a terminal (this might take a while)...";
|
||||
const auto tokensStored = L"Your connection settings have been saved for future logins.\r\n";
|
||||
const auto noTokens = L"No tokens to remove. \r\n";
|
||||
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"<unknown tenant name>";
|
||||
const auto ithTenant = L"Tenant %d: %s (%s)\r\n";
|
||||
@@ -5,12 +5,16 @@
|
||||
#include "ConhostConnection.h"
|
||||
#include "windows.h"
|
||||
#include <sstream>
|
||||
// STARTF_USESTDHANDLES is only defined in WINAPI_PARTITION_DESKTOP
|
||||
// We're just gonna manually define it for this prototyping code
|
||||
#ifndef STARTF_USESTDHANDLES
|
||||
#define STARTF_USESTDHANDLES 0x00000100
|
||||
#endif
|
||||
|
||||
#include "ConhostConnection.g.cpp"
|
||||
|
||||
#include <conpty-universal.h>
|
||||
#include "../../types/inc/Utils.hpp"
|
||||
#include "../../types/inc/UTF8OutPipeReader.hpp"
|
||||
|
||||
using namespace ::Microsoft::Console;
|
||||
|
||||
@@ -18,7 +22,6 @@ 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) :
|
||||
@@ -26,7 +29,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
_initialCols{ initialCols },
|
||||
_commandline{ commandline },
|
||||
_startingDirectory{ startingDirectory },
|
||||
_startingTitle{ startingTitle },
|
||||
_guid{ initialGuid }
|
||||
{
|
||||
if (_guid == guid{})
|
||||
@@ -35,6 +37,23 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
}
|
||||
}
|
||||
|
||||
ConhostConnection::ConhostConnection(const uint64_t server,
|
||||
const uint32_t initialRows,
|
||||
const uint32_t initialCols,
|
||||
const guid& initialGuid) :
|
||||
_initialRows{ initialRows },
|
||||
_initialCols{ initialCols },
|
||||
_commandline{},
|
||||
_startingDirectory{},
|
||||
_guid{ initialGuid },
|
||||
_hServer{ reinterpret_cast<HANDLE>(server) }
|
||||
{
|
||||
if (_guid == guid{})
|
||||
{
|
||||
_guid = Utils::CreateGuid();
|
||||
}
|
||||
}
|
||||
|
||||
winrt::guid ConhostConnection::Guid() const noexcept
|
||||
{
|
||||
return _guid;
|
||||
@@ -69,6 +88,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
startingDirectory = _startingDirectory;
|
||||
}
|
||||
|
||||
std::optional<HANDLE> server;
|
||||
if (_hServer)
|
||||
{
|
||||
server = _hServer.get();
|
||||
}
|
||||
|
||||
EnvironmentVariableMapW extraEnvVars;
|
||||
{
|
||||
// Convert connection Guid to string and ignore the enclosing '{}'.
|
||||
@@ -81,35 +106,37 @@ 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<wchar_t[]> mutableTitle{ nullptr };
|
||||
if (!_startingTitle.empty())
|
||||
{
|
||||
mutableTitle = std::make_unique<wchar_t[]>(_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,
|
||||
static_cast<short>(_initialCols),
|
||||
static_cast<short>(_initialRows),
|
||||
server,
|
||||
&_inPipe,
|
||||
&_outPipe,
|
||||
&_signalPipe,
|
||||
&_piConhost,
|
||||
0,
|
||||
si,
|
||||
CREATE_SUSPENDED,
|
||||
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));
|
||||
|
||||
// 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,
|
||||
@@ -118,7 +145,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
0,
|
||||
nullptr));
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -185,37 +213,39 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
|
||||
DWORD ConhostConnection::_OutputThread()
|
||||
{
|
||||
UTF8OutPipeReader pipeReader{ _outPipe.get() };
|
||||
std::string_view strView{};
|
||||
|
||||
// process the data of the output pipe in a loop
|
||||
const size_t bufferSize = 4096;
|
||||
BYTE buffer[bufferSize];
|
||||
DWORD dwRead;
|
||||
while (true)
|
||||
{
|
||||
HRESULT result = pipeReader.Read(strView);
|
||||
if (FAILED(result) || result == S_FALSE)
|
||||
dwRead = 0;
|
||||
bool fSuccess = false;
|
||||
|
||||
fSuccess = !!ReadFile(_outPipe.get(), buffer, bufferSize, &dwRead, nullptr);
|
||||
if (!fSuccess)
|
||||
{
|
||||
if (_closing.load())
|
||||
{
|
||||
// This is okay, break out to kill the thread
|
||||
return 0;
|
||||
}
|
||||
|
||||
_disconnectHandlers();
|
||||
return (DWORD)-1;
|
||||
else
|
||||
{
|
||||
_disconnectHandlers();
|
||||
return (DWORD)-1;
|
||||
}
|
||||
}
|
||||
|
||||
if (strView.empty())
|
||||
if (dwRead == 0)
|
||||
{
|
||||
return 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert buffer to hstring
|
||||
auto hstr{ winrt::to_hstring(strView) };
|
||||
char* pchStr = (char*)(buffer);
|
||||
std::string str{ pchStr, dwRead };
|
||||
auto hstr = winrt::to_hstring(str);
|
||||
|
||||
// Pass the output to our registered event handlers
|
||||
_outputHandlers(hstr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
struct ConhostConnection : ConhostConnectionT<ConhostConnection>
|
||||
{
|
||||
ConhostConnection(const hstring& cmdline, const hstring& startingDirectory, const hstring& startingTitle, const uint32_t rows, const uint32_t cols, const guid& guid);
|
||||
ConhostConnection(const hstring& cmdline, const hstring& startingDirectory, const uint32_t rows, const uint32_t cols, const guid& guid);
|
||||
ConhostConnection(const uint64_t server, 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,7 +31,6 @@ 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{};
|
||||
@@ -42,6 +42,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
wil::unique_handle _hOutputThread;
|
||||
wil::unique_process_information _piConhost;
|
||||
wil::unique_handle _hJob;
|
||||
wil::unique_handle _hServer;
|
||||
|
||||
static DWORD WINAPI StaticOutputThreadProc(LPVOID lpParameter);
|
||||
DWORD _OutputThread();
|
||||
|
||||
@@ -5,9 +5,11 @@ import "ITerminalConnection.idl";
|
||||
|
||||
namespace Microsoft.Terminal.TerminalConnection
|
||||
{
|
||||
[default_interface] runtimeclass ConhostConnection : ITerminalConnection
|
||||
[default_interface]
|
||||
runtimeclass ConhostConnection : ITerminalConnection
|
||||
{
|
||||
ConhostConnection(String cmdline, String startingDirectory, String startingTitle, UInt32 rows, UInt32 columns, Guid guid);
|
||||
ConhostConnection(String cmdline, String startingDirectory, UInt32 rows, UInt32 columns, Guid guid);
|
||||
ConhostConnection(UInt64 server, UInt32 rows, UInt32 columns, Guid guid);
|
||||
|
||||
Guid Guid { get; };
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user