diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt index 3452bf4a8..fcaaf797a 100644 --- a/.github/actions/spelling/allow/allow.txt +++ b/.github/actions/spelling/allow/allow.txt @@ -12,6 +12,7 @@ downsides dze dzhe Enum'd +formattings ftp geeksforgeeks ghe @@ -34,6 +35,7 @@ overlined postmodern ptys qof +qps reimplementation reserialization reserialize @@ -46,6 +48,7 @@ tokenizes tonos tshe UIs +und versioned We'd wildcards diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 8252b10b7..56142fc56 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -45,6 +45,7 @@ IBind IBox IClass IComparable +IComparer IConnection ICustom IDialog @@ -85,6 +86,7 @@ NOREPEAT ntprivapi oaidl ocidl +ODR osver OSVERSIONINFOEXW otms diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 722631b26..fc07e9d2a 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -163,6 +163,7 @@ BPBF bpp BPPF branchconfig +brandings BRK Browsable bsearch diff --git a/.github/actions/spelling/patterns/patterns.txt b/.github/actions/spelling/patterns/patterns.txt index bee4446d4..882243396 100644 --- a/.github/actions/spelling/patterns/patterns.txt +++ b/.github/actions/spelling/patterns/patterns.txt @@ -23,3 +23,4 @@ VERIFY_ARE_EQUAL\(L"[^"]+" "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\+/" std::memory_order_[\w]+ D2DERR_SHADER_COMPILE_FAILED +TIL_FEATURE_[0-9A-Z_]+ diff --git a/.vscode/settings.json b/.vscode/settings.json index 2b746e3d1..142fa20f6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -100,6 +100,6 @@ "**/bin/**": true, "**/obj/**": true, "**/packages/**": true, - "**/generated files/**": true + "**/Generated Files/**": true } } \ No newline at end of file diff --git a/Scratch.sln b/Scratch.sln new file mode 100644 index 000000000..ac6caf341 --- /dev/null +++ b/Scratch.sln @@ -0,0 +1,221 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31205.134 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "Package", "scratch\ScratchIslandApp\Package\Package.wapproj", "{CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SampleAppLib", "scratch\ScratchIslandApp\SampleApp\SampleAppLib.vcxproj", "{A4394404-37F7-41C1-802B-49788D3720E3}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SampleApp", "scratch\ScratchIslandApp\SampleApp\dll\SampleApp.vcxproj", "{26C51792-41A3-4FE0-AB5E-8B69D557BF91}" + ProjectSection(ProjectDependencies) = postProject + {A4394404-37F7-41C1-802B-49788D3720E3} = {A4394404-37F7-41C1-802B-49788D3720E3} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowExe", "scratch\ScratchIslandApp\WindowExe\WindowExe.vcxproj", "{B4427499-9FDE-4208-B456-5BC580637633}" + ProjectSection(ProjectDependencies) = postProject + {26C51792-41A3-4FE0-AB5E-8B69D557BF91} = {26C51792-41A3-4FE0-AB5E-8B69D557BF91} + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common Props", "Common Props", "{53DD5520-E64C-4C06-B472-7CE62CA539C9}" + ProjectSection(SolutionItems) = preProject + src\common.build.post.props = src\common.build.post.props + src\common.build.pre.props = src\common.build.pre.props + src\common.build.tests.props = src\common.build.tests.props + common.openconsole.props = common.openconsole.props + src\cppwinrt.build.post.props = src\cppwinrt.build.post.props + src\cppwinrt.build.pre.props = src\cppwinrt.build.pre.props + src\wap-common.build.post.props = src\wap-common.build.post.props + src\wap-common.build.pre.props = src\wap-common.build.pre.props + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fmt", "src\dep\fmt\fmt.vcxproj", "{6BAE5851-50D5-4934-8D5E-30361A8A40F3}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Types", "src\types\lib\types.vcxproj", "{18D09A24-8240-42D6-8CB6-236EEE820263}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dependencies", "dependencies", "{75AC9360-76FD-4ABC-AFEC-EF342BD2B3E9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + AuditMode|ARM64 = AuditMode|ARM64 + AuditMode|x64 = AuditMode|x64 + AuditMode|x86 = AuditMode|x86 + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Fuzzing|ARM64 = Fuzzing|ARM64 + Fuzzing|x64 = Fuzzing|x64 + Fuzzing|x86 = Fuzzing|x86 + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.AuditMode|ARM64.ActiveCfg = Debug|ARM64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.AuditMode|ARM64.Build.0 = Debug|ARM64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.AuditMode|ARM64.Deploy.0 = Debug|ARM64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.AuditMode|x64.ActiveCfg = Debug|x64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.AuditMode|x64.Build.0 = Debug|x64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.AuditMode|x64.Deploy.0 = Debug|x64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.AuditMode|x86.ActiveCfg = Debug|x86 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.AuditMode|x86.Build.0 = Debug|x86 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.AuditMode|x86.Deploy.0 = Debug|x86 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Debug|ARM64.Build.0 = Debug|ARM64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Debug|x64.ActiveCfg = Debug|x64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Debug|x64.Build.0 = Debug|x64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Debug|x64.Deploy.0 = Debug|x64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Debug|x86.ActiveCfg = Debug|x86 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Debug|x86.Build.0 = Debug|x86 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Debug|x86.Deploy.0 = Debug|x86 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Fuzzing|ARM64.ActiveCfg = Debug|ARM64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Fuzzing|ARM64.Build.0 = Debug|ARM64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Fuzzing|ARM64.Deploy.0 = Debug|ARM64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Fuzzing|x64.ActiveCfg = Debug|x64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Fuzzing|x64.Build.0 = Debug|x64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Fuzzing|x64.Deploy.0 = Debug|x64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Fuzzing|x86.ActiveCfg = Debug|x86 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Fuzzing|x86.Build.0 = Debug|x86 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Fuzzing|x86.Deploy.0 = Debug|x86 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Release|ARM64.ActiveCfg = Release|ARM64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Release|ARM64.Build.0 = Release|ARM64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Release|ARM64.Deploy.0 = Release|ARM64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Release|x64.ActiveCfg = Release|x64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Release|x64.Build.0 = Release|x64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Release|x64.Deploy.0 = Release|x64 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Release|x86.ActiveCfg = Release|x86 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Release|x86.Build.0 = Release|x86 + {CF31505E-3BAE-4C0A-81D7-F1EB279F40BB}.Release|x86.Deploy.0 = Release|x86 + {A4394404-37F7-41C1-802B-49788D3720E3}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 + {A4394404-37F7-41C1-802B-49788D3720E3}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 + {A4394404-37F7-41C1-802B-49788D3720E3}.AuditMode|x64.ActiveCfg = AuditMode|x64 + {A4394404-37F7-41C1-802B-49788D3720E3}.AuditMode|x64.Build.0 = AuditMode|x64 + {A4394404-37F7-41C1-802B-49788D3720E3}.AuditMode|x86.ActiveCfg = AuditMode|Win32 + {A4394404-37F7-41C1-802B-49788D3720E3}.AuditMode|x86.Build.0 = AuditMode|Win32 + {A4394404-37F7-41C1-802B-49788D3720E3}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A4394404-37F7-41C1-802B-49788D3720E3}.Debug|ARM64.Build.0 = Debug|ARM64 + {A4394404-37F7-41C1-802B-49788D3720E3}.Debug|x64.ActiveCfg = Debug|x64 + {A4394404-37F7-41C1-802B-49788D3720E3}.Debug|x64.Build.0 = Debug|x64 + {A4394404-37F7-41C1-802B-49788D3720E3}.Debug|x86.ActiveCfg = Debug|Win32 + {A4394404-37F7-41C1-802B-49788D3720E3}.Debug|x86.Build.0 = Debug|Win32 + {A4394404-37F7-41C1-802B-49788D3720E3}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 + {A4394404-37F7-41C1-802B-49788D3720E3}.Fuzzing|ARM64.Build.0 = Fuzzing|ARM64 + {A4394404-37F7-41C1-802B-49788D3720E3}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 + {A4394404-37F7-41C1-802B-49788D3720E3}.Fuzzing|x64.Build.0 = Fuzzing|x64 + {A4394404-37F7-41C1-802B-49788D3720E3}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 + {A4394404-37F7-41C1-802B-49788D3720E3}.Fuzzing|x86.Build.0 = Fuzzing|Win32 + {A4394404-37F7-41C1-802B-49788D3720E3}.Release|ARM64.ActiveCfg = Release|ARM64 + {A4394404-37F7-41C1-802B-49788D3720E3}.Release|ARM64.Build.0 = Release|ARM64 + {A4394404-37F7-41C1-802B-49788D3720E3}.Release|x64.ActiveCfg = Release|x64 + {A4394404-37F7-41C1-802B-49788D3720E3}.Release|x64.Build.0 = Release|x64 + {A4394404-37F7-41C1-802B-49788D3720E3}.Release|x86.ActiveCfg = Release|Win32 + {A4394404-37F7-41C1-802B-49788D3720E3}.Release|x86.Build.0 = Release|Win32 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.AuditMode|x64.ActiveCfg = AuditMode|x64 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.AuditMode|x64.Build.0 = AuditMode|x64 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.AuditMode|x86.ActiveCfg = AuditMode|Win32 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.AuditMode|x86.Build.0 = AuditMode|Win32 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.Debug|ARM64.Build.0 = Debug|ARM64 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.Debug|x64.ActiveCfg = Debug|x64 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.Debug|x64.Build.0 = Debug|x64 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.Debug|x86.ActiveCfg = Debug|Win32 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.Debug|x86.Build.0 = Debug|Win32 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.Fuzzing|ARM64.Build.0 = Fuzzing|ARM64 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.Fuzzing|x64.Build.0 = Fuzzing|x64 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.Fuzzing|x86.Build.0 = Fuzzing|Win32 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.Release|ARM64.ActiveCfg = Release|ARM64 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.Release|ARM64.Build.0 = Release|ARM64 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.Release|x64.ActiveCfg = Release|x64 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.Release|x64.Build.0 = Release|x64 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.Release|x86.ActiveCfg = Release|Win32 + {26C51792-41A3-4FE0-AB5E-8B69D557BF91}.Release|x86.Build.0 = Release|Win32 + {B4427499-9FDE-4208-B456-5BC580637633}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 + {B4427499-9FDE-4208-B456-5BC580637633}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 + {B4427499-9FDE-4208-B456-5BC580637633}.AuditMode|x64.ActiveCfg = AuditMode|x64 + {B4427499-9FDE-4208-B456-5BC580637633}.AuditMode|x64.Build.0 = AuditMode|x64 + {B4427499-9FDE-4208-B456-5BC580637633}.AuditMode|x86.ActiveCfg = AuditMode|Win32 + {B4427499-9FDE-4208-B456-5BC580637633}.AuditMode|x86.Build.0 = AuditMode|Win32 + {B4427499-9FDE-4208-B456-5BC580637633}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {B4427499-9FDE-4208-B456-5BC580637633}.Debug|ARM64.Build.0 = Debug|ARM64 + {B4427499-9FDE-4208-B456-5BC580637633}.Debug|x64.ActiveCfg = Debug|x64 + {B4427499-9FDE-4208-B456-5BC580637633}.Debug|x64.Build.0 = Debug|x64 + {B4427499-9FDE-4208-B456-5BC580637633}.Debug|x86.ActiveCfg = Debug|Win32 + {B4427499-9FDE-4208-B456-5BC580637633}.Debug|x86.Build.0 = Debug|Win32 + {B4427499-9FDE-4208-B456-5BC580637633}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 + {B4427499-9FDE-4208-B456-5BC580637633}.Fuzzing|ARM64.Build.0 = Fuzzing|ARM64 + {B4427499-9FDE-4208-B456-5BC580637633}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 + {B4427499-9FDE-4208-B456-5BC580637633}.Fuzzing|x64.Build.0 = Fuzzing|x64 + {B4427499-9FDE-4208-B456-5BC580637633}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 + {B4427499-9FDE-4208-B456-5BC580637633}.Fuzzing|x86.Build.0 = Fuzzing|Win32 + {B4427499-9FDE-4208-B456-5BC580637633}.Release|ARM64.ActiveCfg = Release|ARM64 + {B4427499-9FDE-4208-B456-5BC580637633}.Release|ARM64.Build.0 = Release|ARM64 + {B4427499-9FDE-4208-B456-5BC580637633}.Release|x64.ActiveCfg = Release|x64 + {B4427499-9FDE-4208-B456-5BC580637633}.Release|x64.Build.0 = Release|x64 + {B4427499-9FDE-4208-B456-5BC580637633}.Release|x86.ActiveCfg = Release|Win32 + {B4427499-9FDE-4208-B456-5BC580637633}.Release|x86.Build.0 = Release|Win32 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.AuditMode|x64.ActiveCfg = AuditMode|x64 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.AuditMode|x64.Build.0 = AuditMode|x64 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.AuditMode|x86.ActiveCfg = AuditMode|Win32 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.AuditMode|x86.Build.0 = AuditMode|Win32 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Debug|ARM64.Build.0 = Debug|ARM64 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Debug|x64.ActiveCfg = Debug|x64 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Debug|x64.Build.0 = Debug|x64 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Debug|x86.ActiveCfg = Debug|Win32 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Debug|x86.Build.0 = Debug|Win32 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Fuzzing|ARM64.Build.0 = Fuzzing|ARM64 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Fuzzing|x64.Build.0 = Fuzzing|x64 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Fuzzing|x86.Build.0 = Fuzzing|Win32 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Release|ARM64.ActiveCfg = Release|ARM64 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Release|ARM64.Build.0 = Release|ARM64 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Release|x64.ActiveCfg = Release|x64 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Release|x64.Build.0 = Release|x64 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Release|x86.ActiveCfg = Release|Win32 + {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Release|x86.Build.0 = Release|Win32 + {18D09A24-8240-42D6-8CB6-236EEE820263}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 + {18D09A24-8240-42D6-8CB6-236EEE820263}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 + {18D09A24-8240-42D6-8CB6-236EEE820263}.AuditMode|x64.ActiveCfg = AuditMode|x64 + {18D09A24-8240-42D6-8CB6-236EEE820263}.AuditMode|x64.Build.0 = AuditMode|x64 + {18D09A24-8240-42D6-8CB6-236EEE820263}.AuditMode|x86.ActiveCfg = AuditMode|Win32 + {18D09A24-8240-42D6-8CB6-236EEE820263}.AuditMode|x86.Build.0 = AuditMode|Win32 + {18D09A24-8240-42D6-8CB6-236EEE820263}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {18D09A24-8240-42D6-8CB6-236EEE820263}.Debug|ARM64.Build.0 = Debug|ARM64 + {18D09A24-8240-42D6-8CB6-236EEE820263}.Debug|x64.ActiveCfg = Debug|x64 + {18D09A24-8240-42D6-8CB6-236EEE820263}.Debug|x64.Build.0 = Debug|x64 + {18D09A24-8240-42D6-8CB6-236EEE820263}.Debug|x86.ActiveCfg = Debug|Win32 + {18D09A24-8240-42D6-8CB6-236EEE820263}.Debug|x86.Build.0 = Debug|Win32 + {18D09A24-8240-42D6-8CB6-236EEE820263}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 + {18D09A24-8240-42D6-8CB6-236EEE820263}.Fuzzing|ARM64.Build.0 = Fuzzing|ARM64 + {18D09A24-8240-42D6-8CB6-236EEE820263}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 + {18D09A24-8240-42D6-8CB6-236EEE820263}.Fuzzing|x64.Build.0 = Fuzzing|x64 + {18D09A24-8240-42D6-8CB6-236EEE820263}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 + {18D09A24-8240-42D6-8CB6-236EEE820263}.Fuzzing|x86.Build.0 = Fuzzing|Win32 + {18D09A24-8240-42D6-8CB6-236EEE820263}.Release|ARM64.ActiveCfg = Release|ARM64 + {18D09A24-8240-42D6-8CB6-236EEE820263}.Release|ARM64.Build.0 = Release|ARM64 + {18D09A24-8240-42D6-8CB6-236EEE820263}.Release|x64.ActiveCfg = Release|x64 + {18D09A24-8240-42D6-8CB6-236EEE820263}.Release|x64.Build.0 = Release|x64 + {18D09A24-8240-42D6-8CB6-236EEE820263}.Release|x86.ActiveCfg = Release|Win32 + {18D09A24-8240-42D6-8CB6-236EEE820263}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {6BAE5851-50D5-4934-8D5E-30361A8A40F3} = {75AC9360-76FD-4ABC-AFEC-EF342BD2B3E9} + {18D09A24-8240-42D6-8CB6-236EEE820263} = {75AC9360-76FD-4ABC-AFEC-EF342BD2B3E9} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {05EAE315-9188-4D7B-B889-7D5F480A8915} + EndGlobalSection +EndGlobal diff --git a/build/rules/GenerateFeatureFlags.proj b/build/rules/GenerateFeatureFlags.proj new file mode 100644 index 000000000..8a8b494a6 --- /dev/null +++ b/build/rules/GenerateFeatureFlags.proj @@ -0,0 +1,97 @@ + + + + + + + + Release + AnyCPU + + + Fuzzing + AnyCPU + + + AuditMode + AnyCPU + + + Debug + AnyCPU + + + + + d97c3c61-53cd-4e72-919b-9a0940e038f9 + + + + $(SolutionDir)obj\$(Configuration)\GenerateFeatureFlags\ + $(SolutionDir)bin\$(Configuration)\ + + <_WTBrandingName Condition="'$(WindowsTerminalBranding)'=='Preview'">Preview + <_WTBrandingName Condition="'$(WindowsTerminalBranding)'=='Release'">Release + <_WTBrandingName Condition="'$(_WTBrandingName)'==''">Dev + + + + + + + + + <_BrandingLines Include="$(_WTBrandingName)" /> + + + + + + + <_BranchBrandingCacheFiles Include="$(IntermediateOutputPath)branch_branding_cache.txt" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/consolegit2gitfilters.json b/consolegit2gitfilters.json index 5a760692b..5f5d0eb26 100644 --- a/consolegit2gitfilters.json +++ b/consolegit2gitfilters.json @@ -23,6 +23,8 @@ "/doc/cascadia/", "/doc/user-docs/", "/src/tools/MonarchPeasantSample/", + "/scratch/", + "Scratch.sln", ], "SuffixFilters": [ ".dbb", diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index c5cb67540..66e914fa9 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -191,6 +191,7 @@ "find", "findMatch", "focusPane", + "globalSummon", "identifyWindow", "identifyWindows", "moveFocus", @@ -206,6 +207,7 @@ "prevTab", "renameTab", "openTabRenamer", + "quakeMode", "resetFontSize", "resizePane", "renameWindow", @@ -817,6 +819,63 @@ } ] }, + "GlobalSummonAction": { + "description": "This is a special action that works globally in the OS, rather than only in the context of the terminal window. When pressed, this action will summon the terminal window.", + "allOf": [ + { "$ref": "#/definitions/ShortcutAction" }, + { + "properties": { + "action": { "type": "string", "pattern": "globalSummon" }, + "desktop": { + "type": "string", + "default": "toCurrent", + "description": "This controls how the terminal should interact with virtual desktops.\n- \"any\": Leave the window on whichever desktop it's already on - will switch to that desktop as the window is activated.\n- \"toCurrent\" (default): Move the window to the current virtual desktop.\n- \"onCurrent\": Only summon the window if it's already on the current virtual desktop. ", + "enum": [ + "any", + "toCurrent", + "onCurrent" + ] + }, + "monitor": { + "type": "string", + "default": "toMouse", + "description": "This controls the monitor that the window will be summoned from/to.\n- \"any\": Summon the most recently used window, regardless of which monitor it's currently on.\n- \"toCurrent\": Summon the most recently used window to the monitor with the current foreground window.\n- \"toMouse\" (default): Summon the most recently used window to the monitor where the mouse cursor is.", + "enum": [ + "any", + "toCurrent", + "toMouse" + ] + }, + "name": { + "type": "string", + "description": "When provided, summon the window whose name or ID matches the given name value. If no such window exists, then create a new window with that name." + }, + "dropdownDuration": { + "type": "number", + "minimum": 0, + "default": 0, + "description": "When provided with a positive number, \"slide\" the window in from the top of the screen using an animation that lasts dropdownDuration milliseconds." + }, + "toggleVisibility": { + "type": "boolean", + "default": true, + "description": "When true, pressing the assigned keys for this action will dismiss (minimize) the window when the window is currently the foreground window." + } + } + } + ] + }, + "QuakeModeAction": { + "description": "This action is a special variation of the globalSummon action. It specifically summons a window called \"_quake\". If you would like to change the behavior of the quakeMode action, we recommended creating a new globalSummon entry.", + "allOf": [ + { "$ref": "#/definitions/ShortcutAction" }, + { + "properties": { + "action": { "type": "string", "pattern": "quakeMode" } + } + } + ] + }, "Keybinding": { "additionalProperties": false, "properties": { @@ -848,6 +907,8 @@ { "$ref": "#/definitions/RenameTabAction" }, { "$ref": "#/definitions/RenameWindowAction" }, { "$ref": "#/definitions/FocusPaneAction" }, + { "$ref": "#/definitions/GlobalSummonAction" }, + { "$ref": "#/definitions/QuakeModeAction" }, { "type": "null" } ] }, @@ -868,16 +929,38 @@ }, "icon": { "$ref": "#/definitions/Icon" }, "name": { - "description": "The name that will appear in the command palette. If one isn't provided, the terminal will attempt to automatically generate a name.", + "description": "The name that will appear in the command palette. If one isn't provided, the terminal will attempt to automatically generate a name.\nIf name is a string, it will be the name of the command.\nIf name is a object, the key property of the object will be used to lookup a localized string resource for the command", + "properties": { + "key": { + "type": "string" + } + }, "type": [ "string", + "object", "null" ] + }, + "iterateOn": { + "type": "string", + "description": "Used to create iterable commands based on other objects in your settings. Possible values:\n- \"profiles\" \n- \"schemes\"", + "enum": [ + "profiles", + "schemes" + ] + }, + "commands": { + "description": "List of commands to execute", + "items": { + "$ref": "#/definitions/Keybinding/properties/command" + }, + "minItems": 1, + "type": "array" } }, - "required": [ - "command", - "keys" + "anyOf": [ + {"required": ["name","commands"]}, + {"required": ["command"]} ], "type": "object" }, @@ -1011,13 +1094,26 @@ "type": [ "integer", "string" ], "deprecated": true }, + "actions": { + "description": "Properties are specific to each custom action.", + "items": { + "$ref": "#/definitions/Keybinding" + }, + "type": "array" + }, "keybindings": { - "description": "Properties are specific to each custom key binding.", + "description": "[deprecated] Use actions instead.", + "deprecated": true, "items": { "$ref": "#/definitions/Keybinding" }, "type": "array" }, + "language": { + "default": "", + "description": "Sets an override for the app's preferred language, expressed as a BCP-47 language tag like en-US.", + "type": "string" + }, "theme": { "default": "system", "description": "Sets the theme of the application. The special value \"system\" refers to the active Windows system theme.", diff --git a/doc/feature_flags.md b/doc/feature_flags.md new file mode 100644 index 000000000..f4c2a7d68 --- /dev/null +++ b/doc/feature_flags.md @@ -0,0 +1,65 @@ +# til::feature + +Feature flags are controlled by an XML document stored at `src/features.xml`. + +## Example Document + +```xml + + + + + Feature_XYZ + + Does a cool thing + + + 1234 + + + AlwaysEnabled|AlwaysDisabled + + + + branch/with/wildcard/* + + + + + + ... + + + + + + Release + + + + + + ... + + + + + + +``` + +## Notes + +Features that are disabled for Release using `alwaysDisabledReleaseTokens` are +*always* disabled in Release, even if they come from a branch that would have +been enabled by the wildcard. + +### Precedence + +1. `alwaysDisabledReleaseTokens` +2. Enabled branches +3. Disabled branches + * The longest branch token that matches your branch will win. +3. Enabled brandings +4. Disabled brandings +5. The feature's default state diff --git a/scratch/ScratchIslandApp/Package/Package.appxmanifest b/scratch/ScratchIslandApp/Package/Package.appxmanifest new file mode 100644 index 000000000..dcd54adca --- /dev/null +++ b/scratch/ScratchIslandApp/Package/Package.appxmanifest @@ -0,0 +1,68 @@ + + + + + + + + Sample App + A Lone Developer + Images\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scratch/ScratchIslandApp/Package/Package.wapproj b/scratch/ScratchIslandApp/Package/Package.wapproj new file mode 100644 index 000000000..262719541 --- /dev/null +++ b/scratch/ScratchIslandApp/Package/Package.wapproj @@ -0,0 +1,155 @@ + + + + + + + false + false + + + cf31505e-3bae-4c0a-81d7-f1eb279f40bb + ..\WindowExe\WindowExe.vcxproj + NativeOnly + + + false + Never + + + true + False + Package_TemporaryKey.pfx + + + + + + + Designer + + + + + + + + + + + + + + + + + + $(OpenConsoleCommonOutDir)TerminalConnection\Microsoft.Terminal.TerminalConnection.winmd + $(OpenConsoleCommonOutDir)TerminalConnection\TerminalConnection.dll + true + true + true + + + $(OpenConsoleCommonOutDir)Microsoft.Terminal.Control\Microsoft.Terminal.Control.winmd + $(OpenConsoleCommonOutDir)Microsoft.Terminal.Control\Microsoft.Terminal.Control.dll + true + true + true + + + + + + + + <_TemporaryFilteredWapProjOutput Include="@(_FilteredNonWapProjProjectOutput)" /> + <_FilteredNonWapProjProjectOutput Remove="@(_TemporaryFilteredWapProjOutput)" /> + <_FilteredNonWapProjProjectOutput Include="@(_TemporaryFilteredWapProjOutput)"> + + + + + + + + + + <_GenerateProjectPriFileDependsOn Condition="$(MSBuildVersion) < '16.3.0'">OpenConsoleLiftDesktopBridgePriFiles;$(_GenerateProjectPriFileDependsOn) + + + + <_PriFile Include="@(_NonWapProjProjectOutput)" Condition="'%(Extension)' == '.pri'" /> + + + + + + + + $([MSBuild]::Unescape('$(WapProjBeforeGenerateAppxManifestDependsOn.Replace('_RemoveAllNonWapUWPItems', '_OpenConsoleRemoveAllNonWapUWPItems'))')) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + diff --git a/scratch/ScratchIslandApp/Package/Resources/Resources.resw b/scratch/ScratchIslandApp/Package/Resources/Resources.resw new file mode 100644 index 000000000..872f430e7 --- /dev/null +++ b/scratch/ScratchIslandApp/Package/Resources/Resources.resw @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Scratch XAML Island App + + + Scratch App + + diff --git a/scratch/ScratchIslandApp/Package/Resources/en-US/Resources.resw b/scratch/ScratchIslandApp/Package/Resources/en-US/Resources.resw new file mode 100644 index 000000000..4ffee428e --- /dev/null +++ b/scratch/ScratchIslandApp/Package/Resources/en-US/Resources.resw @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + A scratch app for XAML Islands tests + + diff --git a/scratch/ScratchIslandApp/SampleApp/App.base.h b/scratch/ScratchIslandApp/SampleApp/App.base.h new file mode 100644 index 000000000..d6da79f1b --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/App.base.h @@ -0,0 +1,40 @@ +#pragma once + +namespace winrt::SampleApp::implementation +{ + template + struct App_baseWithProvider : public App_base + { + using IXamlType = ::winrt::Windows::UI::Xaml::Markup::IXamlType; + + IXamlType GetXamlType(::winrt::Windows::UI::Xaml::Interop::TypeName const& type) + { + return AppProvider()->GetXamlType(type); + } + + IXamlType GetXamlType(::winrt::hstring const& fullName) + { + return AppProvider()->GetXamlType(fullName); + } + + ::winrt::com_array<::winrt::Windows::UI::Xaml::Markup::XmlnsDefinition> GetXmlnsDefinitions() + { + return AppProvider()->GetXmlnsDefinitions(); + } + + private: + bool _contentLoaded{ false }; + std::shared_ptr _appProvider; + std::shared_ptr AppProvider() + { + if (!_appProvider) + { + _appProvider = std::make_shared(); + } + return _appProvider; + } + }; + + template + using AppT2 = App_baseWithProvider; +} diff --git a/scratch/ScratchIslandApp/SampleApp/App.cpp b/scratch/ScratchIslandApp/SampleApp/App.cpp new file mode 100644 index 000000000..5a2ae523f --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/App.cpp @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "App.h" +#include "App.g.cpp" + +using namespace winrt; +using namespace winrt::Windows::ApplicationModel::Activation; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Xaml::Controls; +using namespace winrt::Windows::UI::Xaml::Navigation; + +namespace winrt::SampleApp::implementation +{ + App::App() + { + // This is the same trick that Initialize() is about to use to figure out whether we're coming + // from a UWP context or from a Win32 context + // See https://github.com/windows-toolkit/Microsoft.Toolkit.Win32/blob/52611c57d89554f357f281d0c79036426a7d9257/Microsoft.Toolkit.Win32.UI.XamlApplication/XamlApplication.cpp#L42 + const auto dispatcherQueue = ::winrt::Windows::System::DispatcherQueue::GetForCurrentThread(); + if (dispatcherQueue) + { + _isUwp = true; + } + + Initialize(); + + // Disable XAML's automatic backplating of text when in High Contrast + // mode: we want full control of and responsibility for the foreground + // and background colors that we draw in XAML. + HighContrastAdjustment(::winrt::Windows::UI::Xaml::ApplicationHighContrastAdjustment::None); + } + + SampleAppLogic App::Logic() + { + static SampleAppLogic logic; + return logic; + } + + /// + /// Invoked when the application is launched normally by the end user. Other entry points + /// will be used such as when the application is launched to open a specific file. + /// + /// Details about the launch request and process. + void App::OnLaunched(LaunchActivatedEventArgs const& /*e*/) + { + // if this is a UWP... it means its our problem to hook up the content to the window here. + if (_isUwp) + { + auto content = Window::Current().Content(); + if (content == nullptr) + { + auto logic = Logic(); + logic.Create(); + + auto page = logic.GetRoot().as(); + + Window::Current().Content(page); + Window::Current().Activate(); + } + } + } +} diff --git a/scratch/ScratchIslandApp/SampleApp/App.h b/scratch/ScratchIslandApp/SampleApp/App.h new file mode 100644 index 000000000..fb6235d75 --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/App.h @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "App.g.h" +#include "App.base.h" + +namespace winrt::SampleApp::implementation +{ + struct App : AppT2 + { + public: + App(); + void OnLaunched(Windows::ApplicationModel::Activation::LaunchActivatedEventArgs const&); + + SampleApp::SampleAppLogic Logic(); + + private: + bool _isUwp = false; + }; +} + +namespace winrt::SampleApp::factory_implementation +{ + struct App : AppT + { + }; +} diff --git a/scratch/ScratchIslandApp/SampleApp/App.idl b/scratch/ScratchIslandApp/SampleApp/App.idl new file mode 100644 index 000000000..e51a73332 --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/App.idl @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "SampleAppLogic.idl"; + +namespace SampleApp +{ + // ADD ARBITRARY APP LOGIC TO SampleAppLogic.idl, NOT HERE. + // This is for XAML platform setup only. + [default_interface] runtimeclass App : Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication + { + App(); + + SampleAppLogic Logic { get; }; + } +} diff --git a/scratch/ScratchIslandApp/SampleApp/App.xaml b/scratch/ScratchIslandApp/SampleApp/App.xaml new file mode 100644 index 000000000..c6a2328f4 --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/App.xaml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + 8,0,8,0 + + + + + + + + + + + + + + + + + + + + + diff --git a/scratch/ScratchIslandApp/SampleApp/MyPage.cpp b/scratch/ScratchIslandApp/SampleApp/MyPage.cpp new file mode 100644 index 000000000..d4af3d80e --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/MyPage.cpp @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "MyPage.h" +#include +#include "MyPage.g.cpp" +#include "..\..\..\src\cascadia\UnitTests_Control\MockControlSettings.h" + +using namespace std::chrono_literals; +using namespace winrt::Microsoft::Terminal; + +namespace winrt +{ + namespace MUX = Microsoft::UI::Xaml; + namespace WUX = Windows::UI::Xaml; + using IInspectable = Windows::Foundation::IInspectable; +} + +namespace winrt::SampleApp::implementation +{ + MyPage::MyPage() + { + InitializeComponent(); + } + + void MyPage::Create() + { + TerminalConnection::EchoConnection conn{}; + auto settings = winrt::make_self(); + + Control::TermControl control{ *settings, conn }; + + InProcContent().Children().Append(control); + + // Once the control loads (and not before that), write some text for debugging: + control.Initialized([conn](auto&&, auto&&) { + conn.WriteInput(L"This TermControl is hosted in-proc..."); + }); + } + + // Method Description: + // - Gets the title of the currently focused terminal control. If there + // isn't a control selected for any reason, returns "Windows Terminal" + // Arguments: + // - + // Return Value: + // - the title of the focused control if there is one, else "Windows Terminal" + hstring MyPage::Title() + { + return { L"Sample Application" }; + } + +} diff --git a/scratch/ScratchIslandApp/SampleApp/MyPage.h b/scratch/ScratchIslandApp/SampleApp/MyPage.h new file mode 100644 index 000000000..c16c02bb3 --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/MyPage.h @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "MyPage.g.h" +#include "../../../src/cascadia/inc/cppwinrt_utils.h" + +namespace winrt::SampleApp::implementation +{ + struct MyPage : MyPageT + { + public: + MyPage(); + + void Create(); + + hstring Title(); + + private: + friend struct MyPageT; // for Xaml to bind events + }; +} + +namespace winrt::SampleApp::factory_implementation +{ + BASIC_FACTORY(MyPage); +} diff --git a/scratch/ScratchIslandApp/SampleApp/MyPage.idl b/scratch/ScratchIslandApp/SampleApp/MyPage.idl new file mode 100644 index 000000000..d3d0645b5 --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/MyPage.idl @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace SampleApp +{ + [default_interface] runtimeclass MyPage : Windows.UI.Xaml.Controls.Page + { + MyPage(); + } +} diff --git a/scratch/ScratchIslandApp/SampleApp/MyPage.xaml b/scratch/ScratchIslandApp/SampleApp/MyPage.xaml new file mode 100644 index 000000000..f6e129ee7 --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/MyPage.xaml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scratch/ScratchIslandApp/SampleApp/MySettings.cpp b/scratch/ScratchIslandApp/SampleApp/MySettings.cpp new file mode 100644 index 000000000..c042a34e7 --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/MySettings.cpp @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" + +#include "MySettings.h" +#include "MySettings.g.cpp" + +namespace winrt::SampleApp::implementation +{ +} diff --git a/scratch/ScratchIslandApp/SampleApp/MySettings.h b/scratch/ScratchIslandApp/SampleApp/MySettings.h new file mode 100644 index 000000000..170c9b540 --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/MySettings.h @@ -0,0 +1,97 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. +--*/ +#pragma once +#include "../../inc/cppwinrt_utils.h" +#include +#include +#include "MySettings.g.h" + +namespace winrt::SampleApp::implementation +{ + struct MySettings : MySettingsT + { + public: + MySettings() = default; + + // --------------------------- Core Settings --------------------------- + // All of these settings are defined in ICoreSettings. + + WINRT_PROPERTY(til::color, DefaultForeground, DEFAULT_FOREGROUND); + WINRT_PROPERTY(til::color, DefaultBackground, DEFAULT_BACKGROUND); + WINRT_PROPERTY(til::color, SelectionBackground, DEFAULT_FOREGROUND); + WINRT_PROPERTY(int32_t, HistorySize, DEFAULT_HISTORY_SIZE); + WINRT_PROPERTY(int32_t, InitialRows, 30); + WINRT_PROPERTY(int32_t, InitialCols, 80); + + WINRT_PROPERTY(bool, SnapOnInput, true); + WINRT_PROPERTY(bool, AltGrAliasing, true); + WINRT_PROPERTY(til::color, CursorColor, DEFAULT_CURSOR_COLOR); + WINRT_PROPERTY(winrt::Microsoft::Terminal::Core::CursorStyle, CursorShape, winrt::Microsoft::Terminal::Core::CursorStyle::Vintage); + WINRT_PROPERTY(uint32_t, CursorHeight, DEFAULT_CURSOR_HEIGHT); + WINRT_PROPERTY(winrt::hstring, WordDelimiters, DEFAULT_WORD_DELIMITERS); + WINRT_PROPERTY(bool, CopyOnSelect, false); + WINRT_PROPERTY(bool, InputServiceWarning, true); + WINRT_PROPERTY(bool, FocusFollowMouse, false); + + WINRT_PROPERTY(winrt::Windows::Foundation::IReference, TabColor, nullptr); + + WINRT_PROPERTY(winrt::Windows::Foundation::IReference, StartingTabColor, nullptr); + + winrt::Microsoft::Terminal::Core::ICoreAppearance UnfocusedAppearance() { return {}; }; + + WINRT_PROPERTY(bool, TrimBlockSelection, false); + // ------------------------ End of Core Settings ----------------------- + + WINRT_PROPERTY(winrt::hstring, ProfileName); + WINRT_PROPERTY(bool, UseAcrylic, false); + WINRT_PROPERTY(double, TintOpacity, 0.5); + WINRT_PROPERTY(winrt::hstring, Padding, DEFAULT_PADDING); + WINRT_PROPERTY(winrt::hstring, FontFace, L"Consolas"); + WINRT_PROPERTY(int32_t, FontSize, DEFAULT_FONT_SIZE); + + WINRT_PROPERTY(winrt::Windows::UI::Text::FontWeight, FontWeight); + + WINRT_PROPERTY(winrt::hstring, BackgroundImage); + WINRT_PROPERTY(double, BackgroundImageOpacity, 1.0); + + WINRT_PROPERTY(winrt::Windows::UI::Xaml::Media::Stretch, BackgroundImageStretchMode, winrt::Windows::UI::Xaml::Media::Stretch::UniformToFill); + WINRT_PROPERTY(winrt::Windows::UI::Xaml::HorizontalAlignment, BackgroundImageHorizontalAlignment, winrt::Windows::UI::Xaml::HorizontalAlignment::Center); + WINRT_PROPERTY(winrt::Windows::UI::Xaml::VerticalAlignment, BackgroundImageVerticalAlignment, winrt::Windows::UI::Xaml::VerticalAlignment::Center); + + WINRT_PROPERTY(winrt::Microsoft::Terminal::Control::IKeyBindings, KeyBindings, nullptr); + + WINRT_PROPERTY(winrt::hstring, Commandline); + WINRT_PROPERTY(winrt::hstring, StartingDirectory); + WINRT_PROPERTY(winrt::hstring, StartingTitle); + WINRT_PROPERTY(bool, SuppressApplicationTitle); + WINRT_PROPERTY(winrt::hstring, EnvironmentVariables); + + WINRT_PROPERTY(winrt::Microsoft::Terminal::Control::ScrollbarState, ScrollState, winrt::Microsoft::Terminal::Control::ScrollbarState::Visible); + + WINRT_PROPERTY(winrt::Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale); + + WINRT_PROPERTY(bool, RetroTerminalEffect, false); + WINRT_PROPERTY(bool, ForceFullRepaintRendering, false); + WINRT_PROPERTY(bool, SoftwareRendering, false); + WINRT_PROPERTY(bool, ForceVTInput, false); + + WINRT_PROPERTY(winrt::hstring, PixelShaderPath); + + WINRT_PROPERTY(bool, DetectURLs, true); + + private: + std::array _ColorTable; + + public: + winrt::Microsoft::Terminal::Core::Color GetColorTableEntry(int32_t index) noexcept { return _ColorTable.at(index); } + std::array ColorTable() { return _ColorTable; } + void ColorTable(std::array /*colors*/) {} + }; +} + +namespace winrt::SampleApp::factory_implementation +{ + BASIC_FACTORY(MySettings); +} diff --git a/scratch/ScratchIslandApp/SampleApp/MySettings.idl b/scratch/ScratchIslandApp/SampleApp/MySettings.idl new file mode 100644 index 000000000..42422b21a --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/MySettings.idl @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace SampleApp +{ + [default_interface] runtimeclass MySettings : Microsoft.Terminal.Core.ICoreSettings, + Microsoft.Terminal.Control.IControlSettings, + Microsoft.Terminal.Core.ICoreAppearance, + Microsoft.Terminal.Control.IControlAppearance + + { + MySettings(); + } +} diff --git a/scratch/ScratchIslandApp/SampleApp/Resources/en-US/Resources.resw b/scratch/ScratchIslandApp/SampleApp/Resources/en-US/Resources.resw new file mode 100644 index 000000000..f4af46df5 --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/Resources/en-US/Resources.resw @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + diff --git a/scratch/ScratchIslandApp/SampleApp/SampleAppLib.vcxproj b/scratch/ScratchIslandApp/SampleApp/SampleAppLib.vcxproj new file mode 100644 index 000000000..c16d6be6f --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/SampleAppLib.vcxproj @@ -0,0 +1,181 @@ + + + + {a4394404-37f7-41c1-802b-49788d3720e3} + Win32Proj + SampleApp + SampleAppLib + SampleAppLib + StaticLibrary + Console + true + + false + nested + + + + + + + + + true + + + + + + + + Designer + + + + + + Designer + + + + + + + + MyPage.xaml + Code + + + App.xaml + + + SampleAppLogic.idl + + + + + + + + MyPage.xaml + Code + + + Create + + + App.xaml + + + SampleAppLogic.idl + + + + + + + + + App.xaml + + + + + MyPage.xaml + Code + + + + + + + + + + + + Warning + + + + + + $(OpenConsoleCommonOutDir)TerminalCore\Microsoft.Terminal.Core.winmd + true + false + false + + + $(OpenConsoleCommonOutDir)TerminalConnection\Microsoft.Terminal.TerminalConnection.winmd + true + false + false + + + $(OpenConsoleCommonOutDir)Microsoft.Terminal.Control\Microsoft.Terminal.Control.winmd + true + false + false + + + + + + pch.h + ..;%(AdditionalIncludeDirectories); + + 4702;%(DisableSpecificWarnings) + + + $(OpenConsoleCommonOutDir)\ConTypes.lib;WindowsApp.lib;shell32.lib;%(AdditionalDependencies) + + + + + + + + + + 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}. + + + + + + + + <_GenerateProjectPriFileDependsOn>OpenConsolePlaceAppXbfAtRootOfResourceTree;$(_GenerateProjectPriFileDependsOn) + + + + <_RelocatedAppXamlData Include="@(PackagingOutputs)" Condition="'%(Filename)' == 'App' and ('%(Extension)' == '.xaml' or '%(Extension)' == '.xbf')" /> + + + %(Filename)%(Extension) + + + + + + diff --git a/scratch/ScratchIslandApp/SampleApp/SampleAppLogic.cpp b/scratch/ScratchIslandApp/SampleApp/SampleAppLogic.cpp new file mode 100644 index 000000000..4498f70df --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/SampleAppLogic.cpp @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "SampleAppLogic.h" +#include "SampleAppLogic.g.cpp" + +#include + +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Xaml::Controls; +using namespace winrt::Windows::UI::Core; +using namespace winrt::Windows::System; + +namespace winrt +{ + namespace MUX = Microsoft::UI::Xaml; + using IInspectable = Windows::Foundation::IInspectable; +} + +namespace winrt::SampleApp::implementation +{ + // Function Description: + // - Get the SampleAppLogic for the current active Xaml application, or null if there isn't one. + // Return value: + // - A pointer (bare) to the SampleAppLogic, or nullptr. The app logic outlives all other objects, + // unless the application is in a terrible way, so this is "safe." + SampleAppLogic* SampleAppLogic::Current() noexcept + try + { + if (auto currentXamlApp{ winrt::Windows::UI::Xaml::Application::Current().try_as() }) + { + if (auto SampleAppLogicPointer{ winrt::get_self(currentXamlApp.Logic()) }) + { + return SampleAppLogicPointer; + } + } + return nullptr; + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + return nullptr; + } + + SampleAppLogic::SampleAppLogic() + { + // 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, + // then it might look like App just failed to activate, which will + // cause you to chase down the rabbit hole of "why is App not + // registered?" when it definitely is. + + // The MyPage has to be constructed during our construction, to + // make sure that there's a terminal page for callers of + // SetTitleBarContent + _root = winrt::make_self(); + } + + // Method Description: + // - Build the UI for the terminal app. Before this method is called, it + // should not be assumed that the SampleApp is usable. The Settings + // should be loaded before this is called, either with LoadSettings or + // GetLaunchDimensions (which will call LoadSettings) + // Arguments: + // - + // Return Value: + // - + void SampleAppLogic::Create() + { + _root->Create(); + } + + UIElement SampleAppLogic::GetRoot() noexcept + { + return _root.as(); + } + + hstring SampleAppLogic::Title() + { + return _root->Title(); + } + +} diff --git a/scratch/ScratchIslandApp/SampleApp/SampleAppLogic.h b/scratch/ScratchIslandApp/SampleApp/SampleAppLogic.h new file mode 100644 index 000000000..8b8642a8e --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/SampleAppLogic.h @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "SampleAppLogic.g.h" +#include "MyPage.h" +#include "../../../src/cascadia/inc/cppwinrt_utils.h" + +namespace winrt::SampleApp::implementation +{ + struct SampleAppLogic : SampleAppLogicT + { + public: + static SampleAppLogic* Current() noexcept; + + SampleAppLogic(); + ~SampleAppLogic() = default; + + void Create(); + + Windows::UI::Xaml::UIElement GetRoot() noexcept; + + winrt::hstring Title(); + + private: + // If you add controls here, but forget to null them either here or in + // the ctor, you're going to have a bad time. It'll mysteriously fail to + // activate the SampleAppLogic. + // ALSO: If you add any UIElements as roots here, make sure they're + // updated in _ApplyTheme. The root currently is _root. + winrt::com_ptr _root{ nullptr }; + }; +} + +namespace winrt::SampleApp::factory_implementation +{ + struct SampleAppLogic : SampleAppLogicT + { + }; +} diff --git a/scratch/ScratchIslandApp/SampleApp/SampleAppLogic.idl b/scratch/ScratchIslandApp/SampleApp/SampleAppLogic.idl new file mode 100644 index 000000000..9b7d49e1a --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/SampleAppLogic.idl @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + + +namespace SampleApp +{ + + [default_interface] runtimeclass SampleAppLogic + { + SampleAppLogic(); + + void Create(); + + Windows.UI.Xaml.UIElement GetRoot(); + + String Title { get; }; + + } +} diff --git a/scratch/ScratchIslandApp/SampleApp/dll/SampleApp.def b/scratch/ScratchIslandApp/SampleApp/dll/SampleApp.def new file mode 100644 index 000000000..8c1a02932 --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/dll/SampleApp.def @@ -0,0 +1,3 @@ +EXPORTS +DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE +DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE diff --git a/scratch/ScratchIslandApp/SampleApp/dll/SampleApp.vcxproj b/scratch/ScratchIslandApp/SampleApp/dll/SampleApp.vcxproj new file mode 100644 index 000000000..1b1961fac --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/dll/SampleApp.vcxproj @@ -0,0 +1,101 @@ + + + + {26c51792-41a3-4fe0-ab5e-8b69d557bf91} + SampleApp + SampleApp + + + DynamicLibrary + Console + + true + + + + + + + + + + + + + + + + + + Create + + + + + + + + + + + + + + + true + true + + + + + + + + $(OpenConsoleCommonOutDir)TerminalCore\Microsoft.Terminal.Core.winmd + true + false + false + + + $(OpenConsoleCommonOutDir)TerminalConnection\Microsoft.Terminal.TerminalConnection.winmd + true + false + false + + + $(OpenConsoleCommonOutDir)Microsoft.Terminal.Control\Microsoft.Terminal.Control.winmd + true + false + false + + + + + + + + + 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}. + + + + + + + + $(OpenConsoleDir)\dep\jsoncpp\json;%(AdditionalIncludeDirectories); + + + WindowsApp.lib;%(AdditionalDependencies) + + /INCLUDE:_DllMain@12 %(AdditionalOptions) + /INCLUDE:DllMain %(AdditionalOptions) + + + + diff --git a/scratch/ScratchIslandApp/SampleApp/dll/pch.cpp b/scratch/ScratchIslandApp/SampleApp/dll/pch.cpp new file mode 100644 index 000000000..3c27d44d5 --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/dll/pch.cpp @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" diff --git a/scratch/ScratchIslandApp/SampleApp/dll/pch.h b/scratch/ScratchIslandApp/SampleApp/dll/pch.h new file mode 100644 index 000000000..76004c88f --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/dll/pch.h @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// pch.h +// Header for platform projection include files +// + +#pragma once + +// This file can be empty - the pch.h in SampleApp/lib does the heavy lifting +// of including all the headers we need. As this project is just a dll wrapper, +// we don't actually need anything in here. diff --git a/scratch/ScratchIslandApp/SampleApp/init.cpp b/scratch/ScratchIslandApp/SampleApp/init.cpp new file mode 100644 index 000000000..1c169d9d9 --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/init.cpp @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation +// Licensed under the MIT license. + +#include "pch.h" +#include +#include + +BOOL WINAPI DllMain(HINSTANCE /*hInstDll*/, DWORD /*reason*/, LPVOID /*reserved*/) +{ + return TRUE; +} + +UTILS_DEFINE_LIBRARY_RESOURCE_SCOPE(L"SampleApp/Resources") diff --git a/scratch/ScratchIslandApp/SampleApp/packages.config b/scratch/ScratchIslandApp/SampleApp/packages.config new file mode 100644 index 000000000..a6c68340a --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/packages.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/scratch/ScratchIslandApp/SampleApp/pch.cpp b/scratch/ScratchIslandApp/SampleApp/pch.cpp new file mode 100644 index 000000000..3c27d44d5 --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/pch.cpp @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" diff --git a/scratch/ScratchIslandApp/SampleApp/pch.h b/scratch/ScratchIslandApp/SampleApp/pch.h new file mode 100644 index 000000000..fd9d893f9 --- /dev/null +++ b/scratch/ScratchIslandApp/SampleApp/pch.h @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// pch.h +// Header for platform projection include files +// + +#pragma once + +#define WIN32_LEAN_AND_MEAN +#define NOMCX +#define NOHELP +#define NOCOMM + +// Manually include til after we include Windows.Foundation to give it winrt superpowers +#define BLOCK_TIL +#include +// This is inexplicable, but for whatever reason, cppwinrt conflicts with the +// SDK definition of this function, so the only fix is to undef it. +// from WinBase.h +// Windows::UI::Xaml::Media::Animation::IStoryboard::GetCurrentTime +#ifdef GetCurrentTime +#undef GetCurrentTime +#endif + +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "winrt/Windows.UI.Xaml.Markup.h" +#include "winrt/Windows.UI.ViewManagement.h" + +#include +#include +#include +#include + +#include + +// Including TraceLogging essentials for the binary +#include +#include +TRACELOGGING_DECLARE_PROVIDER(g_hSampleAppProvider); +#include +#include + +#include +#include + +#include +#include +#include + +// Manually include til after we include Windows.Foundation to give it winrt superpowers +#include "til.h" diff --git a/scratch/ScratchIslandApp/WindowExe/SampleAppHost.cpp b/scratch/ScratchIslandApp/WindowExe/SampleAppHost.cpp new file mode 100644 index 000000000..2d3afefc5 --- /dev/null +++ b/scratch/ScratchIslandApp/WindowExe/SampleAppHost.cpp @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "SampleAppHost.h" +#include "../types/inc/Viewport.hpp" +#include "../types/inc/utils.hpp" +#include "../types/inc/User32Utils.hpp" +#include "resource.h" + +using namespace winrt::Windows::UI; +using namespace winrt::Windows::UI::Composition; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Xaml::Hosting; +using namespace winrt::Windows::Foundation::Numerics; +using namespace ::Microsoft::Console; +using namespace ::Microsoft::Console::Types; + +SampleAppHost::SampleAppHost() noexcept : + _app{}, + _logic{ nullptr }, // don't make one, we're going to take a ref on app's + _window{ nullptr } +{ + _logic = _app.Logic(); // get a ref to app's logic + + _window = std::make_unique(); + _window->MakeWindow(); +} + +SampleAppHost::~SampleAppHost() +{ + // destruction order is important for proper teardown here + _window = nullptr; + _app.Close(); + _app = nullptr; +} +// Method Description: +// - Initializes the XAML island, creates the terminal app, and sets the +// island's content to that of the terminal app's content. Also registers some +// callbacks with TermApp. +// !!! IMPORTANT!!! +// This must be called *AFTER* WindowsXamlManager::InitializeForCurrentThread. +// If it isn't, then we won't be able to create the XAML island. +// Arguments: +// - +// Return Value: +// - +void SampleAppHost::Initialize() +{ + _window->Initialize(); + + _logic.Create(); + + _window->UpdateTitle(_logic.Title()); + + // Set up the content of the application. If the app has a custom titlebar, + // set that content as well. + _window->SetContent(_logic.GetRoot()); + + _window->OnAppInitialized(); + + // THIS IS A HACK + // + // We've got a weird crash that happens terribly inconsistently, only in + // Debug mode. Apparently, there's some weird ref-counting magic that goes + // on during teardown, and our Application doesn't get closed quite right, + // which can cause us to crash into the debugger. This of course, only + // happens on exit, and happens somewhere in the XamlHost.dll code. + // + // Crazily, if we _manually leak the Application_ here, then the crash + // doesn't happen. This doesn't matter, because we really want the + // Application to live for _the entire lifetime of the process_, so the only + // time when this object would actually need to get cleaned up is _during + // exit_. So we can safely leak this Application object, and have it just + // get cleaned up normally when our process exits. + ::winrt::SampleApp::App a{ _app }; + ::winrt::detach_abi(a); +} diff --git a/scratch/ScratchIslandApp/WindowExe/SampleAppHost.h b/scratch/ScratchIslandApp/WindowExe/SampleAppHost.h new file mode 100644 index 000000000..226f90a4b --- /dev/null +++ b/scratch/ScratchIslandApp/WindowExe/SampleAppHost.h @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" + +#include "SampleIslandWindow.h" + +class SampleAppHost +{ +public: + SampleAppHost() noexcept; + virtual ~SampleAppHost(); + + void Initialize(); + +private: + std::unique_ptr _window; + winrt::SampleApp::App _app; + winrt::SampleApp::SampleAppLogic _logic; +}; diff --git a/scratch/ScratchIslandApp/WindowExe/SampleBaseWindow.h b/scratch/ScratchIslandApp/WindowExe/SampleBaseWindow.h new file mode 100644 index 000000000..5c25452b3 --- /dev/null +++ b/scratch/ScratchIslandApp/WindowExe/SampleBaseWindow.h @@ -0,0 +1,228 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +// Custom window messages +#define CM_UPDATE_TITLE (WM_USER) + +#include + +template +class BaseWindow +{ +public: + virtual ~BaseWindow() = 0; + static T* GetThisFromHandle(HWND const window) noexcept + { + return reinterpret_cast(GetWindowLongPtr(window, GWLP_USERDATA)); + } + + [[nodiscard]] static LRESULT __stdcall WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept + { + WINRT_ASSERT(window); + + if (WM_NCCREATE == message) + { + auto cs = reinterpret_cast(lparam); + T* that = static_cast(cs->lpCreateParams); + WINRT_ASSERT(that); + WINRT_ASSERT(!that->_window); + that->_window = wil::unique_hwnd(window); + + return that->_OnNcCreate(wparam, lparam); + } + else if (T* that = GetThisFromHandle(window)) + { + return that->MessageHandler(message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); + } + + [[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept + { + switch (message) + { + case WM_DPICHANGED: + { + return HandleDpiChange(_window.get(), wparam, lparam); + } + + case WM_DESTROY: + { + PostQuitMessage(0); + return 0; + } + + case WM_SIZE: + { + UINT width = LOWORD(lparam); + UINT height = HIWORD(lparam); + + switch (wparam) + { + case SIZE_MAXIMIZED: + [[fallthrough]]; + case SIZE_RESTORED: + if (_minimized) + { + _minimized = false; + OnRestore(); + } + + // We always need to fire the resize event, even when we're transitioning from minimized. + // We might be transitioning directly from minimized to maximized, and we'll need + // to trigger any size-related content changes. + OnResize(width, height); + break; + case SIZE_MINIMIZED: + if (!_minimized) + { + _minimized = true; + OnMinimize(); + } + break; + default: + // do nothing. + break; + } + break; + } + case CM_UPDATE_TITLE: + { + SetWindowTextW(_window.get(), _title.c_str()); + break; + } + } + + return DefWindowProc(_window.get(), message, wparam, lparam); + } + + // DPI Change handler. on WM_DPICHANGE resize the window + [[nodiscard]] LRESULT HandleDpiChange(const HWND hWnd, const WPARAM wParam, const LPARAM lParam) + { + _inDpiChange = true; + const HWND hWndStatic = GetWindow(hWnd, GW_CHILD); + if (hWndStatic != nullptr) + { + const UINT uDpi = HIWORD(wParam); + + // Resize the window + auto lprcNewScale = reinterpret_cast(lParam); + + SetWindowPos(hWnd, nullptr, lprcNewScale->left, lprcNewScale->top, lprcNewScale->right - lprcNewScale->left, lprcNewScale->bottom - lprcNewScale->top, SWP_NOZORDER | SWP_NOACTIVATE); + + _currentDpi = uDpi; + } + _inDpiChange = false; + return 0; + } + + virtual void OnResize(const UINT width, const UINT height) = 0; + virtual void OnMinimize() = 0; + virtual void OnRestore() = 0; + + RECT GetWindowRect() const noexcept + { + RECT rc = { 0 }; + ::GetWindowRect(_window.get(), &rc); + return rc; + } + + HWND GetHandle() const noexcept + { + return _window.get(); + } + + float GetCurrentDpiScale() const noexcept + { + const auto dpi = ::GetDpiForWindow(_window.get()); + const auto scale = static_cast(dpi) / static_cast(USER_DEFAULT_SCREEN_DPI); + return scale; + } + + // Gets the physical size of the client area of the HWND in _window + SIZE GetPhysicalSize() const noexcept + { + RECT rect = {}; + GetClientRect(_window.get(), &rect); + const auto windowsWidth = rect.right - rect.left; + const auto windowsHeight = rect.bottom - rect.top; + return SIZE{ windowsWidth, windowsHeight }; + } + + // Gets the logical (in DIPs) size of a physical size specified by the parameter physicalSize + // Remarks: + // XAML coordinate system is always in Display Independent Pixels (a.k.a DIPs or Logical). However Win32 GDI (because of legacy reasons) + // in DPI mode "Per-Monitor and Per-Monitor (V2) DPI Awareness" is always in physical pixels. + // The formula to transform is: + // logical = (physical / dpi) + 0.5 // 0.5 is to ensure that we pixel snap correctly at the edges, this is necessary with odd DPIs like 1.25, 1.5, 1, .75 + // See also: + // https://docs.microsoft.com/en-us/windows/desktop/LearnWin32/dpi-and-device-independent-pixels + // https://docs.microsoft.com/en-us/windows/desktop/hidpi/high-dpi-desktop-application-development-on-windows#per-monitor-and-per-monitor-v2-dpi-awareness + winrt::Windows::Foundation::Size GetLogicalSize(const SIZE physicalSize) const noexcept + { + const auto scale = GetCurrentDpiScale(); + // 0.5 is to ensure that we pixel snap correctly at the edges, this is necessary with odd DPIs like 1.25, 1.5, 1, .75 + const auto logicalWidth = (physicalSize.cx / scale) + 0.5f; + const auto logicalHeight = (physicalSize.cy / scale) + 0.5f; + return winrt::Windows::Foundation::Size(logicalWidth, logicalHeight); + } + + winrt::Windows::Foundation::Size GetLogicalSize() const noexcept + { + return GetLogicalSize(GetPhysicalSize()); + } + + // Method Description: + // - Sends a message to our message loop to update the title of the window. + // Arguments: + // - newTitle: a string to use as the new title of the window. + // Return Value: + // - + void UpdateTitle(std::wstring_view newTitle) + { + _title = newTitle; + PostMessageW(_window.get(), CM_UPDATE_TITLE, 0, reinterpret_cast(nullptr)); + } + + // Method Description: + // Reset the current dpi of the window. This method is only called after we change the + // initial launch position. This makes sure the dpi is consistent with the monitor on which + // the window will launch + void RefreshCurrentDPI() + { + _currentDpi = GetDpiForWindow(_window.get()); + } + +protected: + using base_type = BaseWindow; + wil::unique_hwnd _window; + + unsigned int _currentDpi = 0; + bool _inDpiChange = false; + + std::wstring _title = L""; + + bool _minimized = false; + + // Method Description: + // - This method is called when the window receives the WM_NCCREATE message. + // Return Value: + // - The value returned from the window proc. + virtual [[nodiscard]] LRESULT _OnNcCreate(WPARAM wParam, LPARAM lParam) noexcept + { + SetWindowLongPtr(_window.get(), GWLP_USERDATA, reinterpret_cast(this)); + + EnableNonClientDpiScaling(_window.get()); + _currentDpi = GetDpiForWindow(_window.get()); + + return DefWindowProc(_window.get(), WM_NCCREATE, wParam, lParam); + }; +}; + +template +inline BaseWindow::~BaseWindow() +{ +} diff --git a/scratch/ScratchIslandApp/WindowExe/SampleIslandWindow.cpp b/scratch/ScratchIslandApp/WindowExe/SampleIslandWindow.cpp new file mode 100644 index 000000000..1c5cf10d0 --- /dev/null +++ b/scratch/ScratchIslandApp/WindowExe/SampleIslandWindow.cpp @@ -0,0 +1,220 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "SampleIslandWindow.h" +#include "../types/inc/Viewport.hpp" +#include "resource.h" +#include "icon.h" + +extern "C" IMAGE_DOS_HEADER __ImageBase; + +using namespace winrt::Windows::UI; +using namespace winrt::Windows::UI::Composition; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Xaml::Hosting; +using namespace winrt::Windows::Foundation::Numerics; +using namespace ::Microsoft::Console::Types; + +#define XAML_HOSTING_WINDOW_CLASS_NAME L"SCRATCH_HOSTING_WINDOW_CLASS" + +SampleIslandWindow::SampleIslandWindow() noexcept : + _interopWindowHandle{ nullptr }, + _rootGrid{ nullptr }, + _source{ nullptr } +{ +} + +SampleIslandWindow::~SampleIslandWindow() +{ + _source.Close(); +} + +// Method Description: +// - Create the actual window that we'll use for the application. +// Arguments: +// - +// Return Value: +// - +void SampleIslandWindow::MakeWindow() noexcept +{ + WNDCLASS wc{}; + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.hInstance = reinterpret_cast(&__ImageBase); + wc.lpszClassName = XAML_HOSTING_WINDOW_CLASS_NAME; + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = WndProc; + wc.hIcon = LoadIconW(wc.hInstance, MAKEINTRESOURCEW(IDI_APPICON)); + RegisterClass(&wc); + WINRT_ASSERT(!_window); + + // Create the window with the default size here - During the creation of the + // window, the system will give us a chance to set its size in WM_CREATE. + // WM_CREATE will be handled synchronously, before CreateWindow returns. + WINRT_VERIFY(CreateWindowEx(0, + wc.lpszClassName, + L"ScratchApp", + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + nullptr, + nullptr, + wc.hInstance, + this)); + + WINRT_ASSERT(_window); +} + +// Method Description: +// - Called when no tab is remaining to close the window. +// Arguments: +// - +// Return Value: +// - +void SampleIslandWindow::Close() +{ + PostQuitMessage(0); +} + +// Method Description: +// - Handles a WM_CREATE message. Calls our create callback, if one's been set. +// Arguments: +// - wParam: unused +// - lParam: the lParam of a WM_CREATE, which is a pointer to a CREATESTRUCTW +// Return Value: +// - +void SampleIslandWindow::_HandleCreateWindow(const WPARAM, const LPARAM lParam) noexcept +{ + // Get proposed window rect from create structure + CREATESTRUCTW* pcs = reinterpret_cast(lParam); + RECT rc; + rc.left = pcs->x; + rc.top = pcs->y; + rc.right = rc.left + pcs->cx; + rc.bottom = rc.top + pcs->cy; + + ShowWindow(_window.get(), SW_SHOW); + + UpdateWindow(_window.get()); + + UpdateWindowIconForActiveMetrics(_window.get()); +} + +void SampleIslandWindow::Initialize() +{ + const bool initialized = (_interopWindowHandle != nullptr); + + _source = DesktopWindowXamlSource{}; + + auto interop = _source.as(); + winrt::check_hresult(interop->AttachToWindow(_window.get())); + + // stash the child interop handle so we can resize it when the main hwnd is resized + interop->get_WindowHandle(&_interopWindowHandle); + + _rootGrid = winrt::Windows::UI::Xaml::Controls::Grid(); + _source.Content(_rootGrid); +} + +void SampleIslandWindow::OnSize(const UINT width, const UINT height) +{ + // update the interop window size + SetWindowPos(_interopWindowHandle, nullptr, 0, 0, width, height, SWP_SHOWWINDOW | SWP_NOACTIVATE); + + if (_rootGrid) + { + const auto size = GetLogicalSize(); + _rootGrid.Width(size.Width); + _rootGrid.Height(size.Height); + } +} + +[[nodiscard]] LRESULT SampleIslandWindow::MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept +{ + switch (message) + { + case WM_CREATE: + { + _HandleCreateWindow(wparam, lparam); + return 0; + } + case WM_SETFOCUS: + { + if (_interopWindowHandle != nullptr) + { + // send focus to the child window + SetFocus(_interopWindowHandle); + return 0; + } + break; + } + case WM_MENUCHAR: + { + // GH#891: return this LRESULT here to prevent the app from making a + // bell when alt+key is pressed. A menu is active and the user presses a + // key that does not correspond to any mnemonic or accelerator key, + return MAKELRESULT(0, MNC_CLOSE); + } + case WM_THEMECHANGED: + UpdateWindowIconForActiveMetrics(_window.get()); + return 0; + } + + return base_type::MessageHandler(message, wparam, lparam); +} + +// Method Description: +// - Called when the window has been resized (or maximized) +// Arguments: +// - width: the new width of the window _in pixels_ +// - height: the new height of the window _in pixels_ +void SampleIslandWindow::OnResize(const UINT width, const UINT height) +{ + if (_interopWindowHandle) + { + OnSize(width, height); + } +} + +// Method Description: +// - Called when the window is minimized to the taskbar. +void SampleIslandWindow::OnMinimize() +{ +} + +// Method Description: +// - Called when the window is restored from having been minimized. +void SampleIslandWindow::OnRestore() +{ +} + +void SampleIslandWindow::SetContent(winrt::Windows::UI::Xaml::UIElement content) +{ + _rootGrid.Children().Clear(); + _rootGrid.Children().Append(content); +} + +void SampleIslandWindow::OnAppInitialized() +{ + // Do a quick resize to force the island to paint + const auto size = GetPhysicalSize(); + OnSize(size.cx, size.cy); +} + +// Method Description: +// - Called when the app wants to change its theme. We'll update the root UI +// element of the entire XAML tree, so that all UI elements get the theme +// applied. +// Arguments: +// - arg: the ElementTheme to use as the new theme for the UI +// Return Value: +// - +void SampleIslandWindow::OnApplicationThemeChanged(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme) +{ + _rootGrid.RequestedTheme(requestedTheme); + // Invalidate the window rect, so that we'll repaint any elements we're + // drawing ourselves to match the new theme + ::InvalidateRect(_window.get(), nullptr, false); +} diff --git a/scratch/ScratchIslandApp/WindowExe/SampleIslandWindow.h b/scratch/ScratchIslandApp/WindowExe/SampleIslandWindow.h new file mode 100644 index 000000000..14a62f13c --- /dev/null +++ b/scratch/ScratchIslandApp/WindowExe/SampleIslandWindow.h @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "SampleBaseWindow.h" +#include "../../../src/cascadia/inc/cppwinrt_utils.h" + +class SampleIslandWindow : + public BaseWindow +{ +public: + SampleIslandWindow() noexcept; + virtual ~SampleIslandWindow() override; + + virtual void MakeWindow() noexcept; + void Close(); + + virtual void OnSize(const UINT width, const UINT height); + + [[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; + void OnResize(const UINT width, const UINT height) override; + void OnMinimize() override; + void OnRestore() override; + virtual void OnAppInitialized(); + virtual void SetContent(winrt::Windows::UI::Xaml::UIElement content); + virtual void OnApplicationThemeChanged(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme); + + virtual void Initialize(); + +protected: + void ForceResize() + { + // Do a quick resize to force the island to paint + const auto size = GetPhysicalSize(); + OnSize(size.cx, size.cy); + } + + HWND _interopWindowHandle; + + winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource _source; + + winrt::Windows::UI::Xaml::Controls::Grid _rootGrid; + + void _HandleCreateWindow(const WPARAM wParam, const LPARAM lParam) noexcept; +}; diff --git a/scratch/ScratchIslandApp/WindowExe/SampleMain.cpp b/scratch/ScratchIslandApp/WindowExe/SampleMain.cpp new file mode 100644 index 000000000..4c09f262c --- /dev/null +++ b/scratch/ScratchIslandApp/WindowExe/SampleMain.cpp @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "SampleAppHost.h" +#include "resource.h" +#include "../types/inc/User32Utils.hpp" +#include + +using namespace winrt; +using namespace winrt::Windows::UI; +using namespace winrt::Windows::UI::Composition; +using namespace winrt::Windows::UI::Xaml::Hosting; +using namespace winrt::Windows::Foundation::Numerics; + +// Routine Description: +// - Takes an image architecture and locates a string resource that maps to that architecture. +// Arguments: +// - imageArchitecture - An IMAGE_FILE_MACHINE architecture enum value +// - See https://docs.microsoft.com/en-us/windows/win32/sysinfo/image-file-machine-constants +// Return Value: +// - A string value representing the human-readable name of this architecture. +static std::wstring ImageArchitectureToString(USHORT imageArchitecture) +{ + // clang-format off + const auto id = imageArchitecture == IMAGE_FILE_MACHINE_I386 ? IDS_X86_ARCHITECTURE : + imageArchitecture == IMAGE_FILE_MACHINE_AMD64 ? IDS_AMD64_ARCHITECTURE : + imageArchitecture == IMAGE_FILE_MACHINE_ARM64 ? IDS_ARM64_ARCHITECTURE : + imageArchitecture == IMAGE_FILE_MACHINE_ARM ? IDS_ARM_ARCHITECTURE : + IDS_UNKNOWN_ARCHITECTURE; + // clang-format on + + return GetStringResource(id); +} + +// Routine Description: +// - Blocks the user from launching the application with a message box dialog and early exit +// if the process architecture doesn't match the system platform native architecture. +// - This is because the conhost.exe must match the condrv.sys on the system and the PTY +// infrastructure that powers everything won't work if we have a mismatch. +// Arguments: +// - +// Return Value: +// - +static void EnsureNativeArchitecture() +{ + USHORT processMachine{}; + USHORT nativeMachine{}; + THROW_IF_WIN32_BOOL_FALSE(IsWow64Process2(GetCurrentProcess(), &processMachine, &nativeMachine)); + if (processMachine != IMAGE_FILE_MACHINE_UNKNOWN && processMachine != nativeMachine) + { + const auto formatPattern = GetStringResource(IDS_ERROR_ARCHITECTURE_FORMAT); + + const auto nativeArchitecture = ImageArchitectureToString(nativeMachine); + const auto processArchitecture = ImageArchitectureToString(processMachine); + + auto buffer{ wil::str_printf(formatPattern.data(), nativeArchitecture.data(), processArchitecture.data()) }; + + MessageBoxW(nullptr, + buffer.data(), + GetStringResource(IDS_ERROR_DIALOG_TITLE).data(), + MB_OK | MB_ICONERROR); + + ExitProcess(0); + } +} + +static bool _messageIsF7Keypress(const MSG& message) +{ + return (message.message == WM_KEYDOWN || message.message == WM_SYSKEYDOWN) && message.wParam == VK_F7; +} +static bool _messageIsAltKeyup(const MSG& message) +{ + return (message.message == WM_KEYUP || message.message == WM_SYSKEYUP) && message.wParam == VK_MENU; +} + +int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) +{ + // If Terminal is spawned by a shortcut that requests that it run in a new process group + // while attached to a console session, that request is nonsense. That request will, however, + // cause WT to start with Ctrl-C disabled. This wouldn't matter, because it's a Windows-subsystem + // application. Unfortunately, that state is heritable. In short, if you start WT using cmd in + // a weird way, ^C stops working _inside_ the terminal. Mad. + SetConsoleCtrlHandler(NULL, FALSE); + + // Block the user from starting if they launched the incorrect architecture version of the project. + // This should only be applicable to developer versions. The package installation process + // should choose and install the correct one from the bundle. + EnsureNativeArchitecture(); + + // Make sure to call this so we get WM_POINTER messages. + EnableMouseInPointer(true); + + // !!! LOAD BEARING !!! + // We must initialize the main thread as a single-threaded apartment before + // constructing any Xaml objects. Failing to do so will cause some issues + // in accessibility somewhere down the line when a UIAutomation object will + // be queried on the wrong thread at the wrong time. + // We used to initialize as STA only _after_ initializing the application + // host, which loaded the settings. The settings needed to be loaded in MTA + // because we were using the Windows.Storage APIs. Since we're no longer + // doing that, we can safely init as STA before any WinRT dispatches. + winrt::init_apartment(winrt::apartment_type::single_threaded); + + // Create the SampleAppHost object, which will create both the window and the + // Terminal App. This MUST BE constructed before the Xaml manager as TermApp + // provides an implementation of Windows.UI.Xaml.Application. + SampleAppHost host; + + // Initialize the xaml content. This must be called AFTER the + // WindowsXamlManager is initialized. + host.Initialize(); + + MSG message; + + while (GetMessage(&message, nullptr, 0, 0)) + { + TranslateMessage(&message); + DispatchMessage(&message); + } + return 0; +} diff --git a/scratch/ScratchIslandApp/WindowExe/WindowExe.def b/scratch/ScratchIslandApp/WindowExe/WindowExe.def new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/scratch/ScratchIslandApp/WindowExe/WindowExe.def @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scratch/ScratchIslandApp/WindowExe/WindowExe.manifest b/scratch/ScratchIslandApp/WindowExe/WindowExe.manifest new file mode 100644 index 000000000..9d91e543d --- /dev/null +++ b/scratch/ScratchIslandApp/WindowExe/WindowExe.manifest @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + PerMonitorV2 + true + + + diff --git a/scratch/ScratchIslandApp/WindowExe/WindowExe.rc b/scratch/ScratchIslandApp/WindowExe/WindowExe.rc new file mode 100644 index 000000000..b11a8c88d --- /dev/null +++ b/scratch/ScratchIslandApp/WindowExe/WindowExe.rc @@ -0,0 +1,97 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. + +IDI_APPICON ICON "..\\..\\..\\res\\terminal.ico" +IDI_APPICON_HC_BLACK ICON "..\\..\\..\\res\\terminal\\images\\terminal_contrast-black.ico" +IDI_APPICON_HC_WHITE ICON "..\\..\\..\\res\\terminal\\images\\terminal_contrast-white.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_ERROR_DIALOG_TITLE "Error" + IDS_HELP_DIALOG_TITLE "Help" + IDS_ERROR_ARCHITECTURE_FORMAT + "This sample is designed to run on your system's native architecture (%s).\nYou are currently using the %s version.\n\nPlease use the version of this sample that matches your system's native architecture." + IDS_X86_ARCHITECTURE "i386" +END + +STRINGTABLE +BEGIN + IDS_AMD64_ARCHITECTURE "AMD64" + IDS_ARM64_ARCHITECTURE "ARM64" + IDS_ARM_ARCHITECTURE "ARM" + IDS_UNKNOWN_ARCHITECTURE "Unknown" +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/scratch/ScratchIslandApp/WindowExe/WindowExe.vcxproj b/scratch/ScratchIslandApp/WindowExe/WindowExe.vcxproj new file mode 100644 index 000000000..edd372c2f --- /dev/null +++ b/scratch/ScratchIslandApp/WindowExe/WindowExe.vcxproj @@ -0,0 +1,220 @@ + + + + + + {b4427499-9fde-4208-b456-5bc580637633} + Win32Proj + WindowExe + WindowExe + WindowExe + Application + false + Windows Store + true + false + Windows + + + + + + + + true + + + + + + $(OpenConsoleDir)\src\inc;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\Console;$(OpenConsoleDir)\dep\Win32K;$(OpenConsoleDir)\dep\gsl\include;%(AdditionalIncludeDirectories); + + + %(AdditionalDependencies) + + + + true + true + + + + + + + + + + + + + Create + + + + + + + + + + + + + + + + + + + + + + + + + + $(OpenConsoleCommonOutDir)TerminalCore\Microsoft.Terminal.Core.winmd + true + true + true + + + $(OpenConsoleCommonOutDir)TerminalConnection\Microsoft.Terminal.TerminalConnection.winmd + $(OpenConsoleCommonOutDir)TerminalConnection\TerminalConnection.dll + true + true + true + + + $(OpenConsoleCommonOutDir)Microsoft.Terminal.Control\Microsoft.Terminal.Control.winmd + $(OpenConsoleCommonOutDir)Microsoft.Terminal.Control\Microsoft.Terminal.Control.dll + true + true + true + + + + + + + + + WindowsLocalDebugger + + + + + + + + + + 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}. + + + + + + + + + + <_ContinueOnError Condition="'$(BuildingProject)' == 'true'">true + <_ContinueOnError Condition="'$(BuildingProject)' != 'true'">false + + + + + + + + + + + + + x86 + $(Platform) + + + + + <_OpenConsoleVCLibToCopy Include="$(VCToolsRedistInstallDir)\$(ReasonablePlatform)\Microsoft.VC142.CRT\*.dll" /> + + + $(ProjectName) + BuiltProjectOutputGroup + %(Filename)%(Extension) + + + + + + + + + <_TerminalConnectionDlls Include="$(OpenConsoleCommonOutDir)\TerminalConnection\*.dll" /> + + + $(ProjectName) + BuiltProjectOutputGroup + %(Filename)%(Extension) + + + + + + + <_WindowsTerminalExe Include="$(OpenConsoleCommonOutDir)\WindowsTerminal\*.exe" /> + + + $(ProjectName) + BuiltProjectOutputGroup + %(Filename)%(Extension) + + + + + + + + + + + + diff --git a/scratch/ScratchIslandApp/WindowExe/icon.cpp b/scratch/ScratchIslandApp/WindowExe/icon.cpp new file mode 100644 index 000000000..ca194de06 --- /dev/null +++ b/scratch/ScratchIslandApp/WindowExe/icon.cpp @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "resource.h" + +static int _GetActiveAppIconResource() +{ + auto iconResource{ IDI_APPICON }; + + HIGHCONTRASTW hcInfo{}; + hcInfo.cbSize = sizeof(hcInfo); + + if (SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(hcInfo), &hcInfo, 0)) + { + if (WI_IsFlagSet(hcInfo.dwFlags, HCF_HIGHCONTRASTON)) + { + iconResource = IDI_APPICON_HC_BLACK; + + if (0x00FFFFFF == GetSysColor(COLOR_WINDOW)) // white window color == white high contrast + { + iconResource = IDI_APPICON_HC_WHITE; + } + } + } + + return iconResource; +} + +void UpdateWindowIconForActiveMetrics(HWND window) +{ + auto iconResource{ MAKEINTRESOURCEW(_GetActiveAppIconResource()) }; + + // These handles are loaded with LR_SHARED, so they are safe to "leak". + HANDLE smallIcon{ LoadImageW(wil::GetModuleInstanceHandle(), iconResource, IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_SHARED) }; + LOG_LAST_ERROR_IF_NULL(smallIcon); + + HANDLE largeIcon{ LoadImageW(wil::GetModuleInstanceHandle(), iconResource, IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), LR_SHARED) }; + LOG_LAST_ERROR_IF_NULL(largeIcon); + + if (smallIcon) + { + SendMessageW(window, WM_SETICON, ICON_SMALL, reinterpret_cast(smallIcon)); + } + if (largeIcon) + { + SendMessageW(window, WM_SETICON, ICON_BIG, reinterpret_cast(largeIcon)); + } +} diff --git a/scratch/ScratchIslandApp/WindowExe/icon.h b/scratch/ScratchIslandApp/WindowExe/icon.h new file mode 100644 index 000000000..5e418fc28 --- /dev/null +++ b/scratch/ScratchIslandApp/WindowExe/icon.h @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +void UpdateWindowIconForActiveMetrics(HWND window); diff --git a/scratch/ScratchIslandApp/WindowExe/packages.config b/scratch/ScratchIslandApp/WindowExe/packages.config new file mode 100644 index 000000000..409baff13 --- /dev/null +++ b/scratch/ScratchIslandApp/WindowExe/packages.config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/scratch/ScratchIslandApp/WindowExe/pch.cpp b/scratch/ScratchIslandApp/WindowExe/pch.cpp new file mode 100644 index 000000000..398a99f66 --- /dev/null +++ b/scratch/ScratchIslandApp/WindowExe/pch.cpp @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" diff --git a/scratch/ScratchIslandApp/WindowExe/pch.h b/scratch/ScratchIslandApp/WindowExe/pch.h new file mode 100644 index 000000000..de08907f8 --- /dev/null +++ b/scratch/ScratchIslandApp/WindowExe/pch.h @@ -0,0 +1,82 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- pch.h + +Abstract: +- Contains external headers to include in the precompile phase of console build process. +- Avoid including internal project headers. Instead include them only in the classes that need them (helps with test project building). +--*/ + +#pragma once + +// Ignore checked iterators warning from VC compiler. +#define _SCL_SECURE_NO_WARNINGS + +// Block minwindef.h min/max macros to prevent conflict +#define NOMINMAX + +#define WIN32_LEAN_AND_MEAN +#define NOMCX +#define NOHELP +#define NOCOMM + +#include + +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) + +#include +#include +#include +#include +#include +#include +#include + +// Manually include til after we include Windows.Foundation to give it winrt superpowers +#define BLOCK_TIL +#include "../inc/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 + +// Needed just for XamlIslands to work at all: +#include +#include +#include +#include + +// Additional headers for various xaml features. We need: +// * Core so we can resume_foreground with CoreDispatcher +// * Controls for grid +// * Media for ScaleTransform +#include +#include +#include + +#include + +#include +#include + +// Including TraceLogging essentials for the binary +#include +#include +TRACELOGGING_DECLARE_PROVIDER(g_hWindowsTerminalProvider); +#include +#include + +// For commandline argument processing +#include +#include +#include +#include "til.h" diff --git a/scratch/ScratchIslandApp/WindowExe/resource.h b/scratch/ScratchIslandApp/WindowExe/resource.h new file mode 100644 index 000000000..7e2918600 --- /dev/null +++ b/scratch/ScratchIslandApp/WindowExe/resource.h @@ -0,0 +1,27 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by WindowsTerminal.rc +// +#define IDI_APPICON 101 +#define IDI_APPICON_HC_BLACK 102 +#define IDI_APPICON_HC_WHITE 103 + +#define IDS_ERROR_DIALOG_TITLE 105 +#define IDS_HELP_DIALOG_TITLE 106 +#define IDS_ERROR_ARCHITECTURE_FORMAT 110 +#define IDS_X86_ARCHITECTURE 111 +#define IDS_AMD64_ARCHITECTURE 112 +#define IDS_ARM64_ARCHITECTURE 113 +#define IDS_ARM_ARCHITECTURE 114 +#define IDS_UNKNOWN_ARCHITECTURE 115 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 104 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp b/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp index 97251b8b2..164ac0bb8 100644 --- a/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp @@ -229,10 +229,12 @@ namespace SettingsModelLocalTests void SerializationTests::Actions() { + // simple command const std::string actionsString1{ R"([ { "command": "paste" } ])" }; + // complex command const std::string actionsString2A{ R"([ { "command": { "action": "setTabColor" } } ])" }; @@ -244,29 +246,35 @@ namespace SettingsModelLocalTests { "command": { "action": "copy", "singleLine": true, "copyFormatting": "html" } } ])" }; + // simple command with key chords const std::string actionsString3{ R"([ { "command": "toggleAlwaysOnTop", "keys": "ctrl+a" }, { "command": "toggleAlwaysOnTop", "keys": "ctrl+b" } ])" }; + // complex command with key chords const std::string actionsString4{ R"([ { "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+c" }, { "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+d" } ])" }; + // command with name and icon and multiple key chords const std::string actionsString5{ R"([ { "icon": "image.png", "name": "Scroll To Top Name", "command": "scrollToTop", "keys": "ctrl+e" }, { "command": "scrollToTop", "keys": "ctrl+f" } ])" }; + // complex command with new terminal args const std::string actionsString6{ R"([ { "command": { "action": "newTab", "index": 0 }, "keys": "ctrl+g" }, ])" }; + // complex command with meaningful null arg const std::string actionsString7{ R"([ { "command": { "action": "renameWindow", "name": null }, "keys": "ctrl+h" } ])" }; + // nested command const std::string actionsString8{ R"([ { "name": "Change font size...", @@ -278,6 +286,7 @@ namespace SettingsModelLocalTests } ])" }; + // iterable command const std::string actionsString9A{ R"([ { "name": "New tab", @@ -330,7 +339,20 @@ namespace SettingsModelLocalTests "name": "Send Input (Evil) ..." } ])"" }; + const std::string actionsString9D{ R""([ + { + "command": + { + "action": "newTab", + "profile": "${profile.name}" + }, + "icon": "${profile.icon}", + "iterateOn": "profiles", + "name": "${profile.name}: New tab" + } + ])"" }; + // unbound command const std::string actionsString10{ R"([ { "command": "unbound", "keys": "ctrl+c" } ])" }; @@ -365,6 +387,7 @@ namespace SettingsModelLocalTests RoundtripTest(actionsString9A); RoundtripTest(actionsString9B); RoundtripTest(actionsString9C); + RoundtripTest(actionsString9D); Log::Comment(L"unbound command"); RoundtripTest(actionsString10); diff --git a/src/cascadia/TerminalApp/App.xaml b/src/cascadia/TerminalApp/App.xaml index 88630a11c..a0678e05e 100644 --- a/src/cascadia/TerminalApp/App.xaml +++ b/src/cascadia/TerminalApp/App.xaml @@ -47,6 +47,9 @@ 8,0,8,0 + + 12 + diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 449d19b6b..6d60c146b 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -306,7 +306,8 @@ namespace winrt::TerminalApp::implementation }); _root->Create(); - _ApplyTheme(_settings.GlobalSettings().Theme()); + _ApplyLanguageSettingChange(); + _RefreshThemeRoutine(); _ApplyStartupTaskStateChange(); TraceLoggingWrite( @@ -895,36 +896,38 @@ namespace winrt::TerminalApp::implementation // this stops us from reloading too many times or too quickly. fire_and_forget AppLogic::_DispatchReloadSettings() { - static constexpr auto FileActivityQuiesceTime{ std::chrono::milliseconds(50) }; - if (!_settingsReloadQueued.exchange(true)) + if (_settingsReloadQueued.exchange(true)) + { + co_return; + } + + auto weakSelf = get_weak(); + + co_await winrt::resume_after(std::chrono::milliseconds(100)); + co_await winrt::resume_foreground(_root->Dispatcher()); + + if (auto self{ weakSelf.get() }) { - co_await winrt::resume_after(FileActivityQuiesceTime); _ReloadSettings(); _settingsReloadQueued.store(false); } } - fire_and_forget AppLogic::_LoadErrorsDialogRoutine() + void AppLogic::_ApplyLanguageSettingChange() { - co_await winrt::resume_foreground(_root->Dispatcher()); + using ApplicationLanguages = winrt::Windows::Globalization::ApplicationLanguages; - const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle"); - const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText"); - _ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult); + const auto language = _settings.GlobalSettings().Language(); + const auto primaryLanguageOverride = ApplicationLanguages::PrimaryLanguageOverride(); + + if (primaryLanguageOverride != language) + { + ApplicationLanguages::PrimaryLanguageOverride(language); + } } - fire_and_forget AppLogic::_ShowLoadWarningsDialogRoutine() + void AppLogic::_RefreshThemeRoutine() { - co_await winrt::resume_foreground(_root->Dispatcher()); - - _ShowLoadWarningsDialog(); - } - - fire_and_forget AppLogic::_RefreshThemeRoutine() - { - co_await winrt::resume_foreground(_root->Dispatcher()); - - // Refresh the UI theme _ApplyTheme(_settings.GlobalSettings().Theme()); } @@ -959,39 +962,26 @@ namespace winrt::TerminalApp::implementation return; } - auto weakThis{ get_weak() }; - co_await winrt::resume_foreground(_root->Dispatcher(), CoreDispatcherPriority::Normal); - if (auto page{ weakThis.get() }) - { - StartupTaskState state; - bool tryEnableStartupTask = _settings.GlobalSettings().StartOnUserLogin(); - StartupTask task = co_await StartupTask::GetAsync(StartupTaskName); + const auto tryEnableStartupTask = _settings.GlobalSettings().StartOnUserLogin(); + const auto task = co_await StartupTask::GetAsync(StartupTaskName); - state = task.State(); - switch (state) + switch (task.State()) + { + case StartupTaskState::Disabled: + if (tryEnableStartupTask) { - case StartupTaskState::Disabled: + co_await task.RequestEnableAsync(); + } + break; + case StartupTaskState::DisabledByUser: + // TODO: GH#6254: define UX for other StartupTaskStates + break; + case StartupTaskState::Enabled: + if (!tryEnableStartupTask) { - if (tryEnableStartupTask) - { - co_await task.RequestEnableAsync(); - } - break; - } - case StartupTaskState::DisabledByUser: - { - // TODO: GH#6254: define UX for other StartupTaskStates - break; - } - case StartupTaskState::Enabled: - { - if (!tryEnableStartupTask) - { - task.Disable(); - } - break; - } + task.Disable(); } + break; } } CATCH_LOG(); @@ -1009,12 +999,15 @@ namespace winrt::TerminalApp::implementation if (FAILED(_settingsLoadedResult)) { - _LoadErrorsDialogRoutine(); + const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle"); + const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText"); + _ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult); return; } - else if (_settingsLoadedResult == S_FALSE) + + if (_settingsLoadedResult == S_FALSE) { - _ShowLoadWarningsDialogRoutine(); + _ShowLoadWarningsDialog(); } // Here, we successfully reloaded the settings, and created a new @@ -1023,6 +1016,7 @@ namespace winrt::TerminalApp::implementation // Update the settings in TerminalPage _root->SetSettings(_settings, true); + _ApplyLanguageSettingChange(); _RefreshThemeRoutine(); _ApplyStartupTaskStateChange(); diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 50b7b95f0..ad520894b 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -124,18 +124,15 @@ namespace winrt::TerminalApp::implementation ::TerminalApp::AppCommandlineArgs _appArgs; ::TerminalApp::AppCommandlineArgs _settingsAppArgs; - int _ParseArgs(winrt::array_view& args); static TerminalApp::FindTargetWindowResult _doFindTargetWindow(winrt::array_view args, const Microsoft::Terminal::Settings::Model::WindowingMode& windowingBehavior); void _ShowLoadErrorsDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey, HRESULT settingsLoadedResult); void _ShowLoadWarningsDialog(); bool _IsKeyboardServiceEnabled(); - void _ShowKeyboardServiceDisabledDialog(); - fire_and_forget _LoadErrorsDialogRoutine(); - fire_and_forget _ShowLoadWarningsDialogRoutine(); - fire_and_forget _RefreshThemeRoutine(); + void _ApplyLanguageSettingChange(); + void _RefreshThemeRoutine(); fire_and_forget _ApplyStartupTaskStateChange(); void _OnLoaded(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); diff --git a/src/cascadia/TerminalApp/ColorPickupFlyout.cpp b/src/cascadia/TerminalApp/ColorPickupFlyout.cpp index b1c15be48..ed1d20b83 100644 --- a/src/cascadia/TerminalApp/ColorPickupFlyout.cpp +++ b/src/cascadia/TerminalApp/ColorPickupFlyout.cpp @@ -96,7 +96,7 @@ namespace winrt::TerminalApp::implementation Hide(); } - void ColorPickupFlyout::ColorPicker_ColorChanged(const Windows::UI::Xaml::Controls::ColorPicker&, const Windows::UI::Xaml::Controls::ColorChangedEventArgs& args) + void ColorPickupFlyout::ColorPicker_ColorChanged(const Microsoft::UI::Xaml::Controls::ColorPicker&, const Microsoft::UI::Xaml::Controls::ColorChangedEventArgs& args) { _ColorSelectedHandlers(args.NewColor()); } diff --git a/src/cascadia/TerminalApp/ColorPickupFlyout.h b/src/cascadia/TerminalApp/ColorPickupFlyout.h index c73f68881..b124dfe43 100644 --- a/src/cascadia/TerminalApp/ColorPickupFlyout.h +++ b/src/cascadia/TerminalApp/ColorPickupFlyout.h @@ -12,7 +12,7 @@ namespace winrt::TerminalApp::implementation void ShowColorPickerButton_Click(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); void CustomColorButton_Click(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); void ClearColorButton_Click(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); - void ColorPicker_ColorChanged(const Windows::UI::Xaml::Controls::ColorPicker&, const Windows::UI::Xaml::Controls::ColorChangedEventArgs& args); + void ColorPicker_ColorChanged(const Microsoft::UI::Xaml::Controls::ColorPicker&, const Microsoft::UI::Xaml::Controls::ColorChangedEventArgs& args); WINRT_CALLBACK(ColorCleared, TerminalApp::ColorClearedArgs); WINRT_CALLBACK(ColorSelected, TerminalApp::ColorSelectedArgs); diff --git a/src/cascadia/TerminalApp/ColorPickupFlyout.xaml b/src/cascadia/TerminalApp/ColorPickupFlyout.xaml index 7674b357e..b7da875af 100644 --- a/src/cascadia/TerminalApp/ColorPickupFlyout.xaml +++ b/src/cascadia/TerminalApp/ColorPickupFlyout.xaml @@ -4,190 +4,200 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:TerminalApp" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:muxc="using:Microsoft.UI.Xaml.Controls" mc:Ignorable="d"> - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cascadia/TerminalSettingsEditor/Converters.idl b/src/cascadia/TerminalSettingsEditor/Converters.idl index 58fb27db0..89eb05791 100644 --- a/src/cascadia/TerminalSettingsEditor/Converters.idl +++ b/src/cascadia/TerminalSettingsEditor/Converters.idl @@ -39,6 +39,11 @@ namespace Microsoft.Terminal.Settings.Editor PercentageConverter(); }; + runtimeclass PercentageSignConverter : [default] Windows.UI.Xaml.Data.IValueConverter + { + PercentageSignConverter(); + }; + runtimeclass StringIsEmptyConverter : [default] Windows.UI.Xaml.Data.IValueConverter { StringIsEmptyConverter(); diff --git a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.cpp b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.cpp index 7556dc067..1c53db7dd 100644 --- a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.cpp +++ b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.cpp @@ -2,10 +2,12 @@ // Licensed under the MIT license. #include "pch.h" +#include "EnumEntry.h" #include "GlobalAppearance.h" #include "GlobalAppearance.g.cpp" #include "GlobalAppearancePageNavigationState.g.cpp" -#include "EnumEntry.h" + +#include using namespace winrt; using namespace winrt::Windows::UI::Xaml; @@ -16,6 +18,11 @@ using namespace winrt::Windows::Foundation::Collections; namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { + // For ComboBox an empty SelectedItem string denotes no selection. + // What we want instead is for "Use system language" to be selected by default. + // --> "und" is synonymous for "Use system language". + constexpr std::wstring_view systemLanguageTag{ L"und" }; + GlobalAppearance::GlobalAppearance() { InitializeComponent(); @@ -28,4 +35,139 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { _State = e.Parameter().as(); } + + winrt::hstring GlobalAppearance::LanguageDisplayConverter(const winrt::hstring& tag) + { + if (tag == systemLanguageTag) + { + return RS_(L"Globals_LanguageDefault"); + } + + winrt::Windows::Globalization::Language language{ tag }; + return language.NativeName(); + } + + // Returns the list of languages the user may override the application language with. + // The returned list are BCP 47 language tags like {"und", "en-US", "de-DE", "es-ES", ...}. + // "und" is short for "undefined" and is synonymous for "Use system language" in this code. + winrt::Windows::Foundation::Collections::IObservableVector GlobalAppearance::LanguageList() + { + if (_languageList) + { + return _languageList; + } + + // In order to return the language list this code does the following: + // [1] Get all possible languages we want to allow the user to choose. + // We have to acquire languages from multiple sources, creating duplicates. See below at [1]. + // [2] Sort languages by their ASCII tags, forcing the UI in a consistent/stable order. + // I wanted to sort the localized language names initially, but it turned out to be complex. + // [3] Remove potential duplicates in our language list from [1]. + // We don't want to have en-US twice in the list, do we? + // [4] Optionally remove unwanted language tags (like pseudo-localizations). + + std::vector tags; + + // [1]: + { + // ManifestLanguages contains languages the app ships with. + // + // Languages is a computed list that merges the ManifestLanguages with the + // user's ranked list of preferred languages taken from the system settings. + // As is tradition the API documentation is incomplete though, as it can also + // contain regional language variants. If our app supports en-US, but the user + // has en-GB or en-DE in their system's preferred language list, Languages will + // contain those as well, as they're variants from a supported language. We should + // allow a user to select those, as regional formattings can vary significantly. + const std::array tagSources{ + winrt::Windows::Globalization::ApplicationLanguages::ManifestLanguages(), + winrt::Windows::Globalization::ApplicationLanguages::Languages() + }; + + // tags will hold all the flattened results from tagSources. + // We resize() the vector to the proper size in order to efficiently GetMany() all items. + tags.resize(std::accumulate( + tagSources.begin(), + tagSources.end(), + // tags[0] will be "und" - the "Use system language" item + // tags[1..n] will contain tags from tagSources. + // --> totalTags is offset by 1 + 1, + [](uint32_t sum, const auto& v) -> uint32_t { + return sum + v.Size(); + })); + + // As per the function definition, the first item + // is always "Use system language" ("und"). + auto data = tags.data(); + *data++ = systemLanguageTag; + + // Finally GetMany() all the tags from tagSources. + for (const auto& v : tagSources) + { + const auto size = v.Size(); + v.GetMany(0, winrt::array_view(data, size)); + data += size; + } + } + + // NOTE: The size of tags is always >0, due to tags[0] being hardcoded to "und". + const auto tagsBegin = ++tags.begin(); + const auto tagsEnd = tags.end(); + + // [2]: + std::sort(tagsBegin, tagsEnd); + + // I'd love for both, std::unique and std::remove_if, to occur in a single loop, + // but the code turned out to be complex and even less maintainable, so I gave up. + { + // [3] part 1: + auto it = std::unique(tagsBegin, tagsEnd); + + // The qps- languages are useful for testing ("pseudo-localization"). + // --> Leave them in if debug features are enabled. + if (!_State.Globals().DebugFeaturesEnabled()) + { + // [4] part 1: + it = std::remove_if(tagsBegin, it, [](const winrt::hstring& tag) -> bool { + return til::starts_with(tag, L"qps-"); + }); + } + + // [3], [4] part 2 (completing the so called "erase-remove idiom"): + tags.erase(it, tagsEnd); + } + + _languageList = winrt::single_threaded_observable_vector(std::move(tags)); + return _languageList; + } + + winrt::Windows::Foundation::IInspectable GlobalAppearance::CurrentLanguage() + { + if (_currentLanguage.empty()) + { + _currentLanguage = winrt::Windows::Globalization::ApplicationLanguages::PrimaryLanguageOverride(); + if (_currentLanguage.empty()) + { + _currentLanguage = systemLanguageTag; + } + } + + return winrt::box_value(_currentLanguage); + } + + void GlobalAppearance::CurrentLanguage(const winrt::Windows::Foundation::IInspectable& tag) + { + _currentLanguage = winrt::unbox_value(tag); + + const auto globals = _State.Globals(); + if (_currentLanguage == systemLanguageTag) + { + globals.ClearLanguage(); + } + else + { + globals.Language(_currentLanguage); + } + } } diff --git a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.h b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.h index dddbf23f5..484fb1441 100644 --- a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.h +++ b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.h @@ -26,9 +26,24 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e); WINRT_PROPERTY(Editor::GlobalAppearancePageNavigationState, State, nullptr); - GETSET_BINDABLE_ENUM_SETTING(Theme, winrt::Windows::UI::Xaml::ElementTheme, State().Globals, Theme); GETSET_BINDABLE_ENUM_SETTING(TabWidthMode, winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, State().Globals, TabWidthMode); + + public: + // LanguageDisplayConverter maps the given BCP 47 tag to a localized string. + // For instance "en-US" produces "English (United States)", while "de-DE" produces + // "Deutsch (Deutschland)". This works independently of the user's locale. + static winrt::hstring LanguageDisplayConverter(const winrt::hstring& tag); + + winrt::Windows::Foundation::Collections::IObservableVector LanguageList(); + winrt::Windows::Foundation::IInspectable CurrentLanguage(); + void CurrentLanguage(const winrt::Windows::Foundation::IInspectable& tag); + + private: + std::vector _GetSupportedLanguageTags(); + + winrt::Windows::Foundation::Collections::IObservableVector _languageList{ nullptr }; + winrt::hstring _currentLanguage; }; } diff --git a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.idl b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.idl index 294f73022..8901b9934 100644 --- a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.idl +++ b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.idl @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import "EnumEntry.idl"; +import "EnumEntry.idl"; namespace Microsoft.Terminal.Settings.Editor { @@ -15,6 +15,10 @@ namespace Microsoft.Terminal.Settings.Editor GlobalAppearance(); GlobalAppearancePageNavigationState State { get; }; + static String LanguageDisplayConverter(String tag); + Windows.Foundation.Collections.IObservableVector LanguageList { get; }; + IInspectable CurrentLanguage; + IInspectable CurrentTheme; Windows.Foundation.Collections.IObservableVector ThemeList { get; }; diff --git a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml index 84c1408ae..7b33b45d0 100644 --- a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml +++ b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.xaml @@ -28,9 +28,21 @@ - - + + + + + + + + + + + + diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index fc256a9f4..69d52294e 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -65,13 +65,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // - settings - the new settings source // Return value: // - - fire_and_forget MainPage::UpdateSettings(Model::CascadiaSettings settings) + void MainPage::UpdateSettings(const Model::CascadiaSettings& settings) { _settingsSource = settings; _settingsClone = settings.Copy(); - co_await winrt::resume_foreground(Dispatcher()); - // Deduce information about the currently selected item IInspectable selectedItemTag; auto menuItems{ SettingsNav().MenuItems() }; @@ -83,34 +81,43 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } - // remove all profile-related NavViewItems by populating a std::vector - // with the ones we want to keep. - // NOTE: menuItems.Remove() causes an out-of-bounds crash. Using ReplaceAll() - // gets around this crash. - std::vector menuItemsSTL; - for (const auto& item : menuItems) - { - if (const auto& navViewItem{ item.try_as() }) - { - if (const auto& tag{ navViewItem.Tag() }) - { - if (tag.try_as()) + // We'll remove a bunch of items and iterate over it twice. + // --> Copy it into an STL vector to simplify our code and reduce COM overhead. + std::vector menuItemsSTL(menuItems.Size(), nullptr); + menuItems.GetMany(0, menuItemsSTL); + + // We want to refresh the list of profiles in the NavigationView. + // In order to add profiles we can use _InitializeProfilesList(); + // But before we can do that we have to remove existing profiles first of course. + // This "erase-remove" idiom will achieve just that. + menuItemsSTL.erase( + std::remove_if( + menuItemsSTL.begin(), + menuItemsSTL.end(), + [](const auto& item) -> bool { + if (const auto& navViewItem{ item.try_as() }) { - // don't add NavViewItem pointing to a Profile - continue; - } - else if (const auto& stringTag{ tag.try_as() }) - { - if (stringTag == addProfileTag) + if (const auto& tag{ navViewItem.Tag() }) { - // don't add the "Add Profile" item - continue; + if (tag.try_as()) + { + // remove NavViewItem pointing to a Profile + return true; + } + if (const auto& stringTag{ tag.try_as() }) + { + if (stringTag == addProfileTag) + { + // remove the "Add Profile" item + return true; + } + } } } - } - } - menuItemsSTL.emplace_back(item); - } + return false; + }), + menuItemsSTL.end()); + menuItems.ReplaceAll(menuItemsSTL); // Repopulate profile-related menu items @@ -123,7 +130,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // refresh the current page using the SelectedItem data we collected before the refresh if (selectedItemTag) { - for (const auto& item : menuItems) + for (const auto& item : menuItemsSTL) { if (const auto& menuItem{ item.try_as() }) { @@ -138,7 +145,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // found the one that was selected before the refresh SettingsNav().SelectedItem(item); _Navigate(*stringTag); - co_return; + return; } } } @@ -151,7 +158,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // found the one that was selected before the refresh SettingsNav().SelectedItem(item); _Navigate(*profileTag); - co_return; + return; } } } diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.h b/src/cascadia/TerminalSettingsEditor/MainPage.h index 677b7c6d1..401c2576e 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.h +++ b/src/cascadia/TerminalSettingsEditor/MainPage.h @@ -13,7 +13,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation MainPage() = delete; MainPage(const Model::CascadiaSettings& settings); - fire_and_forget UpdateSettings(Model::CascadiaSettings settings); + void UpdateSettings(const Model::CascadiaSettings& settings); void OpenJsonKeyDown(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& args); void OpenJsonTapped(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs const& args); diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj index de8f8435b..a3df59932 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj @@ -60,6 +60,9 @@ Converters.idl + + Converters.idl + Converters.idl @@ -167,6 +170,9 @@ Converters.idl + + Converters.idl + Converters.idl @@ -332,4 +338,4 @@ - + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters index ba44e4844..4273f715d 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters @@ -10,10 +10,16 @@ + + Converters + + + Converters + @@ -43,4 +49,4 @@ {00f725c8-41b4-40a8-995e-8ee2e49a4a4c} - + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/PercentageSignConverter.cpp b/src/cascadia/TerminalSettingsEditor/PercentageSignConverter.cpp new file mode 100644 index 000000000..a8acb84d1 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/PercentageSignConverter.cpp @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "PercentageSignConverter.h" +#include "PercentageSignConverter.g.cpp" + +using namespace winrt::Windows; +using namespace winrt::Windows::UI::Xaml; + +namespace winrt::Microsoft::Terminal::Settings::Editor::implementation +{ + Foundation::IInspectable PercentageSignConverter::Convert(Foundation::IInspectable const& value, + Windows::UI::Xaml::Interop::TypeName const& /* targetType */, + Foundation::IInspectable const& /* parameter */, + hstring const& /* language */) + { + const auto number{ winrt::unbox_value(value) }; + return winrt::box_value(to_hstring((int)number) + L"%"); + } + + Foundation::IInspectable PercentageSignConverter::ConvertBack(Foundation::IInspectable const& /*value*/, + Windows::UI::Xaml::Interop::TypeName const& /* targetType */, + Foundation::IInspectable const& /*parameter*/, + hstring const& /* language */) + { + throw hresult_not_implemented(); + } +} diff --git a/src/cascadia/TerminalSettingsEditor/PercentageSignConverter.h b/src/cascadia/TerminalSettingsEditor/PercentageSignConverter.h new file mode 100644 index 000000000..08b4f13b3 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/PercentageSignConverter.h @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "PercentageSignConverter.g.h" +#include "Utils.h" + +DECLARE_CONVERTER(winrt::Microsoft::Terminal::Settings::Editor, PercentageSignConverter); diff --git a/src/cascadia/TerminalSettingsEditor/Profiles.xaml b/src/cascadia/TerminalSettingsEditor/Profiles.xaml index 68c592d5b..3ec1ab719 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles.xaml +++ b/src/cascadia/TerminalSettingsEditor/Profiles.xaml @@ -35,6 +35,7 @@ + @@ -626,7 +627,7 @@ Value="{x:Bind State.Profile.BackgroundImageOpacity, Converter={StaticResource PercentageConverter}, Mode=TwoWay}" /> + Text="{Binding ElementName=BIOpacitySlider, Path=Value, Mode=OneWay, Converter={StaticResource PercentageSignConverter}}" /> @@ -664,7 +665,7 @@ Value="{x:Bind State.Profile.AcrylicOpacity, Converter={StaticResource PercentageConverter}, Mode=TwoWay}" /> + Text="{Binding ElementName=AcrylicOpacitySlider, Path=Value, Mode=OneWay, Converter={StaticResource PercentageSignConverter}}" /> diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index a42629596..495191792 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -215,6 +215,18 @@ Yellow This is the header for a control that lets the user select the yellow color for text displayed on the screen. + + Language + The header for a control allowing users to choose the app's language. + + + Sets an override for the app's preferred language. + A description explaining how this control changes the app's language. + + + Use system default + The app contains a control allowing users to choose the app's language. If this value is chosen, the language preference list of the system settings is used instead. + Always show tabs Header for a control to toggle if the app should always show the tabs (similar to a website browser). diff --git a/src/cascadia/TerminalSettingsEditor/pch.h b/src/cascadia/TerminalSettingsEditor/pch.h index df45f6bd2..4ee6261bf 100644 --- a/src/cascadia/TerminalSettingsEditor/pch.h +++ b/src/cascadia/TerminalSettingsEditor/pch.h @@ -20,21 +20,14 @@ #undef GetCurrentTime #endif -#include - -#include - -#include #include #include -#include -#include - +#include #include #include -#include #include #include +#include #include #include #include diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 433535dc5..a9ab094e7 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -29,7 +29,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } ActionMap::ActionMap() : - _NestedCommands{ single_threaded_map() } + _NestedCommands{ single_threaded_map() }, + _IterableCommands{ single_threaded_vector() } { } @@ -87,7 +88,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // populate _NameMapCache std::unordered_map nameMap{}; - _PopulateNameMapWithNestedCommands(nameMap); + _PopulateNameMapWithSpecialCommands(nameMap); _PopulateNameMapWithStandardCommands(nameMap); _NameMapCache = single_threaded_map(std::move(nameMap)); @@ -96,18 +97,19 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // Method Description: - // - Populates the provided nameMap with all of our nested commands and our parents nested commands - // - Performs a top-down approach by going to the root first, then recursively adding the nested commands layer-by-layer + // - Populates the provided nameMap with all of our special commands and our parent's special commands. + // - Special commands include nested and iterable commands. + // - Performs a top-down approach by going to the root first, then recursively adding the nested commands layer-by-layer. // Arguments: // - nameMap: the nameMap we're populating. This maps the name (hstring) of a command to the command itself. - void ActionMap::_PopulateNameMapWithNestedCommands(std::unordered_map& nameMap) const + void ActionMap::_PopulateNameMapWithSpecialCommands(std::unordered_map& nameMap) const { // Update NameMap with our parents. // Starting with this means we're doing a top-down approach. FAIL_FAST_IF(_parents.size() > 1); for (const auto& parent : _parents) { - parent->_PopulateNameMapWithNestedCommands(nameMap); + parent->_PopulateNameMapWithSpecialCommands(nameMap); } // Add NestedCommands to NameMap _after_ we handle our parents. @@ -125,6 +127,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation nameMap.erase(name); } } + + // Add IterableCommands to NameMap + for (const auto& cmd : _IterableCommands) + { + nameMap.insert_or_assign(cmd.Name(), cmd); + } } // Method Description: @@ -296,6 +304,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation actionMap->_NestedCommands.Insert(name, *(get_self(cmd)->Copy())); } + // copy _IterableCommands + for (const auto& cmd : _IterableCommands) + { + actionMap->_IterableCommands.Append(*(get_self(cmd)->Copy())); + } + // repeat this for each of our parents FAIL_FAST_IF(_parents.size() > 1); for (const auto& parent : _parents) @@ -336,6 +350,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return; } + // Handle iterable commands + if (cmdImpl->IterateOn() != ExpandCommandType::None) + { + _IterableCommands.Append(cmd); + return; + } + // General Case: // Add the new command to the KeyMap. // This map directs you to an entry in the ActionMap. diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index e33638cff..3435d120c 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -81,7 +81,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::optional _GetActionByID(const InternalActionID actionID) const; std::optional _GetActionByKeyChordInternal(Control::KeyChord const& keys) const; - void _PopulateNameMapWithNestedCommands(std::unordered_map& nameMap) const; + void _PopulateNameMapWithSpecialCommands(std::unordered_map& nameMap) const; void _PopulateNameMapWithStandardCommands(std::unordered_map& nameMap) const; void _PopulateKeyBindingMapWithStandardCommands(std::unordered_map& keyBindingsMap, std::unordered_set& unboundKeys) const; std::vector _GetCumulativeActions() const noexcept; @@ -94,6 +94,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Windows::Foundation::Collections::IMap _GlobalHotkeysCache{ nullptr }; Windows::Foundation::Collections::IMap _KeyBindingMapCache{ nullptr }; Windows::Foundation::Collections::IMap _NestedCommands{ nullptr }; + Windows::Foundation::Collections::IVector _IterableCommands{ nullptr }; std::unordered_map _KeyMap; std::unordered_map _ActionMap; diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index 1358c7865..5956bf757 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -60,34 +60,40 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { Json::Value actionList{ Json::ValueType::arrayValue }; - // Serialize all standard Command objects in the current layer - for (const auto& [_, cmd] : _ActionMap) - { - // Command serializes to an array of JSON objects. - // This is because a Command may have multiple key chords associated with it. - // The name and icon are only serialized in the first object. - // Example: - // { "name": "Custom Copy", "command": "copy", "keys": "ctrl+c" } - // { "command": "copy", "keys": "ctrl+shift+c" } - // { "command": "copy", "keys": "ctrl+ins" } + // Command serializes to an array of JSON objects. + // This is because a Command may have multiple key chords associated with it. + // The name and icon are only serialized in the first object. + // Example: + // { "name": "Custom Copy", "command": "copy", "keys": "ctrl+c" } + // { "command": "copy", "keys": "ctrl+shift+c" } + // { "command": "copy", "keys": "ctrl+ins" } + auto toJson = [&actionList](const Model::Command& cmd) { const auto cmdImpl{ winrt::get_self(cmd) }; const auto& cmdJsonArray{ cmdImpl->ToJson() }; for (const auto& cmdJson : cmdJsonArray) { actionList.append(cmdJson); } + }; + + // Serialize all standard Command objects in the current layer + for (const auto& [_, cmd] : _ActionMap) + { + toJson(cmd); } // Serialize all nested Command objects added in the current layer for (const auto& [_, cmd] : _NestedCommands) { - const auto cmdImpl{ winrt::get_self(cmd) }; - const auto& cmdJsonArray{ cmdImpl->ToJson() }; - for (const auto& cmdJson : cmdJsonArray) - { - actionList.append(cmdJson); - } + toJson(cmd); } + + // Serialize all iterable Command objects added in the current layer + for (const auto& cmd : _IterableCommands) + { + toJson(cmd); + } + return actionList; } diff --git a/src/cascadia/TerminalSettingsModel/AppearanceConfig.h b/src/cascadia/TerminalSettingsModel/AppearanceConfig.h index d0b08e9e2..cdcdc9613 100644 --- a/src/cascadia/TerminalSettingsModel/AppearanceConfig.h +++ b/src/cascadia/TerminalSettingsModel/AppearanceConfig.h @@ -16,7 +16,6 @@ Author(s): #pragma once -#include "pch.h" #include "AppearanceConfig.g.h" #include "JsonUtils.h" #include "../inc/cppwinrt_utils.h" diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index ee560b78d..dbdfd6e5c 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -394,10 +394,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { Json::Value cmdList{ Json::ValueType::arrayValue }; - if (_nestedCommand) + if (_nestedCommand || _IterateOn != ExpandCommandType::None) { - // handle nested command - // For nested commands, we can trust _originalJson to be correct. + // handle special commands + // For these, we can trust _originalJson to be correct. // In fact, we _need_ to use it here because we don't actually deserialize `iterateOn` // until we expand the command. cmdList.append(_originalJson); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index 39c370228..466031b05 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -26,6 +26,7 @@ static constexpr std::string_view InitialColsKey{ "initialCols" }; static constexpr std::string_view InitialPositionKey{ "initialPosition" }; static constexpr std::string_view CenterOnLaunchKey{ "centerOnLaunch" }; static constexpr std::string_view ShowTitleInTitlebarKey{ "showTerminalTitleInTitlebar" }; +static constexpr std::string_view LanguageKey{ "language" }; static constexpr std::string_view ThemeKey{ "theme" }; static constexpr std::string_view TabWidthModeKey{ "tabWidthMode" }; static constexpr std::string_view ShowTabsInTitlebarKey{ "showTabsInTitlebar" }; @@ -101,6 +102,7 @@ winrt::com_ptr GlobalAppSettings::Copy() const globals->_AlwaysShowTabs = _AlwaysShowTabs; globals->_ShowTitleInTitlebar = _ShowTitleInTitlebar; globals->_ConfirmCloseAllTabs = _ConfirmCloseAllTabs; + globals->_Language = _Language; globals->_Theme = _Theme; globals->_TabWidthMode = _TabWidthMode; globals->_ShowTabsInTitlebar = _ShowTabsInTitlebar; @@ -279,6 +281,8 @@ void GlobalAppSettings::LayerJson(const Json::Value& json) JsonUtils::GetValueForKey(json, LaunchModeKey, _LaunchMode); + JsonUtils::GetValueForKey(json, LanguageKey, _Language); + JsonUtils::GetValueForKey(json, ThemeKey, _Theme); JsonUtils::GetValueForKey(json, TabWidthModeKey, _TabWidthMode); @@ -393,6 +397,7 @@ Json::Value GlobalAppSettings::ToJson() const JsonUtils::SetValueForKey(json, WarnAboutLargePasteKey, _WarnAboutLargePaste); JsonUtils::SetValueForKey(json, WarnAboutMultiLinePasteKey, _WarnAboutMultiLinePaste); JsonUtils::SetValueForKey(json, LaunchModeKey, _LaunchMode); + JsonUtils::SetValueForKey(json, LanguageKey, _Language); JsonUtils::SetValueForKey(json, ThemeKey, _Theme); JsonUtils::SetValueForKey(json, TabWidthModeKey, _TabWidthMode); JsonUtils::SetValueForKey(json, SnapToGridOnResizeKey, _SnapToGridOnResize); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index f93bcd0e3..e11807b2a 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -65,6 +65,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::GlobalAppSettings, bool, AlwaysShowTabs, true); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, ShowTitleInTitlebar, true); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, ConfirmCloseAllTabs, true); + INHERITABLE_SETTING(Model::GlobalAppSettings, hstring, Language); INHERITABLE_SETTING(Model::GlobalAppSettings, winrt::Windows::UI::Xaml::ElementTheme, Theme, winrt::Windows::UI::Xaml::ElementTheme::Default); INHERITABLE_SETTING(Model::GlobalAppSettings, winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, TabWidthMode, winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode::Equal); INHERITABLE_SETTING(Model::GlobalAppSettings, bool, ShowTabsInTitlebar, true); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl index 9f3c8c745..fba59e1a4 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl @@ -43,6 +43,7 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_SETTING(Boolean, AlwaysShowTabs); INHERITABLE_SETTING(Boolean, ShowTitleInTitlebar); INHERITABLE_SETTING(Boolean, ConfirmCloseAllTabs); + INHERITABLE_SETTING(String, Language); INHERITABLE_SETTING(Windows.UI.Xaml.ElementTheme, Theme); INHERITABLE_SETTING(Microsoft.UI.Xaml.Controls.TabViewWidthMode, TabWidthMode); INHERITABLE_SETTING(Boolean, ShowTabsInTitlebar); diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 00fe42c04..753166bf1 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -594,6 +594,7 @@ void AppHost::_DispatchCommandline(winrt::Windows::Foundation::IInspectable send summonArgs.MoveToCurrentDesktop(false); summonArgs.DropdownDuration(0); summonArgs.ToMonitor(Remoting::MonitorBehavior::InPlace); + summonArgs.ToggleVisibility(false); // Do not toggle, just make visible. // Summon the window whenever we dispatch a commandline to it. This will // make it obvious when a new tab/pane is created in a window. _HandleSummon(sender, summonArgs); @@ -935,5 +936,6 @@ void AppHost::_SummonWindowRequested(const winrt::Windows::Foundation::IInspecta summonArgs.MoveToCurrentDesktop(false); summonArgs.DropdownDuration(0); summonArgs.ToMonitor(Remoting::MonitorBehavior::InPlace); + summonArgs.ToggleVisibility(false); // Do not toggle, just make visible. _HandleSummon(sender, summonArgs); } diff --git a/src/common.build.post.props b/src/common.build.post.props index 87416a898..e5bc2f5f3 100644 --- a/src/common.build.post.props +++ b/src/common.build.post.props @@ -64,4 +64,21 @@ + + + + OCCallFeatureFlagGenerator; + $(BuildDependsOn) + + + + + + + + + + $(SolutionDir)\bin\$(Configuration)\inc\TilFeatureStaging.h;%(ForcedIncludeFiles) + + diff --git a/src/features.xml b/src/features.xml new file mode 100644 index 000000000..7ad1db40c --- /dev/null +++ b/src/features.xml @@ -0,0 +1,52 @@ + + + + + Feature_ReceiveIncomingHandoff + OpenConsole should be able to receive incoming connections + AlwaysEnabled + + WindowsInbox + + + + + Feature_AttemptHandoff + conhost should try to hand connections over to OpenConsole + AlwaysDisabled + + WindowsInbox + + + + + Feature_ConhostDxEngine + Controls whether conhost supports the DX engine and the UseDx registry key + AlwaysEnabled + + WindowsInbox + + + + Feature_DxEngineShaderSupport + Controls whether the DX engine is built with shader support. + AlwaysEnabled + + WindowsInbox + + + + + Feature_UseNumpadEventsForClipboardInput + Controls whether the clipboard converter (and ConPTY InputStateMachine) uses Numpad events instead of UChar + AlwaysDisabled + + + WindowsInbox + + + diff --git a/src/host/exe/exemain.cpp b/src/host/exe/exemain.cpp index d7ff119ff..a85c8a313 100644 --- a/src/host/exe/exemain.cpp +++ b/src/host/exe/exemain.cpp @@ -9,7 +9,7 @@ #include "../interactivity/inc/ServiceLocator.hpp" #include "../inc/conint.h" -#ifndef __INSIDE_WINDOWS +#if TIL_FEATURE_RECEIVEINCOMINGHANDOFF_ENABLED #include "CConsoleHandoff.h" #endif @@ -247,7 +247,7 @@ int CALLBACK wWinMain( // messages going forward. // 7. The out-of-box `OpenConsole.exe` can then attempt to lookup and invoke a `CTerminalHandoff` to ask a registered // Terminal to become the UI. This OpenConsole.exe will put itself in PTY mode and let the Terminal handle user interaction. -#ifndef __INSIDE_WINDOWS +#if TIL_FEATURE_RECEIVEINCOMINGHANDOFF_ENABLED auto& module = OutOfProcModuleWithRegistrationFlag::Create(&_releaseNotifier); #endif @@ -264,7 +264,7 @@ int CALLBACK wWinMain( if (SUCCEEDED(hr)) { // Only try to register as a handoff target if we are NOT a part of Windows. -#ifndef __INSIDE_WINDOWS +#if TIL_FEATURE_RECEIVEINCOMINGHANDOFF_ENABLED bool defAppEnabled = false; if (args.ShouldRunAsComServer() && SUCCEEDED(Microsoft::Console::Internal::DefaultApp::CheckDefaultAppPolicy(defAppEnabled)) && defAppEnabled) { diff --git a/src/host/readDataCooked.cpp b/src/host/readDataCooked.cpp index c723dcae1..458f351b3 100644 --- a/src/host/readDataCooked.cpp +++ b/src/host/readDataCooked.cpp @@ -1029,7 +1029,7 @@ void COOKED_READ_DATA::SavePendingInput(const size_t index, const bool multiline LOG_IF_FAILED(_commandHistory->Add({ _backupLimit, StringLength / sizeof(wchar_t) }, WI_IsFlagSet(gci.Flags, CONSOLE_HISTORY_NODUP))); } - + Tracing::s_TraceCookedRead(_backupLimit); // check for alias diff --git a/src/host/srvinit.cpp b/src/host/srvinit.cpp index c5283dca4..bebec95b2 100644 --- a/src/host/srvinit.cpp +++ b/src/host/srvinit.cpp @@ -27,9 +27,9 @@ #include "../inc/conint.h" #include "../propslib/DelegationConfig.hpp" -#ifndef __INSIDE_WINDOWS +#if TIL_FEATURE_RECEIVEINCOMINGHANDOFF_ENABLED #include "ITerminalHandoff.h" -#endif // __INSIDE_WINDOWS +#endif // TIL_FEATURE_RECEIVEINCOMINGHANDOFF_ENABLED #pragma hdrstop @@ -367,9 +367,9 @@ HRESULT ConsoleCreateIoThread(_In_ HANDLE Server, [[maybe_unused]] PCONSOLE_API_MSG connectMessage) try { -#ifdef __INSIDE_WINDOWS +#if !TIL_FEATURE_RECEIVEINCOMINGHANDOFF_ENABLED return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); -#else // !__INSIDE_WINDOWS +#else // TIL_FEATURE_RECEIVEINCOMINGHANDOFF_ENABLED auto& g = ServiceLocator::LocateGlobals(); g.handoffTarget = true; @@ -444,7 +444,7 @@ try RETURN_IF_FAILED(consoleArgs.ParseCommandline()); return ConsoleCreateIoThread(Server, &consoleArgs, driverInputEvent, connectMessage); -#endif // __INSIDE_WINDOWS +#endif // TIL_FEATURE_RECEIVEINCOMINGHANDOFF_ENABLED } CATCH_RETURN() diff --git a/src/host/ut_host/ClipboardTests.cpp b/src/host/ut_host/ClipboardTests.cpp index 206559244..fb389757e 100644 --- a/src/host/ut_host/ClipboardTests.cpp +++ b/src/host/ut_host/ClipboardTests.cpp @@ -308,26 +308,29 @@ class ClipboardTests wstr.size()); std::deque expectedEvents; -#ifdef __INSIDE_WINDOWS - // Inside Windows, where numpad events are enabled, this generated numpad events. - // should be converted to: - // 1. left alt keydown - // 2. 1st numpad keydown - // 3. 1st numpad keyup - // 4. 2nd numpad keydown - // 5. 2nd numpad keyup - // 6. left alt keyup - expectedEvents.push_back({ TRUE, 1, VK_MENU, altScanCode, L'\0', LEFT_ALT_PRESSED }); - expectedEvents.push_back({ TRUE, 1, 0x66, 0x4D, L'\0', LEFT_ALT_PRESSED }); - expectedEvents.push_back({ FALSE, 1, 0x66, 0x4D, L'\0', LEFT_ALT_PRESSED }); - expectedEvents.push_back({ TRUE, 1, 0x63, 0x51, L'\0', LEFT_ALT_PRESSED }); - expectedEvents.push_back({ FALSE, 1, 0x63, 0x51, L'\0', LEFT_ALT_PRESSED }); - expectedEvents.push_back({ FALSE, 1, VK_MENU, altScanCode, wstr[0], 0 }); -#else - // Outside Windows, without numpad events, we just emit the key with a nonzero UnicodeChar - expectedEvents.push_back({ TRUE, 1, 0, 0, wstr[0], 0 }); - expectedEvents.push_back({ FALSE, 1, 0, 0, wstr[0], 0 }); -#endif + if constexpr (Feature_UseNumpadEventsForClipboardInput::IsEnabled()) + { + // Inside Windows, where numpad events are enabled, this generated numpad events. + // should be converted to: + // 1. left alt keydown + // 2. 1st numpad keydown + // 3. 1st numpad keyup + // 4. 2nd numpad keydown + // 5. 2nd numpad keyup + // 6. left alt keyup + expectedEvents.push_back({ TRUE, 1, VK_MENU, altScanCode, L'\0', LEFT_ALT_PRESSED }); + expectedEvents.push_back({ TRUE, 1, 0x66, 0x4D, L'\0', LEFT_ALT_PRESSED }); + expectedEvents.push_back({ FALSE, 1, 0x66, 0x4D, L'\0', LEFT_ALT_PRESSED }); + expectedEvents.push_back({ TRUE, 1, 0x63, 0x51, L'\0', LEFT_ALT_PRESSED }); + expectedEvents.push_back({ FALSE, 1, 0x63, 0x51, L'\0', LEFT_ALT_PRESSED }); + expectedEvents.push_back({ FALSE, 1, VK_MENU, altScanCode, wstr[0], 0 }); + } + else + { + // Outside Windows, without numpad events, we just emit the key with a nonzero UnicodeChar + expectedEvents.push_back({ TRUE, 1, 0, 0, wstr[0], 0 }); + expectedEvents.push_back({ FALSE, 1, 0, 0, wstr[0], 0 }); + } VERIFY_ARE_EQUAL(expectedEvents.size(), events.size()); diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index 6497cb6bc..f180d03e0 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -12,7 +12,7 @@ #include "../Settings.hpp" #include "../VtIo.hpp" -#ifndef __INSIDE_WINDOWS +#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED #include "../../renderer/dx/DxRenderer.hpp" #endif @@ -38,7 +38,7 @@ class Microsoft::Console::VirtualTerminal::VtIoTests TEST_METHOD(RendererDtorAndThread); -#ifndef __INSIDE_WINDOWS +#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED TEST_METHOD(RendererDtorAndThreadAndDx); #endif @@ -433,7 +433,7 @@ void VtIoTests::RendererDtorAndThread() } } -#ifndef __INSIDE_WINDOWS +#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED void VtIoTests::RendererDtorAndThreadAndDx() { Log::Comment(NoThrowString().Format( diff --git a/src/inc/LibraryIncludes.h b/src/inc/LibraryIncludes.h index 75bc23816..996f68d24 100644 --- a/src/inc/LibraryIncludes.h +++ b/src/inc/LibraryIncludes.h @@ -20,34 +20,35 @@ #include #include +#include #include +#include +#include +#include +#include +#include #include -#include -#include #include +#include +#include #include -#include #include +#include #include #include +#include +#include +#include +#include #include -#include #include +#include #include #include +#include +#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include // WIL #include diff --git a/src/inc/til.h b/src/inc/til.h index 9e8792b4c..509f02db9 100644 --- a/src/inc/til.h +++ b/src/inc/til.h @@ -19,7 +19,7 @@ #include "til/spsc.h" #include "til/coalesce.h" #include "til/replace.h" -#include "til/visualize_control_codes.h" +#include "til/string.h" #include "til/pmr.h" // Use keywords on TraceLogging providers to specify the category diff --git a/src/inc/til/visualize_control_codes.h b/src/inc/til/string.h similarity index 50% rename from src/inc/til/visualize_control_codes.h rename to src/inc/til/string.h index db5ae73d5..a60ae3659 100644 --- a/src/inc/til/visualize_control_codes.h +++ b/src/inc/til/string.h @@ -29,4 +29,23 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" { return visualize_control_codes(std::wstring{ str }); } + + template + constexpr bool starts_with(const std::basic_string_view str, const std::basic_string_view prefix) noexcept + { +#ifdef __cpp_lib_starts_ends_with +#error This code can be replaced in C++20, which natively supports .starts_with(). +#endif + return str.size() >= prefix.size() && Traits::compare(str.data(), prefix.data(), prefix.size()) == 0; + }; + + constexpr bool starts_with(const std::string_view str, const std::string_view prefix) noexcept + { + return starts_with<>(str, prefix); + }; + + constexpr bool starts_with(const std::wstring_view str, const std::wstring_view prefix) noexcept + { + return starts_with<>(str, prefix); + }; } diff --git a/src/interactivity/base/EventSynthesis.cpp b/src/interactivity/base/EventSynthesis.cpp index e72f80318..9d1c217d9 100644 --- a/src/interactivity/base/EventSynthesis.cpp +++ b/src/interactivity/base/EventSynthesis.cpp @@ -16,19 +16,9 @@ static constexpr WORD altScanCode = 0x38; static constexpr WORD leftShiftScanCode = 0x2A; -#ifdef __INSIDE_WINDOWS -// To reduce the risk of compatibility issues inside Windows, we're going to continue using the old -// version of GetQuickCharWidth to determine whether a character should be synthesized into numpad -// events. -static constexpr bool useNumpadEvents{ true }; -#else // !defined(__INSIDE_WINDOWS) -// In Terminal, however, we will *always* use normal key events (!) -static constexpr bool useNumpadEvents{ false }; -#endif // __INSIDE_WINDOWS - // Routine Description: // - naively determines the width of a UCS2 encoded wchar (with caveats noted above) -#pragma warning(suppress : 4505) // this function will be deleted if useNumpadEvents is false +#pragma warning(suppress : 4505) // this function will be deleted if numpad events are disabled static CodepointWidth GetQuickCharWidthLegacyForNumpadEventSynthesis(const wchar_t wch) noexcept { if ((0x1100 <= wch && wch <= 0x115f) // From Unicode 9.0, Hangul Choseong is wide @@ -62,7 +52,7 @@ std::deque> Microsoft::Console::Interactivity::CharToK if (keyState == invalidKey) { - if constexpr (useNumpadEvents) + if constexpr (Feature_UseNumpadEventsForClipboardInput::IsEnabled()) { // Determine DBCS character because these character does not know by VkKeyScan. // GetStringTypeW(CT_CTYPE3) & C3_ALPHA can determine all linguistic characters. However, this is diff --git a/src/interactivity/win32/window.cpp b/src/interactivity/win32/window.cpp index 8bd5d0ae4..75235fc94 100644 --- a/src/interactivity/win32/window.cpp +++ b/src/interactivity/win32/window.cpp @@ -29,7 +29,7 @@ #include "../../renderer/base/renderer.hpp" #include "../../renderer/gdi/gdirenderer.hpp" -#ifndef __INSIDE_WINDOWS +#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED #include "../../renderer/dx/DxRenderer.hpp" #else // Forward-declare this so we don't blow up later. @@ -217,7 +217,7 @@ void Window::_UpdateSystemMetrics() const [[maybe_unused]] DxEngine* pDxEngine = nullptr; try { -#ifndef __INSIDE_WINDOWS +#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED if (useDx) { pDxEngine = new DxEngine(); @@ -324,7 +324,7 @@ void Window::_UpdateSystemMetrics() const { _hWnd = hWnd; -#ifndef __INSIDE_WINDOWS +#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED if (useDx) { status = NTSTATUS_FROM_WIN32(HRESULT_CODE((pDxEngine->SetHwnd(hWnd)))); diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp index 2b148c68c..c94a7147e 100644 --- a/src/renderer/dx/DxRenderer.cpp +++ b/src/renderer/dx/DxRenderer.cpp @@ -194,7 +194,7 @@ _CompileShader( std::string target, std::string entry = "main") { -#ifdef __INSIDE_WINDOWS +#if !TIL_FEATURE_DXENGINESHADERSUPPORT_ENABLED THROW_HR(E_UNEXPECTED); return 0; #else diff --git a/src/renderer/dx/ScreenPixelShader.h b/src/renderer/dx/ScreenPixelShader.h index 0cbf185e2..fa5322057 100644 --- a/src/renderer/dx/ScreenPixelShader.h +++ b/src/renderer/dx/ScreenPixelShader.h @@ -1,6 +1,6 @@ #pragma once -#ifdef __INSIDE_WINDOWS +#if !TIL_FEATURE_DXENGINESHADERSUPPORT_ENABLED constexpr std::string_view retroPixelShaderString{ "" }; #else constexpr std::string_view retroPixelShaderString{ R"( diff --git a/src/renderer/dx/ScreenVertexShader.h b/src/renderer/dx/ScreenVertexShader.h index f834c18d2..c0a4c8eaf 100644 --- a/src/renderer/dx/ScreenVertexShader.h +++ b/src/renderer/dx/ScreenVertexShader.h @@ -1,6 +1,6 @@ #pragma once -#ifdef __INSIDE_WINDOWS +#if !TIL_FEATURE_DXENGINESHADERSUPPORT_ENABLED const char screenVertexShaderString[] = ""; #else const char screenVertexShaderString[] = R"( diff --git a/src/renderer/dx/precomp.h b/src/renderer/dx/precomp.h index d3d51af5f..9a09c25bb 100644 --- a/src/renderer/dx/precomp.h +++ b/src/renderer/dx/precomp.h @@ -15,11 +15,8 @@ #include -#include #include -#include #include -#include #include diff --git a/src/server/IoDispatchers.cpp b/src/server/IoDispatchers.cpp index c6009e9d4..a55419c17 100644 --- a/src/server/IoDispatchers.cpp +++ b/src/server/IoDispatchers.cpp @@ -146,7 +146,7 @@ static bool _shouldAttemptHandoff(const Globals& globals, const CONSOLE_INFORMATION& gci, CONSOLE_API_CONNECTINFO& cac) { -#ifndef __INSIDE_WINDOWS +#if !TIL_FEATURE_ATTEMPTHANDOFF_ENABLED UNREFERENCED_PARAMETER(globals); UNREFERENCED_PARAMETER(gci); diff --git a/tools/FeatureStagingSchema.xsd b/tools/FeatureStagingSchema.xsd new file mode 100644 index 000000000..3e6819244 --- /dev/null +++ b/tools/FeatureStagingSchema.xsd @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/Generate-FeatureStagingHeader.ps1 b/tools/Generate-FeatureStagingHeader.ps1 new file mode 100644 index 000000000..8206b7bb1 --- /dev/null +++ b/tools/Generate-FeatureStagingHeader.ps1 @@ -0,0 +1,206 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +################################################################################ +# This script generates a header describing which Terminal/Console features +# should be compiled-in, based on an XML document describing them. + +[CmdletBinding()] +Param( + [Parameter(Position=0, Mandatory=$True)] + [ValidateScript({ Test-Path $_ })] + [string]$Path, + + [ValidateSet("Dev", "Preview", "Release", "WindowsInbox")] + [string]$Branding = "Dev", + + [string]$BranchOverride = $Null, + + [string]$OutputPath +) + +Enum Stage { + AlwaysDisabled; + AlwaysEnabled; +} + +Function ConvertTo-FeatureStage([string]$stage) { + Switch($stage) { + "AlwaysEnabled" { [Stage]::AlwaysEnabled; Return } + "AlwaysDisabled" { [Stage]::AlwaysDisabled; Return } + } + Throw "Invalid feature stage $stage" +} + +Class Feature { + [string]$Name + [Stage]$Stage + [System.Collections.Generic.Dictionary[string, Stage]]$BranchTokenStages + [System.Collections.Generic.Dictionary[string, Stage]]$BrandingTokenStages + [bool]$DisabledReleaseToken + + Feature([System.Xml.XmlElement]$entry) { + $this.Name = $entry.name + $this.Stage = ConvertTo-FeatureStage $entry.stage + $this.BranchTokenStages = [System.Collections.Generic.Dictionary[string, Stage]]::new() + $this.BrandingTokenStages = [System.Collections.Generic.Dictionary[string, Stage]]::new() + $this.DisabledReleaseToken = $Null -Ne $entry.alwaysDisabledReleaseTokens + + ForEach ($b in $entry.alwaysDisabledBranchTokens.branchToken) { + $this.BranchTokenStages[$b] = [Stage]::AlwaysDisabled + } + + # AlwaysEnabled branches win over AlwaysDisabled branches + ForEach ($b in $entry.alwaysEnabledBranchTokens.branchToken) { + $this.BranchTokenStages[$b] = [Stage]::AlwaysEnabled + } + + ForEach ($b in $entry.alwaysDisabledBrandingTokens.brandingToken) { + $this.BrandingTokenStages[$b] = [Stage]::AlwaysDisabled + } + + # AlwaysEnabled brandings win over AlwaysDisabled brandings + ForEach ($b in $entry.alwaysEnabledBrandingTokens.brandingToken) { + $this.BrandingTokenStages[$b] = [Stage]::AlwaysEnabled + } + } + + [string] PreprocessorName() { + return "TIL_$($this.Name.ToUpper())_ENABLED" + } +} + +class FeatureComparer : System.Collections.Generic.IComparer[Feature] { + [int] Compare([Feature]$a, [Feature]$b) { + If ($a.Name -lt $b.Name) { + Return -1 + } ElseIf ($a.Name -gt $b.Name) { + Return 1 + } Else { + Return 0 + } + } +} + +Function Resolve-FinalFeatureStage { + Param( + [Feature]$Feature, + [string]$Branch, + [string]$Branding + ) + + # RELEASE=DISABLED wins all checks + # Then, branch match by most-specific branch + # Then, branding type (if no overriding branch match) + + If ($Branding -Eq "Release" -And $Feature.DisabledReleaseToken) { + [Stage]::AlwaysDisabled + Return + } + + If (-Not [String]::IsNullOrEmpty($Branch)) { + $lastMatchLen = 0 + $branchStage = $Null + ForEach ($branchToken in $Feature.BranchTokenStages.Keys) { + # Match the longest branch token -- it should be the most specific + If ($Branch -Like $branchToken -And $branchToken.Length -Gt $lastMatchLen) { + $lastMatchLen = $branchToken.Length + $branchStage = $Feature.BranchTokenStages[$branchToken] + } + } + If ($Null -Ne $branchStage) { + $branchStage + Return + } + } + + $BrandingStage = $Feature.BrandingTokenStages[$Branding] + If ($Null -Ne $BrandingStage) { + $BrandingStage + Return + } + + $Feature.Stage +} + +$ErrorActionPreference = "Stop" +$x = [xml](Get-Content $Path -EA:Stop) +$x.Schemas.Add('http://microsoft.com/TilFeatureStaging-Schema.xsd', (Resolve-Path (Join-Path $PSScriptRoot "FeatureStagingSchema.xsd")).Path) | Out-Null +$x.Validate($null) + +$featureComparer = [FeatureComparer]::new() +$features = [System.Collections.Generic.List[Feature]]::new(16) + +ForEach ($entry in $x.featureStaging.feature) { + $features.Add([Feature]::new($entry)) +} + +$features.Sort($featureComparer) + +$featureFinalStages = [System.Collections.Generic.Dictionary[string, Stage]]::new(16) + +$branch = $BranchOverride +If ([String]::IsNullOrEmpty($branch)) { + Try { + $branch = & git branch --show-current 2>$Null + } Catch { + Try { + $branch = & git rev-parse --abbrev-ref HEAD 2>$Null + } Catch { + Write-Verbose "Cannot determine current Git branch; skipping branch validation" + } + } +} + +ForEach ($feature in $features) { + $featureFinalStages[$feature.Name] = Resolve-FinalFeatureStage -Feature $feature -Branch $branch -Branding $Branding +} + +### CODE GENERATION + +$script:Output = "" +Function AddOutput($s) { + $script:Output += $s +} + +AddOutput @" +// THIS FILE IS AUTOMATICALLY GENERATED; DO NOT EDIT IT +// INPUT FILE: $Path + +"@ + +ForEach ($feature in $features) { + $stage = $featureFinalStages[$feature.Name] + + AddOutput @" +#define $($feature.PreprocessorName()) $(If ($stage -eq [Stage]::AlwaysEnabled) { "1" } Else { "0" }) + +"@ +} + +AddOutput @" + +#if defined(__cplusplus) + +"@ + +ForEach ($feature in $features) { + AddOutput @" +__pragma(detect_mismatch("ODR_violation_$($feature.PreprocessorName())_mismatch", "$($feature.Stage)")) +struct $($feature.Name) +{ + static constexpr bool IsEnabled() { return $($feature.PreprocessorName()) == 1; } +}; + +"@ +} + +AddOutput @" +#endif +"@ + +If ([String]::IsNullOrEmpty($OutputPath)) { + $script:Output +} Else { + Out-File -Encoding UTF8 -FilePath $OutputPath -InputObject $script:Output +}