Compare commits

..

39 Commits

Author SHA1 Message Date
Dustin L. Howett
9021900904 Migrate spelling-0.0.21 changes from main 2020-07-21 08:24:04 -05:00
Dustin L. Howett
f6b051945f Migrate spelling-0.0.19 changes from main 2020-07-21 08:24:04 -05:00
Mike Griese
52b05e065e None of these suggestions worked for me, I'm sure I'm off my rocker 2020-07-21 08:24:04 -05:00
Mike Griese
81a80257c9 It's monday, and I tried TODO this, but the call to
```c++
    auto nativePanel2 = panel.try_as<ISwapChainPanelNative2>();
```

keeps failing in the server, but succeeding in the window process. This doesn't make any sense to me.
2020-07-21 07:52:49 -05:00
Mike Griese
1ea9fc26c8 Get the island spawning a server 2020-07-17 14:16:06 -05:00
Mike Griese
e1d15105d7 add some swapchainpanels 2020-07-17 13:28:47 -05:00
Mike Griese
d321ec084c Hey, lets make sure to get some XAML in there 2020-07-17 13:17:41 -05:00
Mike Griese
f33c69d8b4 Add a scratch island project for testing 2020-07-17 13:01:07 -05:00
Michael Niksa
30b8335479 Use DComp surface handle for Swap Chain management. 2020-07-17 08:42:47 -05:00
Mike Griese
93b79fb23c Merge remote-tracking branch 'origin/master' into dev/migrie/oop-scratch-2 2020-07-17 08:42:19 -05:00
Dustin L. Howett
efb1fddb99 Convert most of our JSON deserializers to use type-based conversion (#6590)
This pull request converts the following JSON deserializers to use the
new JSON deserializer pattern:

* Profile
* Command
* ColorScheme
* Action/Args
* GlobalSettings
* CascadiaSettingsSerialization

This is the completion of a long-term JSON refactoring that makes our
parser and deserializer more type-safe and robust. We're finally able to
get rid of all our manual enum conversion code and unify JSON conversion
around _types_ instead of around _keys_.

I've introduced another file filled with template specializations,
TerminalSettingsSerializationHelpers.h, which comprises a single unit
that holds all of the JSON deserializers (and eventually serializers)
for every type that comes from TerminalApp or TerminalSettings.

I've also moved some types out of Profile and GlobalAppSettings into a
new SettingsTypes.h to improve settings locality.

This does to some extent constitute a breaking change for already-broken
settings. Instead of parsing "successfully" (where invalid values are
null or 0 or unknown or unset), deserialization will now fail when
there's a type mismatch. Because of that, some tests had to be removed.

While I was on a refactoring spree, I removed a number of helpless
helpers, like GetWstringFromJson (which converted a u8 string to an
hstring to make a wstring out of its data pointer :|) and
_ConvertJsonToBool.

In the future, we can make the error types more robust and give them
position and type information such that a conformant application can
display rich error information ("line 3 column 3, I expected a string,
you gave me an integer").

Closes #2550.
2020-07-17 01:31:09 +00:00
Dustin L. Howett
7bc5de613c version: bump to 1.3 on master 2020-07-16 18:18:33 -07:00
Dustin L. Howett
bcbe246a93 Update Cascadia Code to 2007.15 (#6958) 2020-07-16 18:14:57 -07:00
Dustin L. Howett
03e25f12e9 Move to the TerminalDependencies NuGet feed (#6954)
After we stood up our own NuGet feed in Azure blob storage, Azure DevOps
came out with a public artifact feed feature. I've migrated all our
packages over, and the only thing left to do is switch our project's
NuGet config to use it.

Fixes #6952
2020-07-16 17:33:23 -07:00
jtippet
7062a830b8 Smooth animation of command palette filtering (#6939)
The command palette is a ListView of commands.  As you type into the
search box, commands are added or removed from the ListView.  Currently,
each update is done by completely clearing the backing list, then adding
back any items that should be displayed.  However, this defeats the
ListView's built-in animations: upon every keystroke, ListView displays
its list-clearing animation, then animates the insertion of every item
that wasn't deleted.  This results in noticeable flickering.

This PR changes the update logic so that it updates the list using
(roughly) the minimum number of Insert and Remove calls, so the ListView
makes smoother transitions as you type.

I implemented it by keeping the existing code that builds the filtered
list, but I changed it to build into a scratch list.  Then I grafted on
a generic delta algorithm to make the real list look like the scratch
list.

To verify the delta algorithm, I tested all 360,000 permutations of
pairs of up to 5 element lists in a toy C# app.

## Validation
I'm not sure if my screen capture tool really caught all the flickering
here, but the screencasts below should give a rough idea of the
difference.  (All the flickering was becoming a nuisance while I was
testing out the HC changes.)

See the images in #6939 for more info.

Co-authored-by: Jeffrey Tippet <jtippet@microsoft.com>
2020-07-16 17:33:03 -07:00
Dan Thompson
53df6c7f96 Fix 'bcz exclusive' typo (#6938)
Without this fix, you'd end up with an empty target variable, and so
msbuild would complain and tell you to give it a target.
2020-07-16 16:58:48 -07:00
Michael Niksa
81b7e54659 Cache the viewport to make invalidation faster (#6918)
In `Renderer::TriggerRedraw`, the act of fetching the viewport from the
`pData` over and over is wasted time. We already have a cached variable
of the viewport that is updated on every scroll check (on
`TriggerScroll` and on `PaintFrame`.) Scrolling wouldn't be working
correctly if the clients weren't already notifying us that the viewport
has changed for scroll purposes, so we can just keep using that cached
value for the invalidation restriction to speed things up over fetching
it again.

## Validation Steps Performed
- Run `time cat big.txt`. Checked average time before/after, WPR traces
  before/after.

## PR Checklist
* [x] Closes perf itch
* [x] I work here
* [x] Manual test
* [x] Documentation irrelevant.
* [x] Schema irrelevant.
* [x] Am core contributor.
2020-07-16 21:46:10 +00:00
Michael Niksa
3255177dd0 Correct comment in this SPSC test as a quick follow up to merge. 2020-07-16 13:53:56 -07:00
Leonard Hecker
b62f5ea850 Added til::spsc, a lock-free, single-producer/-consumer FIFO queue (#6751)
## Summary of the Pull Request

This PR adds the `til::spsc` namespace, which implements a lock-free, single-producer, single-consumer FIFO queue ("channel"). The queue efficiently blocks the caller using Futexes if no data can be written to / read from the queue (e.g. using `WaitOnAddress` on Windows). Furthermore it allows batching of data and contains logic to signal the caller if the other side has been dropped/destructed.

## PR Checklist
* [ ] Closes #xxx
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] Tests added/passed
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [ ] Schema updated.
* [x] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

## Detailed Description of the Pull Request / Additional comments

`til::spsc::details::arc<T>` contains most of the queue's logic and as such has the relevant documentation for its design.

## Validation Steps Performed

The queue was tested on Windows, Linux and macOS using MSVC, gcc and llvm and each of their available runtime introspection utilities in order to ensure no race conditions or memory leaks occur.
2020-07-16 20:49:06 +00:00
Mike Griese
7b8806b1fe These notes about ISwapChainPanelNative2 are VERY IMPORTANT 2020-07-16 09:53:20 -05:00
Mike Griese
8c5041b2ae dead ends, all dead ends 2020-07-16 07:42:43 -05:00
Mike Griese
a511ab0bc7 Revert "I was thinking of making the HostClass a COM class by default, but this is horrible. Then, the client wouldn't be able to use it to get the winrt type. We want to use the winrt type in Lists in the client. So this won't work"
This reverts commit 501c47adb2.
2020-07-15 16:28:35 -05:00
Mike Griese
501c47adb2 I was thinking of making the HostClass a COM class by default, but this is horrible. Then, the client wouldn't be able to use it to get the winrt type. We want to use the winrt type in Lists in the client. So this won't work 2020-07-15 16:28:27 -05:00
Mike Griese
72121721f3 So this doesn't work because HostClass is fundamentally a WinRT type, not a CoClass. What if it _wasn't_ a runtimeclass? 2020-07-15 16:08:44 -05:00
Mike Griese
3e80b6a466 The server's lifetime is based on the lifetime of the first client it creates now. 2020-07-15 12:52:29 -05:00
Mike Griese
a80e1d3f73 this works neatly for having a HostManager for all the hosts, but the server eats the newline as well as the client, so I'm removing that bit of it 2020-07-15 12:04:20 -05:00
Mike Griese
f45df9a717 This ALSO works 2020-07-15 09:43:29 -05:00
Mike Griese
b37f69826e This experiment has been a wild success 2020-07-15 09:34:55 -05:00
Mike Griese
b2e76c1812 register a single instance of a class over the lifetime of the process?? 2020-07-14 17:01:48 -05:00
Mike Griese
2179e16e3d Mostly refactor these scratch projects to use our shared helpers 2020-07-14 16:53:17 -05:00
Mike Griese
fafc0e1316 Merge remote-tracking branch 'origin/master' into dev/migrie/oop-scratch-2 2020-07-14 14:53:47 -05:00
Mike Griese
ca6dff9f20 revert the XAML bits from the server 2020-07-14 14:18:48 -05:00
Mike Griese
186ff8f638 If you do this, the server will crash when it tries to instantiate the XAML button 2020-07-14 14:16:47 -05:00
Mike Griese
37810aac71 merge branch 'origin/master' 2020-07-14 10:29:54 -05:00
Mike Griese
33bc88b225 oh cool, I can make a ScratchClass 2020-07-10 15:38:04 -05:00
Mike Griese
d56137876e curious, I can get the IClosable, but not the ScratchInterface? 2020-07-10 15:23:58 -05:00
Mike Griese
f928d41917 this is all dead end 2020-07-10 12:02:29 -05:00
Mike Griese
e4cc3104ab Merge remote-tracking branch 'origin/master' into dev/migrie/oop-scratch-2 2020-07-10 10:12:36 -05:00
Mike Griese
0c10b4b265 Well, this actually kinda works. Probably horribly insecure 2020-06-16 17:02:42 -05:00
87 changed files with 5789 additions and 1817 deletions

View File

@@ -8,7 +8,7 @@
<!--<add key="Static Package Dependencies" value="dep\packages" />-->
<!-- Use our own NuGet Feed -->
<add key="Windows Terminal NuGet Feed" value="https://terminalnuget.blob.core.windows.net/feed/index.json" />
<add key="TerminalDependencies" value="https://pkgs.dev.azure.com/ms/terminal/_packaging/TerminalDependencies/nuget/v3/index.json" />
<!-- Internal NuGet feeds that may not be accessible outside Microsoft corporate network -->
<!--<add key="TAEF - internal" value="https://microsoft.pkgs.visualstudio.com/DefaultCollection/_packaging/Taef/nuget/v3/index.json" />

View File

@@ -87,8 +87,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Host.Tests.Feature", "src\h
ProjectSection(ProjectDependencies) = postProject
{18D09A24-8240-42D6-8CB6-236EEE820263} = {18D09A24-8240-42D6-8CB6-236EEE820263}
{FC802440-AD6A-4919-8F2C-7701F2B38D79} = {FC802440-AD6A-4919-8F2C-7701F2B38D79}
{9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B} = {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}
{58A03BB2-DF5A-4B66-91A0-7EF3BA01269A} = {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}
{9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B} = {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalParser.UnitTests", "src\terminal\parser\ut_parser\Parser.UnitTests.vcxproj", "{12144E07-FE63-4D33-9231-748B8D8C3792}"
@@ -314,6 +314,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfTerminalTestNetCore", "s
{84848BFA-931D-42CE-9ADF-01EE54DE7890} = {84848BFA-931D-42CE-9ADF-01EE54DE7890}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ScratchWinRTServer", "src\tools\ScratchWinRTServer\ScratchWinRTServer.vcxproj", "{D46D9547-F085-4645-B8F7-E8CD21559AB4}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ScratchWinRTClient", "src\tools\ScratchWinRTClient\ScratchWinRTClient.vcxproj", "{06382349-D62A-4C7D-A7D3-9CA817EAE092}"
ProjectSection(ProjectDependencies) = postProject
{D46D9547-F085-4645-B8F7-E8CD21559AB4} = {D46D9547-F085-4645-B8F7-E8CD21559AB4}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ScratchIsland", "src\tools\ScratchIsland\ScratchIsland.vcxproj", "{23a1f736-cd19-4196-980f-84bcd50cf783}"
ProjectSection(ProjectDependencies) = postProject
{06382349-D62A-4C7D-A7D3-9CA817EAE092} = {06382349-D62A-4C7D-A7D3-9CA817EAE092}
{D46D9547-F085-4645-B8F7-E8CD21559AB4} = {D46D9547-F085-4645-B8F7-E8CD21559AB4}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wt", "src\cascadia\wt\wt.vcxproj", "{506FD703-BAA7-4F6E-9361-64F550EC8FCA}"
EndProject
Global
@@ -1997,6 +2010,62 @@ Global
{1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Release|DotNet_x86Test.Build.0 = Release|x86
{1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Release|x64.ActiveCfg = Release|Any CPU
{1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Release|x86.ActiveCfg = Release|Any CPU
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.AuditMode|Any CPU.ActiveCfg = Release|x64
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.AuditMode|Any CPU.Build.0 = Release|x64
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.AuditMode|ARM64.ActiveCfg = Release|x64
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.AuditMode|ARM64.Build.0 = Release|x64
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.AuditMode|DotNet_x64Test.ActiveCfg = Release|x64
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.AuditMode|DotNet_x64Test.Build.0 = Release|x64
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.AuditMode|DotNet_x86Test.ActiveCfg = Release|x64
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.AuditMode|DotNet_x86Test.Build.0 = Release|x64
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.AuditMode|x64.ActiveCfg = Release|x64
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.AuditMode|x64.Build.0 = Release|x64
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.AuditMode|x86.ActiveCfg = Release|Win32
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.AuditMode|x86.Build.0 = Release|Win32
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.Debug|Any CPU.ActiveCfg = Debug|Win32
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.Debug|ARM64.ActiveCfg = Debug|Win32
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.Debug|x64.ActiveCfg = Debug|x64
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.Debug|x64.Build.0 = Debug|x64
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.Debug|x86.ActiveCfg = Debug|Win32
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.Debug|x86.Build.0 = Debug|Win32
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.Release|Any CPU.ActiveCfg = Release|Win32
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.Release|ARM64.ActiveCfg = Release|Win32
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.Release|DotNet_x64Test.ActiveCfg = Release|Win32
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.Release|DotNet_x86Test.ActiveCfg = Release|Win32
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.Release|x64.ActiveCfg = Release|x64
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.Release|x64.Build.0 = Release|x64
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.Release|x86.ActiveCfg = Release|Win32
{D46D9547-F085-4645-B8F7-E8CD21559AB4}.Release|x86.Build.0 = Release|Win32
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.AuditMode|Any CPU.ActiveCfg = Release|x64
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.AuditMode|Any CPU.Build.0 = Release|x64
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.AuditMode|ARM64.ActiveCfg = Release|x64
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.AuditMode|ARM64.Build.0 = Release|x64
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.AuditMode|DotNet_x64Test.ActiveCfg = Release|x64
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.AuditMode|DotNet_x64Test.Build.0 = Release|x64
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.AuditMode|DotNet_x86Test.ActiveCfg = Release|x64
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.AuditMode|DotNet_x86Test.Build.0 = Release|x64
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.AuditMode|x64.ActiveCfg = Release|x64
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.AuditMode|x64.Build.0 = Release|x64
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.AuditMode|x86.ActiveCfg = Release|Win32
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.AuditMode|x86.Build.0 = Release|Win32
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.Debug|Any CPU.ActiveCfg = Debug|Win32
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.Debug|ARM64.ActiveCfg = Debug|Win32
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.Debug|x64.ActiveCfg = Debug|x64
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.Debug|x64.Build.0 = Debug|x64
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.Debug|x86.ActiveCfg = Debug|Win32
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.Debug|x86.Build.0 = Debug|Win32
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.Release|Any CPU.ActiveCfg = Release|Win32
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.Release|ARM64.ActiveCfg = Release|Win32
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.Release|DotNet_x64Test.ActiveCfg = Release|Win32
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.Release|DotNet_x86Test.ActiveCfg = Release|Win32
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.Release|x64.ActiveCfg = Release|x64
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.Release|x64.Build.0 = Release|x64
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.Release|x86.ActiveCfg = Release|Win32
{06382349-D62A-4C7D-A7D3-9CA817EAE092}.Release|x86.Build.0 = Release|Win32
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
@@ -2024,6 +2093,37 @@ Global
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x64.Build.0 = Release|x64
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x86.ActiveCfg = Release|Win32
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x86.Build.0 = Release|Win32
{23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|Any CPU.ActiveCfg = Release|x64
{23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|Any CPU.Build.0 = Release|x64
{23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|ARM64.ActiveCfg = Release|x64
{23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|ARM64.Build.0 = Release|x64
{23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|DotNet_x64Test.ActiveCfg = Release|x64
{23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|DotNet_x64Test.Build.0 = Release|x64
{23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|DotNet_x86Test.ActiveCfg = Release|x64
{23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|DotNet_x86Test.Build.0 = Release|x64
{23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|x64.ActiveCfg = Release|x64
{23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|x64.Build.0 = Release|x64
{23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|x86.ActiveCfg = Release|Win32
{23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|x86.Build.0 = Release|Win32
{23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|Any CPU.ActiveCfg = Debug|Win32
{23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|ARM64.ActiveCfg = Debug|Win32
{23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32
{23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32
{23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|x64.ActiveCfg = Debug|x64
{23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|x64.Build.0 = Debug|x64
{23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|x86.ActiveCfg = Debug|Win32
{23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|x86.Build.0 = Debug|Win32
{23a1f736-cd19-4196-980f-84bcd50cf783}.Release|Any CPU.ActiveCfg = Release|Win32
{23a1f736-cd19-4196-980f-84bcd50cf783}.Release|ARM64.ActiveCfg = Release|Win32
{23a1f736-cd19-4196-980f-84bcd50cf783}.Release|DotNet_x64Test.ActiveCfg = Release|Win32
{23a1f736-cd19-4196-980f-84bcd50cf783}.Release|DotNet_x86Test.ActiveCfg = Release|Win32
{23a1f736-cd19-4196-980f-84bcd50cf783}.Release|x64.ActiveCfg = Release|x64
{23a1f736-cd19-4196-980f-84bcd50cf783}.Release|x64.Build.0 = Release|x64
{23a1f736-cd19-4196-980f-84bcd50cf783}.Release|x86.ActiveCfg = Release|Win32
{23a1f736-cd19-4196-980f-84bcd50cf783}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2104,6 +2204,9 @@ Global
{067F0A06-FCB7-472C-96E9-B03B54E8E18D} = {59840756-302F-44DF-AA47-441A9D673202}
{6BAE5851-50D5-4934-8D5E-30361A8A40F3} = {81C352DB-1818-45B7-A284-18E259F1CC87}
{1588FD7C-241E-4E7D-9113-43735F3E6BAD} = {59840756-302F-44DF-AA47-441A9D673202}
{D46D9547-F085-4645-B8F7-E8CD21559AB4} = {A10C4720-DCA4-4640-9749-67F4314F527C}
{06382349-D62A-4C7D-A7D3-9CA817EAE092} = {A10C4720-DCA4-4640-9749-67F4314F527C}
{23a1f736-cd19-4196-980f-84bcd50cf783} = {A10C4720-DCA4-4640-9749-67F4314F527C}
{506FD703-BAA7-4F6E-9361-64F550EC8FCA} = {59840756-302F-44DF-AA47-441A9D673202}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution

View File

@@ -5,7 +5,7 @@
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
<XesBaseYearForStoreVersion>2020</XesBaseYearForStoreVersion>
<VersionMajor>1</VersionMajor>
<VersionMinor>2</VersionMinor>
<VersionMinor>3</VersionMinor>
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
</PropertyGroup>
</Project>

Binary file not shown.

Binary file not shown.

View File

@@ -17,5 +17,5 @@ Please consult the [license](https://raw.githubusercontent.com/microsoft/cascadi
### Fonts Included
* Cascadia Code, Cascadia Mono (2007.01)
* from microsoft/cascadia-code@311cc603f30635da704b6a7d13050e245e61667b
* Cascadia Code, Cascadia Mono (2007.15)
* from microsoft/cascadia-code@2a54363b2c867f7ae811b9a034c0024cef67de96

View File

@@ -147,10 +147,8 @@ namespace TerminalAppLocalTests
{ "name": "command0", "command": { "action": "splitPane", "split": null } },
{ "name": "command1", "command": { "action": "splitPane", "split": "vertical" } },
{ "name": "command2", "command": { "action": "splitPane", "split": "horizontal" } },
{ "name": "command3", "command": { "action": "splitPane", "split": "none" } },
{ "name": "command4", "command": { "action": "splitPane" } },
{ "name": "command5", "command": { "action": "splitPane", "split": "auto" } },
{ "name": "command6", "command": { "action": "splitPane", "split": "foo" } }
{ "name": "command5", "command": { "action": "splitPane", "split": "auto" } }
])" };
const auto commands0Json = VerifyParseSucceeded(commands0String);
@@ -159,7 +157,7 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(0u, commands.size());
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(7u, commands.size());
VERIFY_ARE_EQUAL(5u, commands.size());
{
auto command = commands.at(L"command0");
@@ -191,16 +189,6 @@ namespace TerminalAppLocalTests
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
}
{
auto command = commands.at(L"command3");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
{
auto command = commands.at(L"command4");
VERIFY_IS_NOT_NULL(command);
@@ -221,16 +209,6 @@ namespace TerminalAppLocalTests
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
{
auto command = commands.at(L"command6");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
}
void CommandTests::TestResourceKeyName()
{

View File

@@ -323,10 +323,8 @@ namespace TerminalAppLocalTests
{ "keys": ["ctrl+c"], "command": { "action": "splitPane", "split": null } },
{ "keys": ["ctrl+d"], "command": { "action": "splitPane", "split": "vertical" } },
{ "keys": ["ctrl+e"], "command": { "action": "splitPane", "split": "horizontal" } },
{ "keys": ["ctrl+f"], "command": { "action": "splitPane", "split": "none" } },
{ "keys": ["ctrl+g"], "command": { "action": "splitPane" } },
{ "keys": ["ctrl+h"], "command": { "action": "splitPane", "split": "auto" } },
{ "keys": ["ctrl+i"], "command": { "action": "splitPane", "split": "foo" } }
{ "keys": ["ctrl+h"], "command": { "action": "splitPane", "split": "auto" } }
])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
@@ -335,7 +333,7 @@ namespace TerminalAppLocalTests
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(7u, appKeyBindings->_keyShortcuts.size());
VERIFY_ARE_EQUAL(5u, appKeyBindings->_keyShortcuts.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
@@ -364,15 +362,6 @@ namespace TerminalAppLocalTests
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('G') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
@@ -391,15 +380,6 @@ namespace TerminalAppLocalTests
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('I') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
}
void KeyBindingsTests::TestSetTabColorArgs()
@@ -407,7 +387,6 @@ namespace TerminalAppLocalTests
const std::string bindings0String{ R"([
{ "keys": ["ctrl+c"], "command": { "action": "setTabColor", "color": null } },
{ "keys": ["ctrl+d"], "command": { "action": "setTabColor", "color": "#123456" } },
{ "keys": ["ctrl+e"], "command": { "action": "setTabColor", "color": "thisStringObviouslyWontWork" } },
{ "keys": ["ctrl+f"], "command": "setTabColor" },
])" };
@@ -417,7 +396,7 @@ namespace TerminalAppLocalTests
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(4u, appKeyBindings->_keyShortcuts.size());
VERIFY_ARE_EQUAL(3u, appKeyBindings->_keyShortcuts.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
@@ -439,15 +418,6 @@ namespace TerminalAppLocalTests
// Remember that COLORREFs are actually BBGGRR order, while the string is in #RRGGBB order
VERIFY_ARE_EQUAL(static_cast<uint32_t>(til::color(0x563412)), realArgs.TabColor().Value());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_NULL(realArgs.TabColor());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);

View File

@@ -1431,10 +1431,6 @@ namespace TerminalAppLocalTests
{
"name": "profile3",
"closeOnExit": null
},
{
"name": "profile4",
"closeOnExit": { "clearly": "not a string" }
}
]
})" };
@@ -1449,7 +1445,6 @@ namespace TerminalAppLocalTests
// Unknown modes parse as "Graceful"
VERIFY_ARE_EQUAL(CloseOnExitMode::Graceful, settings._profiles[3].GetCloseOnExitMode());
VERIFY_ARE_EQUAL(CloseOnExitMode::Graceful, settings._profiles[4].GetCloseOnExitMode());
}
void SettingsTests::TestCloseOnExitCompatibilityShim()
{

View File

@@ -2,6 +2,9 @@
#include "ActionArgs.h"
#include "ActionAndArgs.h"
#include "ActionAndArgs.g.cpp"
#include "JsonUtils.h"
#include <LibraryResources.h>
static constexpr std::string_view CopyTextKey{ "copy" };
@@ -44,6 +47,8 @@ static constexpr std::string_view UnboundKey{ "unbound" };
namespace winrt::TerminalApp::implementation
{
using namespace ::TerminalApp;
// Specifically use a map here over an unordered_map. We want to be able to
// iterate over these entries in-order when we're serializing the keybindings.
// HERE BE DRAGONS:
@@ -183,11 +188,9 @@ namespace winrt::TerminalApp::implementation
}
else if (json.isObject())
{
const auto actionVal = json[JsonKey(ActionKey)];
if (actionVal.isString())
if (const auto actionString{ JsonUtils::GetValueForKey<std::optional<std::string>>(json, ActionKey) })
{
auto actionString = actionVal.asString();
action = GetActionFromString(actionString);
action = GetActionFromString(*actionString);
argsVal = json;
}
}
@@ -281,5 +284,4 @@ namespace winrt::TerminalApp::implementation
const auto found = GeneratedActionNames.find(_Action);
return found != GeneratedActionNames.end() ? found->second : L"";
}
}

View File

@@ -23,6 +23,8 @@
#include "JsonUtils.h"
#include "TerminalWarnings.h"
#include "TerminalSettingsSerializationHelpers.h"
// Notes on defining ActionArgs and ActionEventArgs:
// * All properties specific to an action should be defined as an ActionArgs
// class that implements IActionArgs
@@ -31,6 +33,7 @@
namespace winrt::TerminalApp::implementation
{
using namespace ::TerminalApp;
using FromJsonResult = std::tuple<winrt::TerminalApp::IActionArgs, std::vector<::TerminalApp::SettingsLoadWarnings>>;
struct ActionEventArgs : public ActionEventArgsT<ActionEventArgs>
@@ -73,26 +76,11 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<NewTerminalArgs>();
if (auto commandline{ json[JsonKey(CommandlineKey)] })
{
args->_Commandline = winrt::to_hstring(commandline.asString());
}
if (auto startingDirectory{ json[JsonKey(StartingDirectoryKey)] })
{
args->_StartingDirectory = winrt::to_hstring(startingDirectory.asString());
}
if (auto tabTitle{ json[JsonKey(TabTitleKey)] })
{
args->_TabTitle = winrt::to_hstring(tabTitle.asString());
}
if (auto index{ json[JsonKey(ProfileIndexKey)] })
{
args->_ProfileIndex = index.asInt();
}
if (auto profile{ json[JsonKey(ProfileKey)] })
{
args->_Profile = winrt::to_hstring(profile.asString());
}
JsonUtils::GetValueForKey(json, CommandlineKey, args->_Commandline);
JsonUtils::GetValueForKey(json, StartingDirectoryKey, args->_StartingDirectory);
JsonUtils::GetValueForKey(json, TabTitleKey, args->_TabTitle);
JsonUtils::GetValueForKey(json, ProfileIndexKey, args->_ProfileIndex);
JsonUtils::GetValueForKey(json, ProfileKey, args->_Profile);
return *args;
}
};
@@ -120,10 +108,7 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<CopyTextArgs>();
if (auto singleLine{ json[JsonKey(SingleLineKey)] })
{
args->_SingleLine = singleLine.asBool();
}
JsonUtils::GetValueForKey(json, SingleLineKey, args->_SingleLine);
return { *args, {} };
}
};
@@ -177,49 +162,11 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<SwitchToTabArgs>();
if (auto tabIndex{ json[JsonKey(TabIndexKey)] })
{
args->_TabIndex = tabIndex.asUInt();
}
JsonUtils::GetValueForKey(json, TabIndexKey, args->_TabIndex);
return { *args, {} };
}
};
// Possible Direction values
// TODO:GH#2550/#3475 - move these to a centralized deserializing place
static constexpr std::string_view LeftString{ "left" };
static constexpr std::string_view RightString{ "right" };
static constexpr std::string_view UpString{ "up" };
static constexpr std::string_view DownString{ "down" };
// Function Description:
// - Helper function for parsing a Direction from a string
// Arguments:
// - directionString: the string to attempt to parse
// Return Value:
// - The encoded Direction value, or Direction::None if it was an invalid string
static TerminalApp::Direction ParseDirection(const std::string& directionString)
{
if (directionString == LeftString)
{
return TerminalApp::Direction::Left;
}
else if (directionString == RightString)
{
return TerminalApp::Direction::Right;
}
else if (directionString == UpString)
{
return TerminalApp::Direction::Up;
}
else if (directionString == DownString)
{
return TerminalApp::Direction::Down;
}
// default behavior for invalid data
return TerminalApp::Direction::None;
};
struct ResizePaneArgs : public ResizePaneArgsT<ResizePaneArgs>
{
ResizePaneArgs() = default;
@@ -243,10 +190,7 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<ResizePaneArgs>();
if (auto directionString{ json[JsonKey(DirectionKey)] })
{
args->_Direction = ParseDirection(directionString.asString());
}
JsonUtils::GetValueForKey(json, DirectionKey, args->_Direction);
if (args->_Direction == TerminalApp::Direction::None)
{
return { nullptr, { ::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter } };
@@ -281,10 +225,7 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<MoveFocusArgs>();
if (auto directionString{ json[JsonKey(DirectionKey)] })
{
args->_Direction = ParseDirection(directionString.asString());
}
JsonUtils::GetValueForKey(json, DirectionKey, args->_Direction);
if (args->_Direction == TerminalApp::Direction::None)
{
return { nullptr, { ::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter } };
@@ -319,48 +260,11 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<AdjustFontSizeArgs>();
if (auto jsonDelta{ json[JsonKey(AdjustFontSizeDelta)] })
{
args->_Delta = jsonDelta.asInt();
}
JsonUtils::GetValueForKey(json, AdjustFontSizeDelta, args->_Delta);
return { *args, {} };
}
};
// Possible SplitState values
// TODO:GH#2550/#3475 - move these to a centralized deserializing place
static constexpr std::string_view VerticalKey{ "vertical" };
static constexpr std::string_view HorizontalKey{ "horizontal" };
static constexpr std::string_view AutomaticKey{ "auto" };
static TerminalApp::SplitState ParseSplitState(const std::string& stateString)
{
if (stateString == VerticalKey)
{
return TerminalApp::SplitState::Vertical;
}
else if (stateString == HorizontalKey)
{
return TerminalApp::SplitState::Horizontal;
}
else if (stateString == AutomaticKey)
{
return TerminalApp::SplitState::Automatic;
}
// default behavior for invalid data
return TerminalApp::SplitState::Automatic;
};
// Possible SplitType values
static constexpr std::string_view DuplicateKey{ "duplicate" };
static TerminalApp::SplitType ParseSplitModeState(const std::string& stateString)
{
if (stateString == DuplicateKey)
{
return TerminalApp::SplitType::Duplicate;
}
return TerminalApp::SplitType::Manual;
}
struct SplitPaneArgs : public SplitPaneArgsT<SplitPaneArgs>
{
SplitPaneArgs() = default;
@@ -391,48 +295,12 @@ namespace winrt::TerminalApp::implementation
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<SplitPaneArgs>();
args->_TerminalArgs = NewTerminalArgs::FromJson(json);
if (auto jsonStyle{ json[JsonKey(SplitKey)] })
{
args->_SplitStyle = ParseSplitState(jsonStyle.asString());
}
if (auto jsonStyle{ json[JsonKey(SplitModeKey)] })
{
args->_SplitMode = ParseSplitModeState(jsonStyle.asString());
}
JsonUtils::GetValueForKey(json, SplitKey, args->_SplitStyle);
JsonUtils::GetValueForKey(json, SplitModeKey, args->_SplitMode);
return { *args, {} };
}
};
// Possible SettingsTarget values
// TODO:GH#2550/#3475 - move these to a centralized deserializing place
static constexpr std::string_view SettingsFileString{ "settingsFile" };
static constexpr std::string_view DefaultsFileString{ "defaultsFile" };
static constexpr std::string_view AllFilesString{ "allFiles" };
// Function Description:
// - Helper function for parsing a SettingsTarget from a string
// Arguments:
// - targetString: the string to attempt to parse
// Return Value:
// - The encoded SettingsTarget value, or SettingsTarget::SettingsFile if it was an invalid string
static TerminalApp::SettingsTarget ParseSettingsTarget(const std::string& targetString)
{
if (targetString == SettingsFileString)
{
return TerminalApp::SettingsTarget::SettingsFile;
}
else if (targetString == DefaultsFileString)
{
return TerminalApp::SettingsTarget::DefaultsFile;
}
else if (targetString == AllFilesString)
{
return TerminalApp::SettingsTarget::AllFiles;
}
// default behavior for invalid data
return TerminalApp::SettingsTarget::SettingsFile;
};
struct OpenSettingsArgs : public OpenSettingsArgsT<OpenSettingsArgs>
{
OpenSettingsArgs() = default;
@@ -456,10 +324,7 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<OpenSettingsArgs>();
if (auto targetString{ json[JsonKey(TargetKey)] })
{
args->_Target = ParseSettingsTarget(targetString.asString());
}
JsonUtils::GetValueForKey(json, TargetKey, args->_Target);
return { *args, {} };
}
};
@@ -487,16 +352,10 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<SetTabColorArgs>();
std::optional<til::color> temp;
try
if (const auto temp{ JsonUtils::GetValueForKey<std::optional<til::color>>(json, ColorKey) })
{
::TerminalApp::JsonUtils::GetOptionalColor(json, ColorKey, temp);
if (temp.has_value())
{
args->_TabColor = static_cast<uint32_t>(temp.value());
}
args->_TabColor = static_cast<uint32_t>(*temp);
}
CATCH_LOG();
return { *args, {} };
}
};
@@ -524,10 +383,7 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<RenameTabArgs>();
if (auto title{ json[JsonKey(TitleKey)] })
{
args->_Title = winrt::to_hstring(title.asString());
}
JsonUtils::GetValueForKey(json, TitleKey, args->_Title);
return { *args, {} };
}
};

View File

@@ -249,9 +249,9 @@ void CascadiaSettings::_LoadDynamicProfiles()
const auto disabledProfileSources = CascadiaSettings::_GetDisabledProfileSourcesJsonObject(_userSettings);
if (disabledProfileSources.isArray())
{
for (const auto& ns : disabledProfileSources)
for (const auto& json : disabledProfileSources)
{
ignoredNamespaces.emplace(GetWstringFromJson(ns));
ignoredNamespaces.emplace(JsonUtils::GetValue<std::wstring>(json));
}
}

View File

@@ -105,9 +105,9 @@ ColorScheme ColorScheme::FromJson(const Json::Value& json)
// - true iff the json object has the same `name` as we do.
bool ColorScheme::ShouldBeLayered(const Json::Value& json) const
{
if (const auto name{ json[JsonKey(NameKey)] })
std::wstring nameFromJson{};
if (JsonUtils::GetValueForKey(json, NameKey, nameFromJson))
{
const auto nameFromJson = GetWstringFromJson(name);
return nameFromJson == _schemeName;
}
return false;
@@ -125,39 +125,16 @@ bool ColorScheme::ShouldBeLayered(const Json::Value& json) const
// <none>
void ColorScheme::LayerJson(const Json::Value& json)
{
if (auto name{ json[JsonKey(NameKey)] })
{
_schemeName = winrt::to_hstring(name.asString());
}
if (auto fgString{ json[JsonKey(ForegroundKey)] })
{
const auto color = Utils::ColorFromHexString(fgString.asString());
_defaultForeground = color;
}
if (auto bgString{ json[JsonKey(BackgroundKey)] })
{
const auto color = Utils::ColorFromHexString(bgString.asString());
_defaultBackground = color;
}
if (auto sbString{ json[JsonKey(SelectionBackgroundKey)] })
{
const auto color = Utils::ColorFromHexString(sbString.asString());
_selectionBackground = color;
}
if (auto sbString{ json[JsonKey(CursorColorKey)] })
{
const auto color = Utils::ColorFromHexString(sbString.asString());
_cursorColor = color;
}
JsonUtils::GetValueForKey(json, NameKey, _schemeName);
JsonUtils::GetValueForKey(json, ForegroundKey, _defaultForeground);
JsonUtils::GetValueForKey(json, BackgroundKey, _defaultBackground);
JsonUtils::GetValueForKey(json, SelectionBackgroundKey, _selectionBackground);
JsonUtils::GetValueForKey(json, CursorColorKey, _cursorColor);
int i = 0;
for (const auto& current : TableColors)
{
if (auto str{ json[JsonKey(current)] })
{
const auto color = Utils::ColorFromHexString(str.asString());
_table.at(i) = color;
}
JsonUtils::GetValueForKey(json, current, _table.at(i));
i++;
}
}
@@ -200,11 +177,7 @@ til::color ColorScheme::GetCursorColor() const noexcept
// - the name of the color scheme represented by `json` as a std::wstring optional
// i.e. the value of the `name` property.
// - returns std::nullopt if `json` doesn't have the `name` property
std::optional<std::wstring> TerminalApp::ColorScheme::GetNameFromJson(const Json::Value& json)
std::optional<std::wstring> ColorScheme::GetNameFromJson(const Json::Value& json)
{
if (const auto name{ json[JsonKey(NameKey)] })
{
return GetWstringFromJson(name);
}
return std::nullopt;
return JsonUtils::GetValueForKey<std::optional<std::wstring>>(json, NameKey);
}

View File

@@ -7,10 +7,12 @@
#include "Utils.h"
#include "ActionAndArgs.h"
#include "JsonUtils.h"
#include <LibraryResources.h>
using namespace winrt::Microsoft::Terminal::Settings;
using namespace winrt::TerminalApp;
using namespace ::TerminalApp;
static constexpr std::string_view NameKey{ "name" };
static constexpr std::string_view IconPathKey{ "iconPath" };
@@ -35,25 +37,17 @@ namespace winrt::TerminalApp::implementation
{
if (name.isObject())
{
try
if (const auto resourceKey{ JsonUtils::GetValueForKey<std::optional<std::wstring>>(name, "key") })
{
if (const auto keyJson{ name[JsonKey("key")] })
if (HasLibraryResourceWithName(*resourceKey))
{
// Make sure the key is present before we try
// loading it. Otherwise we'll crash
const auto resourceKey = GetWstringFromJson(keyJson);
if (HasLibraryResourceWithName(resourceKey))
{
return GetLibraryResourceString(resourceKey);
}
return GetLibraryResourceString(*resourceKey);
}
}
CATCH_LOG();
}
else if (name.isString())
{
auto nameStr = name.asString();
return winrt::to_hstring(nameStr);
return JsonUtils::GetValue<winrt::hstring>(name);
}
}

View File

@@ -271,16 +271,17 @@ namespace winrt::TerminalApp::implementation
};
// Method Description:
// - Update our list of filtered actions to reflect the current contents of
// - Produce a list of filtered actions to reflect the current contents of
// the input box. For more details on which commands will be displayed,
// see `_getWeight`.
// Arguments:
// - <none>
// - A collection that will receive the filtered actions
// Return Value:
// - <none>
void CommandPalette::_updateFilteredActions()
std::vector<winrt::TerminalApp::Command> CommandPalette::_collectFilteredActions()
{
_filteredActions.Clear();
std::vector<winrt::TerminalApp::Command> actions;
auto searchText = _searchBox().Text();
const bool addAll = searchText.empty();
@@ -303,10 +304,10 @@ namespace winrt::TerminalApp::implementation
for (auto action : sortedCommands)
{
_filteredActions.Append(action);
actions.push_back(action);
}
return;
return actions;
}
// Here, there was some filter text.
@@ -343,7 +344,56 @@ namespace winrt::TerminalApp::implementation
{
auto top = heap.top();
heap.pop();
_filteredActions.Append(top.command);
actions.push_back(top.command);
}
return actions;
}
// Method Description:
// - Update our list of filtered actions to reflect the current contents of
// the input box. For more details on which commands will be displayed,
// see `_getWeight`.
// Arguments:
// - <none>
// Return Value:
// - <none>
void CommandPalette::_updateFilteredActions()
{
auto actions = _collectFilteredActions();
// Make _filteredActions look identical to actions, using only Insert and Remove.
// This allows WinUI to nicely animate the ListView as it changes.
for (uint32_t i = 0; i < _filteredActions.Size() && i < actions.size(); i++)
{
for (uint32_t j = i; j < _filteredActions.Size(); j++)
{
if (_filteredActions.GetAt(j) == actions[i])
{
for (uint32_t k = i; k < j; k++)
{
_filteredActions.RemoveAt(i);
}
break;
}
}
if (_filteredActions.GetAt(i) != actions[i])
{
_filteredActions.InsertAt(i, actions[i]);
}
}
// Remove any extra trailing items from the destination
while (_filteredActions.Size() > actions.size())
{
_filteredActions.RemoveAtEnd();
}
// Add any extra trailing items from the source
while (_filteredActions.Size() < actions.size())
{
_filteredActions.Append(actions[_filteredActions.Size()]);
}
}

View File

@@ -37,6 +37,7 @@ namespace winrt::TerminalApp::implementation
void _selectNextItem(const bool moveDown);
void _updateFilteredActions();
std::vector<winrt::TerminalApp::Command> _collectFilteredActions();
static int _getWeight(const winrt::hstring& searchText, const winrt::hstring& name);
void _close();

View File

@@ -7,11 +7,11 @@
#include "../../inc/DefaultSettings.h"
#include "Utils.h"
#include "JsonUtils.h"
#include "TerminalSettingsSerializationHelpers.h"
using namespace TerminalApp;
using namespace winrt::Microsoft::Terminal::Settings;
using namespace winrt::TerminalApp;
using namespace winrt::Windows::Data::Json;
using namespace winrt::Windows::UI::Xaml;
using namespace ::Microsoft::Console;
using namespace winrt::Microsoft::UI::Xaml::Controls;
@@ -44,21 +44,6 @@ static constexpr std::string_view ForceFullRepaintRenderingKey{ "experimental.re
static constexpr std::string_view SoftwareRenderingKey{ "experimental.rendering.software" };
static constexpr std::string_view ForceVTInputKey{ "experimental.input.forceVT" };
// Launch mode values
static constexpr std::wstring_view DefaultLaunchModeValue{ L"default" };
static constexpr std::wstring_view MaximizedLaunchModeValue{ L"maximized" };
static constexpr std::wstring_view FullscreenLaunchModeValue{ L"fullscreen" };
// Tab Width Mode values
static constexpr std::wstring_view EqualTabWidthModeValue{ L"equal" };
static constexpr std::wstring_view TitleLengthTabWidthModeValue{ L"titleLength" };
static constexpr std::wstring_view TitleLengthCompactModeValue{ L"compact" };
// Theme values
static constexpr std::wstring_view LightThemeValue{ L"light" };
static constexpr std::wstring_view DarkThemeValue{ L"dark" };
static constexpr std::wstring_view SystemThemeValue{ L"system" };
#ifdef _DEBUG
static constexpr bool debugFeaturesDefault{ true };
#else
@@ -149,66 +134,51 @@ GlobalAppSettings GlobalAppSettings::FromJson(const Json::Value& json)
void GlobalAppSettings::LayerJson(const Json::Value& json)
{
if (auto defaultProfile{ json[JsonKey(DefaultProfileKey)] })
{
_unparsedDefaultProfile.emplace(GetWstringFromJson(defaultProfile));
}
JsonUtils::GetValueForKey(json, DefaultProfileKey, _unparsedDefaultProfile);
JsonUtils::GetBool(json, AlwaysShowTabsKey, _AlwaysShowTabs);
JsonUtils::GetValueForKey(json, AlwaysShowTabsKey, _AlwaysShowTabs);
JsonUtils::GetBool(json, ConfirmCloseAllKey, _ConfirmCloseAllTabs);
JsonUtils::GetValueForKey(json, ConfirmCloseAllKey, _ConfirmCloseAllTabs);
JsonUtils::GetInt(json, InitialRowsKey, _InitialRows);
JsonUtils::GetValueForKey(json, InitialRowsKey, _InitialRows);
JsonUtils::GetInt(json, InitialColsKey, _InitialCols);
JsonUtils::GetValueForKey(json, InitialColsKey, _InitialCols);
if (auto initialPosition{ json[JsonKey(InitialPositionKey)] })
{
_ParseInitialPosition(initialPosition.asString(), _InitialPosition);
}
JsonUtils::GetValueForKey(json, InitialPositionKey, _InitialPosition);
JsonUtils::GetBool(json, ShowTitleInTitlebarKey, _ShowTitleInTitlebar);
JsonUtils::GetValueForKey(json, ShowTitleInTitlebarKey, _ShowTitleInTitlebar);
JsonUtils::GetBool(json, ShowTabsInTitlebarKey, _ShowTabsInTitlebar);
JsonUtils::GetValueForKey(json, ShowTabsInTitlebarKey, _ShowTabsInTitlebar);
JsonUtils::GetWstring(json, WordDelimitersKey, _WordDelimiters);
JsonUtils::GetValueForKey(json, WordDelimitersKey, _WordDelimiters);
JsonUtils::GetBool(json, CopyOnSelectKey, _CopyOnSelect);
JsonUtils::GetValueForKey(json, CopyOnSelectKey, _CopyOnSelect);
JsonUtils::GetBool(json, CopyFormattingKey, _CopyFormatting);
JsonUtils::GetValueForKey(json, CopyFormattingKey, _CopyFormatting);
JsonUtils::GetBool(json, WarnAboutLargePasteKey, _WarnAboutLargePaste);
JsonUtils::GetValueForKey(json, WarnAboutLargePasteKey, _WarnAboutLargePaste);
JsonUtils::GetBool(json, WarnAboutMultiLinePasteKey, _WarnAboutMultiLinePaste);
JsonUtils::GetValueForKey(json, WarnAboutMultiLinePasteKey, _WarnAboutMultiLinePaste);
if (auto launchMode{ json[JsonKey(LaunchModeKey)] })
{
_LaunchMode = _ParseLaunchMode(GetWstringFromJson(launchMode));
}
JsonUtils::GetValueForKey(json, LaunchModeKey, _LaunchMode);
if (auto theme{ json[JsonKey(ThemeKey)] })
{
_Theme = _ParseTheme(GetWstringFromJson(theme));
}
JsonUtils::GetValueForKey(json, ThemeKey, _Theme);
if (auto tabWidthMode{ json[JsonKey(TabWidthModeKey)] })
{
_TabWidthMode = _ParseTabWidthMode(GetWstringFromJson(tabWidthMode));
}
JsonUtils::GetValueForKey(json, TabWidthModeKey, _TabWidthMode);
JsonUtils::GetBool(json, SnapToGridOnResizeKey, _SnapToGridOnResize);
JsonUtils::GetValueForKey(json, SnapToGridOnResizeKey, _SnapToGridOnResize);
JsonUtils::GetBool(json, ForceFullRepaintRenderingKey, _ForceFullRepaintRendering);
// GetValueForKey will only override the current value if the key exists
JsonUtils::GetValueForKey(json, DebugFeaturesKey, _DebugFeaturesEnabled);
JsonUtils::GetBool(json, SoftwareRenderingKey, _SoftwareRendering);
JsonUtils::GetBool(json, ForceVTInputKey, _ForceVTInput);
JsonUtils::GetValueForKey(json, ForceFullRepaintRenderingKey, _ForceFullRepaintRendering);
// GetBool will only override the current value if the key exists
JsonUtils::GetBool(json, DebugFeaturesKey, _DebugFeaturesEnabled);
JsonUtils::GetValueForKey(json, SoftwareRenderingKey, _SoftwareRendering);
JsonUtils::GetValueForKey(json, ForceVTInputKey, _ForceVTInput);
JsonUtils::GetBool(json, EnableStartupTaskKey, _StartOnUserLogin);
JsonUtils::GetValueForKey(json, EnableStartupTaskKey, _StartOnUserLogin);
JsonUtils::GetBool(json, AlwaysOnTopKey, _AlwaysOnTop);
JsonUtils::GetValueForKey(json, AlwaysOnTopKey, _AlwaysOnTop);
// This is a helper lambda to get the keybindings and commands out of both
// and array of objects. We'll use this twice, once on the legacy
@@ -236,116 +206,6 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
parseBindings(BindingsKey);
}
// Method Description:
// - Helper function for converting a user-specified cursor style corresponding
// CursorStyle enum value
// Arguments:
// - themeString: The string value from the settings file to parse
// Return Value:
// - The corresponding enum value which maps to the string provided by the user
ElementTheme GlobalAppSettings::_ParseTheme(const std::wstring& themeString) noexcept
{
if (themeString == LightThemeValue)
{
return ElementTheme::Light;
}
else if (themeString == DarkThemeValue)
{
return ElementTheme::Dark;
}
// default behavior for invalid data or SystemThemeValue
return ElementTheme::Default;
}
// Method Description:
// - Helper function for converting the initial position string into
// 2 coordinate values. We allow users to only provide one coordinate,
// thus, we use comma as the separator:
// (100, 100): standard input string
// (, 100), (100, ): if a value is missing, we set this value as a default
// (,): both x and y are set to default
// (abc, 100): if a value is not valid, we treat it as default
// (100, 100, 100): we only read the first two values, this is equivalent to (100, 100)
// Arguments:
// - initialPosition: the initial position string from json
// ret: reference to a struct whose optionals will be populated
// Return Value:
// - None
void GlobalAppSettings::_ParseInitialPosition(const std::string& initialPosition,
LaunchPosition& ret) noexcept
{
static constexpr char singleCharDelim = ',';
std::stringstream tokenStream(initialPosition);
std::string token;
uint8_t initialPosIndex = 0;
// Get initial position values till we run out of delimiter separated values in the stream
// or we hit max number of allowable values (= 2)
// Non-numeral values or empty string will be caught as exception and we do not assign them
for (; std::getline(tokenStream, token, singleCharDelim) && (initialPosIndex < 2); initialPosIndex++)
{
try
{
int32_t position = std::stoi(token);
if (initialPosIndex == 0)
{
ret.x.emplace(position);
}
if (initialPosIndex == 1)
{
ret.y.emplace(position);
}
}
catch (...)
{
// Do nothing
}
}
}
// Method Description:
// - Helper function for converting the user-specified launch mode
// to a LaunchMode enum value
// Arguments:
// - launchModeString: The string value from the settings file to parse
// Return Value:
// - The corresponding enum value which maps to the string provided by the user
LaunchMode GlobalAppSettings::_ParseLaunchMode(const std::wstring& launchModeString) noexcept
{
if (launchModeString == MaximizedLaunchModeValue)
{
return LaunchMode::MaximizedMode;
}
else if (launchModeString == FullscreenLaunchModeValue)
{
return LaunchMode::FullscreenMode;
}
return LaunchMode::DefaultMode;
}
// Method Description:
// - Helper function for converting the user-specified tab width
// to a TabViewWidthMode enum value
// Arguments:
// - tabWidthModeString: The string value from the settings file to parse
// Return Value:
// - The corresponding enum value which maps to the string provided by the user
TabViewWidthMode GlobalAppSettings::_ParseTabWidthMode(const std::wstring& tabWidthModeString) noexcept
{
if (tabWidthModeString == TitleLengthTabWidthModeValue)
{
return TabViewWidthMode::SizeToContent;
}
else if (tabWidthModeString == TitleLengthCompactModeValue)
{
return TabViewWidthMode::Compact;
}
// default behavior for invalid data or EqualTabWidthValue
return TabViewWidthMode::Equal;
}
// Method Description:
// - Adds the given colorscheme to our map of schemes, using its name as the key.
// Arguments:

View File

@@ -17,6 +17,7 @@ Author(s):
#include "AppKeyBindings.h"
#include "ColorScheme.h"
#include "Command.h"
#include "SettingsTypes.h"
// fwdecl unittest classes
namespace TerminalAppLocalTests
@@ -28,12 +29,6 @@ namespace TerminalAppLocalTests
namespace TerminalApp
{
class GlobalAppSettings;
struct LaunchPosition
{
std::optional<int> x;
std::optional<int> y;
};
};
class TerminalApp::GlobalAppSettings final
@@ -96,15 +91,6 @@ private:
std::unordered_map<std::wstring, ColorScheme> _colorSchemes;
std::unordered_map<winrt::hstring, winrt::TerminalApp::Command> _commands;
static winrt::Windows::UI::Xaml::ElementTheme _ParseTheme(const std::wstring& themeString) noexcept;
static winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode _ParseTabWidthMode(const std::wstring& tabWidthModeString) noexcept;
static void _ParseInitialPosition(const std::string& initialPosition,
LaunchPosition& ret) noexcept;
static winrt::TerminalApp::LaunchMode _ParseLaunchMode(const std::wstring& launchModeString) noexcept;
friend class TerminalAppLocalTests::SettingsTests;
friend class TerminalAppLocalTests::ColorSchemeTests;
};

View File

@@ -1,125 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "Utils.h"
#include "JsonUtils.h"
#include "../../types/inc/Utils.hpp"
void TerminalApp::JsonUtils::GetOptionalColor(const Json::Value& json,
std::string_view key,
std::optional<til::color>& target)
{
const auto conversionFn = [](const Json::Value& value) -> til::color {
return ::Microsoft::Console::Utils::ColorFromHexString(value.asString());
};
GetOptionalValue(json,
key,
target,
conversionFn);
}
void TerminalApp::JsonUtils::GetOptionalString(const Json::Value& json,
std::string_view key,
std::optional<std::wstring>& target)
{
const auto conversionFn = [](const Json::Value& value) -> std::wstring {
return GetWstringFromJson(value);
};
GetOptionalValue(json,
key,
target,
conversionFn);
}
void TerminalApp::JsonUtils::GetOptionalGuid(const Json::Value& json,
std::string_view key,
std::optional<GUID>& target)
{
const auto conversionFn = [](const Json::Value& value) -> GUID {
return ::Microsoft::Console::Utils::GuidFromString(GetWstringFromJson(value));
};
GetOptionalValue(json,
key,
target,
conversionFn);
}
void TerminalApp::JsonUtils::GetOptionalDouble(const Json::Value& json,
std::string_view key,
std::optional<double>& target)
{
const auto conversionFn = [](const Json::Value& value) -> double {
return value.asFloat();
};
const auto validationFn = [](const Json::Value& value) -> bool {
return value.isNumeric();
};
GetOptionalValue(json,
key,
target,
conversionFn,
validationFn);
}
void TerminalApp::JsonUtils::GetInt(const Json::Value& json,
std::string_view key,
int& target)
{
const auto conversionFn = [](const Json::Value& value) -> int {
return value.asInt();
};
const auto validationFn = [](const Json::Value& value) -> bool {
return value.isInt();
};
GetValue(json, key, target, conversionFn, validationFn);
}
void TerminalApp::JsonUtils::GetUInt(const Json::Value& json,
std::string_view key,
uint32_t& target)
{
const auto conversionFn = [](const Json::Value& value) -> uint32_t {
return value.asUInt();
};
const auto validationFn = [](const Json::Value& value) -> bool {
return value.isUInt();
};
GetValue(json, key, target, conversionFn, validationFn);
}
void TerminalApp::JsonUtils::GetDouble(const Json::Value& json,
std::string_view key,
double& target)
{
const auto conversionFn = [](const Json::Value& value) -> double {
return value.asFloat();
};
const auto validationFn = [](const Json::Value& value) -> bool {
return value.isNumeric();
};
GetValue(json, key, target, conversionFn, validationFn);
}
void TerminalApp::JsonUtils::GetBool(const Json::Value& json,
std::string_view key,
bool& target)
{
const auto conversionFn = [](const Json::Value& value) -> bool {
return value.asBool();
};
const auto validationFn = [](const Json::Value& value) -> bool {
return value.isBool();
};
GetValue(json, key, target, conversionFn, validationFn);
}
void TerminalApp::JsonUtils::GetWstring(const Json::Value& json,
std::string_view key,
std::wstring& target)
{
const auto conversionFn = [](const Json::Value& value) -> std::wstring {
return GetWstringFromJson(value);
};
GetValue(json, key, target, conversionFn, nullptr);
}

View File

@@ -9,136 +9,483 @@ Abstract:
- Helpers for the TerminalApp project
Author(s):
- Mike Griese - August 2019
- Dustin Howett - January 2020
--*/
#pragma once
#include <json.h>
#include "../types/inc/utils.hpp"
namespace winrt
{
// If we don't use winrt, nobody will include the ConversionTraits for winrt stuff.
// If nobody includes it, these forward declarations will suffice.
struct guid;
struct hstring;
namespace Windows::Foundation
{
template<typename T>
struct IReference;
}
}
namespace TerminalApp::JsonUtils
{
void GetOptionalColor(const Json::Value& json,
std::string_view key,
std::optional<til::color>& target);
void GetOptionalString(const Json::Value& json,
std::string_view key,
std::optional<std::wstring>& target);
void GetOptionalGuid(const Json::Value& json,
std::string_view key,
std::optional<GUID>& target);
void GetOptionalDouble(const Json::Value& json,
std::string_view key,
std::optional<double>& target);
// Method Description:
// - Helper that can be used for retrieving an optional value from a json
// object, and parsing it's value to layer on a given target object.
// - If the key we're looking for _doesn't_ exist in the json object,
// we'll leave the target object unmodified.
// - If the key exists in the json object, but is set to `null`, then
// we'll instead set the target back to nullopt.
// - Each caller should provide a conversion function that takes a
// Json::Value and returns an object of the same type as target.
// Arguments:
// - json: The json object to search for the given key
// - key: The key to look for in the json object
// - target: the optional object to receive the value from json
// - conversion: a std::function<T(const Json::Value&)> which can be used to
// convert the Json::Value to the appropriate type.
// - validation: optional, if provided, will be called first to ensure that
// the json::value is of the correct type before attempting to call
// `conversion`.
// Return Value:
// - <none>
template<typename T, typename F>
void GetOptionalValue(const Json::Value& json,
std::string_view key,
std::optional<T>& target,
F&& conversion,
const std::function<bool(const Json::Value&)>& validation = nullptr)
namespace Detail
{
if (json.isMember(JsonKey(key)))
// Function Description:
// - Returns a string_view to a Json::Value's internal string storage,
// hopefully without copying it.
__declspec(noinline) inline const std::string_view GetStringView(const Json::Value& json)
{
if (auto jsonVal{ json[JsonKey(key)] })
{
if (validation == nullptr || validation(jsonVal))
{
target = conversion(jsonVal);
}
}
else
{
// This branch is hit when the json object contained the key,
// but the key was set to `null`. In this case, explicitly clear
// the target.
target = std::nullopt;
}
const char* begin{ nullptr };
const char* end{ nullptr };
json.getString(&begin, &end);
const std::string_view zeroCopyString{ begin, gsl::narrow_cast<size_t>(end - begin) };
return zeroCopyString;
}
template<typename T>
struct DeduceOptional
{
using Type = typename std::decay<T>::type;
static constexpr bool IsOptional = false;
};
template<typename TOpt>
struct DeduceOptional<std::optional<TOpt>>
{
using Type = typename std::decay<TOpt>::type;
static constexpr bool IsOptional = true;
};
template<typename TOpt>
struct DeduceOptional<::winrt::Windows::Foundation::IReference<TOpt>>
{
using Type = typename std::decay<TOpt>::type;
static constexpr bool IsOptional = true;
};
}
// Method Description:
// - Helper that can be used for retrieving a value from a json
// object, and parsing it's value to set on a given target object.
// - If the key we're looking for _doesn't_ exist in the json object,
// we'll leave the target object unmodified.
// - If the key exists in the json object, we'll use the provided
// `validation` function to ensure that the json value is of the
// correct type.
// - If we successfully validate the json value type (or no validation
// function was provided), then we'll use `conversion` to parse the
// value and place the result into `target`
// - Each caller should provide a conversion function that takes a
// Json::Value and returns an object of the same type as target.
// - Unlike GetOptionalValue, if the key exists but is set to `null`, we'll
// just ignore it.
// Arguments:
// - json: The json object to search for the given key
// - key: The key to look for in the json object
// - target: the optional object to receive the value from json
// - conversion: a std::function<T(const Json::Value&)> which can be used to
// convert the Json::Value to the appropriate type.
// - validation: optional, if provided, will be called first to ensure that
// the json::value is of the correct type before attempting to call
// `conversion`.
// Return Value:
// - <none>
template<typename T, typename F>
void GetValue(const Json::Value& json,
std::string_view key,
T& target,
F&& conversion,
const std::function<bool(const Json::Value&)>& validation = nullptr)
// These exceptions cannot use localized messages, as we do not have
// guaranteed access to the resource loader.
class TypeMismatchException : public std::runtime_error
{
if (json.isMember(JsonKey(key)))
public:
TypeMismatchException() :
runtime_error("unexpected data type") {}
};
class KeyedException : public std::runtime_error
{
public:
KeyedException(const std::string_view key, std::exception_ptr exception) :
runtime_error(fmt::format("error parsing \"{0}\"", key).c_str()),
_key{ key },
_innerException{ std::move(exception) } {}
std::string GetKey() const
{
if (auto jsonVal{ json[JsonKey(key)] })
return _key;
}
[[noreturn]] void RethrowInner() const
{
std::rethrow_exception(_innerException);
}
private:
std::string _key;
std::exception_ptr _innerException;
};
class UnexpectedValueException : public std::runtime_error
{
public:
UnexpectedValueException(const std::string_view value) :
runtime_error(fmt::format("unexpected value \"{0}\"", value).c_str()),
_value{ value } {}
std::string GetValue() const
{
return _value;
}
private:
std::string _value;
};
template<typename T>
struct ConversionTrait
{
// Forward-declare these so the linker can pick up specializations from elsewhere!
T FromJson(const Json::Value&);
bool CanConvert(const Json::Value& json);
};
template<>
struct ConversionTrait<std::string>
{
std::string FromJson(const Json::Value& json)
{
return json.asString();
}
bool CanConvert(const Json::Value& json)
{
return json.isString();
}
};
template<>
struct ConversionTrait<std::wstring>
{
std::wstring FromJson(const Json::Value& json)
{
return til::u8u16(Detail::GetStringView(json));
}
bool CanConvert(const Json::Value& json)
{
return json.isString();
}
};
#ifdef WINRT_BASE_H
template<>
struct ConversionTrait<winrt::hstring> : public ConversionTrait<std::wstring>
{
// Leverage the wstring converter's validation
winrt::hstring FromJson(const Json::Value& json)
{
return winrt::hstring{ til::u8u16(Detail::GetStringView(json)) };
}
};
#endif
template<>
struct ConversionTrait<bool>
{
bool FromJson(const Json::Value& json)
{
return json.asBool();
}
bool CanConvert(const Json::Value& json)
{
return json.isBool();
}
};
template<>
struct ConversionTrait<int>
{
int FromJson(const Json::Value& json)
{
return json.asInt();
}
bool CanConvert(const Json::Value& json)
{
return json.isInt();
}
};
template<>
struct ConversionTrait<unsigned int>
{
unsigned int FromJson(const Json::Value& json)
{
return json.asUInt();
}
bool CanConvert(const Json::Value& json)
{
return json.isUInt();
}
};
template<>
struct ConversionTrait<float>
{
float FromJson(const Json::Value& json)
{
return json.asFloat();
}
bool CanConvert(const Json::Value& json)
{
return json.isNumeric();
}
};
template<>
struct ConversionTrait<double>
{
double FromJson(const Json::Value& json)
{
return json.asDouble();
}
bool CanConvert(const Json::Value& json)
{
return json.isNumeric();
}
};
template<>
struct ConversionTrait<GUID>
{
GUID FromJson(const Json::Value& json)
{
return ::Microsoft::Console::Utils::GuidFromString(til::u8u16(Detail::GetStringView(json)));
}
bool CanConvert(const Json::Value& json)
{
if (!json.isString())
{
if (validation == nullptr || validation(jsonVal))
return false;
}
const auto string{ Detail::GetStringView(json) };
return string.length() == 38 && string.front() == '{' && string.back() == '}';
}
};
// (GUID and winrt::guid are mutually convertible!)
template<>
struct ConversionTrait<winrt::guid> : public ConversionTrait<GUID>
{
};
template<>
struct ConversionTrait<til::color>
{
til::color FromJson(const Json::Value& json)
{
return ::Microsoft::Console::Utils::ColorFromHexString(Detail::GetStringView(json));
}
bool CanConvert(const Json::Value& json)
{
if (!json.isString())
{
return false;
}
const auto string{ Detail::GetStringView(json) };
return (string.length() == 7 || string.length() == 4) && string.front() == '#';
}
};
template<typename T, typename TBase>
struct EnumMapper
{
using BaseEnumMapper = EnumMapper<T, TBase>;
using ValueType = T;
using pair_type = std::pair<std::string_view, T>;
T FromJson(const Json::Value& json)
{
const auto name{ Detail::GetStringView(json) };
for (const auto& pair : TBase::mappings)
{
if (pair.first == name)
{
target = conversion(jsonVal);
return pair.second;
}
}
throw UnexpectedValueException{ name };
}
bool CanConvert(const Json::Value& json)
{
return json.isString();
}
};
// FlagMapper is EnumMapper, but it works for bitfields.
// It supports a string (single flag) or an array of strings.
// Does an O(n*m) search; meant for small search spaces!
//
// Cleverly leverage EnumMapper to do the heavy lifting.
template<typename T, typename TBase>
struct FlagMapper : public EnumMapper<T, TBase>
{
private:
// Hide BaseEnumMapper so FlagMapper's consumers cannot see
// it.
using BaseEnumMapper = EnumMapper<T, TBase>::BaseEnumMapper;
public:
using BaseFlagMapper = FlagMapper<T, TBase>;
static constexpr T AllSet{ static_cast<T>(~0u) };
static constexpr T AllClear{ static_cast<T>(0u) };
T FromJson(const Json::Value& json)
{
if (json.isString())
{
return BaseEnumMapper::FromJson(json);
}
else if (json.isArray())
{
unsigned int seen{ 0 };
T value{};
for (const auto& element : json)
{
const auto newFlag{ BaseEnumMapper::FromJson(element) };
if (++seen > 1 &&
((newFlag == AllClear && value != AllClear) ||
(value == AllClear && newFlag != AllClear)))
{
// attempt to combine AllClear (explicitly) with anything else
throw UnexpectedValueException{ element.asString() };
}
value |= newFlag;
}
return value;
}
// We'll only get here if CanConvert has failed us.
return AllClear;
}
bool CanConvert(const Json::Value& json)
{
return BaseEnumMapper::CanConvert(json) || json.isArray();
}
};
// Method Description:
// - Helper that will populate a reference with a value converted from a json object.
// Arguments:
// - json: the json object to convert
// - target: the value to populate with the converted result
// Return Value:
// - a boolean indicating whether the value existed (in this case, was non-null)
//
// GetValue, type-deduced, manual converter
template<typename T, typename Converter>
bool GetValue(const Json::Value& json, T& target, Converter&& conv)
{
if constexpr (Detail::DeduceOptional<T>::IsOptional)
{
// FOR OPTION TYPES
// - If the json object is set to `null`, then
// we'll instead set the target back to the empty optional.
if (json.isNull())
{
target = T{}; // zero-construct an empty optional
return true;
}
}
if (json)
{
if (!conv.CanConvert(json))
{
throw TypeMismatchException{};
}
target = conv.FromJson(json);
return true;
}
return false;
}
void GetInt(const Json::Value& json,
std::string_view key,
int& target);
// GetValue, forced return type, manual converter
template<typename T, typename Converter>
std::decay_t<T> GetValue(const Json::Value& json, Converter&& conv)
{
std::decay_t<T> local{};
GetValue(json, local, std::forward<Converter>(conv));
return local; // returns zero-initialized or value
}
void GetUInt(const Json::Value& json,
std::string_view key,
uint32_t& target);
// GetValueForKey, type-deduced, manual converter
template<typename T, typename Converter>
bool GetValueForKey(const Json::Value& json, std::string_view key, T& target, Converter&& conv)
{
if (auto found{ json.find(&*key.cbegin(), (&*key.cbegin()) + key.size()) })
{
try
{
return GetValue(*found, target, std::forward<Converter>(conv));
}
catch (...)
{
// Wrap any caught exceptions in one that preserves context.
throw KeyedException(key, std::current_exception());
}
}
return false;
}
void GetDouble(const Json::Value& json,
std::string_view key,
double& target);
// GetValueForKey, forced return type, manual converter
template<typename T, typename Converter>
std::decay_t<T> GetValueForKey(const Json::Value& json, std::string_view key, Converter&& conv)
{
std::decay_t<T> local{};
GetValueForKey(json, key, local, std::forward<Converter>(conv));
return local; // returns zero-initialized?
}
void GetBool(const Json::Value& json,
std::string_view key,
bool& target);
// GetValue, type-deduced, with automatic converter
template<typename T>
bool GetValue(const Json::Value& json, T& target)
{
return GetValue(json, target, ConversionTrait<typename Detail::DeduceOptional<T>::Type>{});
}
void GetWstring(const Json::Value& json,
std::string_view key,
std::wstring& target);
// GetValue, forced return type, with automatic converter
template<typename T>
std::decay_t<T> GetValue(const Json::Value& json)
{
std::decay_t<T> local{};
GetValue(json, local, ConversionTrait<typename Detail::DeduceOptional<T>::Type>{});
return local; // returns zero-initialized or value
}
// GetValueForKey, type-deduced, with automatic converter
template<typename T>
bool GetValueForKey(const Json::Value& json, std::string_view key, T& target)
{
return GetValueForKey(json, key, target, ConversionTrait<typename Detail::DeduceOptional<T>::Type>{});
}
// GetValueForKey, forced return type, with automatic converter
template<typename T>
std::decay_t<T> GetValueForKey(const Json::Value& json, std::string_view key)
{
return GetValueForKey<T>(json, key, ConversionTrait<typename Detail::DeduceOptional<T>::Type>{});
}
// Get multiple values for keys (json, k, &v, k, &v, k, &v, ...).
// Uses the default converter for each v.
// Careful: this can cause a template explosion.
constexpr void GetValuesForKeys(const Json::Value& /*json*/) {}
template<typename T, typename... Args>
void GetValuesForKeys(const Json::Value& json, std::string_view key1, T&& val1, Args&&... args)
{
GetValueForKey(json, key1, val1);
GetValuesForKeys(json, std::forward<Args>(args)...);
}
};
#define JSON_ENUM_MAPPER(...) \
template<> \
struct ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__> : \
public ::TerminalApp::JsonUtils::EnumMapper<__VA_ARGS__, ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__>>
#define JSON_FLAG_MAPPER(...) \
template<> \
struct ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__> : \
public ::TerminalApp::JsonUtils::FlagMapper<__VA_ARGS__, ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__>>
#define JSON_MAPPINGS(Count) \
static constexpr std::array<pair_type, Count> mappings

View File

@@ -1,490 +0,0 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- JsonUtils.h
Abstract:
- Helpers for the TerminalApp project
Author(s):
- Mike Griese - August 2019
- Dustin Howett - January 2020
--*/
#pragma once
#include <json.h>
#include "../types/inc/utils.hpp"
namespace winrt
{
// If we don't use winrt, nobody will include the ConversionTraits for winrt stuff.
// If nobody includes it, these forward declarations will suffice.
struct guid;
struct hstring;
namespace Windows::Foundation
{
template<typename T>
struct IReference;
}
}
namespace TerminalApp::JsonUtils
{
namespace Detail
{
// Function Description:
// - Returns a string_view to a Json::Value's internal string storage,
// hopefully without copying it.
__declspec(noinline) inline const std::string_view GetStringView(const Json::Value& json)
{
const char* begin{ nullptr };
const char* end{ nullptr };
json.getString(&begin, &end);
const std::string_view zeroCopyString{ begin, gsl::narrow_cast<size_t>(end - begin) };
return zeroCopyString;
}
template<typename T>
struct DeduceOptional
{
using Type = typename std::decay<T>::type;
static constexpr bool IsOptional = false;
};
template<typename TOpt>
struct DeduceOptional<std::optional<TOpt>>
{
using Type = typename std::decay<TOpt>::type;
static constexpr bool IsOptional = true;
};
template<typename TOpt>
struct DeduceOptional<::winrt::Windows::Foundation::IReference<TOpt>>
{
using Type = typename std::decay<TOpt>::type;
static constexpr bool IsOptional = true;
};
}
// These exceptions cannot use localized messages, as we do not have
// guaranteed access to the resource loader.
class TypeMismatchException : public std::runtime_error
{
public:
TypeMismatchException() :
runtime_error("unexpected data type") {}
};
class KeyedException : public std::runtime_error
{
public:
KeyedException(const std::string_view key, std::exception_ptr exception) :
runtime_error(fmt::format("error parsing \"{0}\"", key).c_str()),
_key{ key },
_innerException{ std::move(exception) } {}
std::string GetKey() const
{
return _key;
}
[[noreturn]] void RethrowInner() const
{
std::rethrow_exception(_innerException);
}
private:
std::string _key;
std::exception_ptr _innerException;
};
class UnexpectedValueException : public std::runtime_error
{
public:
UnexpectedValueException(const std::string_view value) :
runtime_error(fmt::format("unexpected value \"{0}\"", value).c_str()),
_value{ value } {}
std::string GetValue() const
{
return _value;
}
private:
std::string _value;
};
template<typename T>
struct ConversionTrait
{
// Forward-declare these so the linker can pick up specializations from elsewhere!
T FromJson(const Json::Value&);
bool CanConvert(const Json::Value& json);
};
template<>
struct ConversionTrait<std::string>
{
std::string FromJson(const Json::Value& json)
{
return json.asString();
}
bool CanConvert(const Json::Value& json)
{
return json.isString();
}
};
template<>
struct ConversionTrait<std::wstring>
{
std::wstring FromJson(const Json::Value& json)
{
return til::u8u16(Detail::GetStringView(json));
}
bool CanConvert(const Json::Value& json)
{
return json.isString();
}
};
#ifdef WINRT_BASE_H
template<>
struct ConversionTrait<winrt::hstring> : public ConversionTrait<std::wstring>
{
// Leverage the wstring converter's validation
winrt::hstring FromJson(const Json::Value& json)
{
return winrt::hstring{ til::u8u16(Detail::GetStringView(json)) };
}
};
#endif
template<>
struct ConversionTrait<bool>
{
bool FromJson(const Json::Value& json)
{
return json.asBool();
}
bool CanConvert(const Json::Value& json)
{
return json.isBool();
}
};
template<>
struct ConversionTrait<int>
{
int FromJson(const Json::Value& json)
{
return json.asInt();
}
bool CanConvert(const Json::Value& json)
{
return json.isInt();
}
};
template<>
struct ConversionTrait<unsigned int>
{
unsigned int FromJson(const Json::Value& json)
{
return json.asUInt();
}
bool CanConvert(const Json::Value& json)
{
return json.isUInt();
}
};
template<>
struct ConversionTrait<float>
{
float FromJson(const Json::Value& json)
{
return json.asFloat();
}
bool CanConvert(const Json::Value& json)
{
return json.isNumeric();
}
};
template<>
struct ConversionTrait<double>
{
double FromJson(const Json::Value& json)
{
return json.asDouble();
}
bool CanConvert(const Json::Value& json)
{
return json.isNumeric();
}
};
template<>
struct ConversionTrait<GUID>
{
GUID FromJson(const Json::Value& json)
{
return ::Microsoft::Console::Utils::GuidFromString(til::u8u16(Detail::GetStringView(json)));
}
bool CanConvert(const Json::Value& json)
{
if (!json.isString())
{
return false;
}
const auto string{ Detail::GetStringView(json) };
return string.length() == 38 && string.front() == '{' && string.back() == '}';
}
};
// (GUID and winrt::guid are mutually convertible!)
template<>
struct ConversionTrait<winrt::guid> : public ConversionTrait<GUID>
{
};
template<>
struct ConversionTrait<til::color>
{
til::color FromJson(const Json::Value& json)
{
return ::Microsoft::Console::Utils::ColorFromHexString(Detail::GetStringView(json));
}
bool CanConvert(const Json::Value& json)
{
if (!json.isString())
{
return false;
}
const auto string{ Detail::GetStringView(json) };
return (string.length() == 7 || string.length() == 4) && string.front() == '#';
}
};
template<typename T, typename TBase>
struct EnumMapper
{
using BaseEnumMapper = EnumMapper<T, TBase>;
using pair_type = std::pair<std::string_view, T>;
T FromJson(const Json::Value& json)
{
const auto name{ Detail::GetStringView(json) };
for (const auto& pair : TBase::mappings)
{
if (pair.first == name)
{
return pair.second;
}
}
throw UnexpectedValueException{ name };
}
bool CanConvert(const Json::Value& json)
{
return json.isString();
}
};
// FlagMapper is EnumMapper, but it works for bitfields.
// It supports a string (single flag) or an array of strings.
// Does an O(n*m) search; meant for small search spaces!
//
// Cleverly leverage EnumMapper to do the heavy lifting.
template<typename T, typename TBase>
struct FlagMapper : public EnumMapper<T, TBase>
{
private:
// Hide BaseEnumMapper so FlagMapper's consumers cannot see
// it.
using BaseEnumMapper = EnumMapper<T, TBase>::BaseEnumMapper;
public:
using BaseFlagMapper = FlagMapper<T, TBase>;
static constexpr T AllSet{ static_cast<T>(~0u) };
static constexpr T AllClear{ static_cast<T>(0u) };
T FromJson(const Json::Value& json)
{
if (json.isString())
{
return BaseEnumMapper::FromJson(json);
}
else if (json.isArray())
{
unsigned int seen{ 0 };
T value{};
for (const auto& element : json)
{
const auto newFlag{ BaseEnumMapper::FromJson(element) };
if (++seen > 1 &&
((newFlag == AllClear && value != AllClear) ||
(value == AllClear && newFlag != AllClear)))
{
// attempt to combine AllClear (explicitly) with anything else
throw UnexpectedValueException{ element.asString() };
}
value |= newFlag;
}
return value;
}
// We'll only get here if CanConvert has failed us.
return AllClear;
}
bool CanConvert(const Json::Value& json)
{
return BaseEnumMapper::CanConvert(json) || json.isArray();
}
};
// Method Description:
// - Helper that will populate a reference with a value converted from a json object.
// Arguments:
// - json: the json object to convert
// - target: the value to populate with the converted result
// Return Value:
// - a boolean indicating whether the value existed (in this case, was non-null)
//
// GetValue, type-deduced, manual converter
template<typename T, typename Converter>
bool GetValue(const Json::Value& json, T& target, Converter&& conv)
{
if constexpr (Detail::DeduceOptional<T>::IsOptional)
{
// FOR OPTION TYPES
// - If the json object is set to `null`, then
// we'll instead set the target back to the empty optional.
if (json.isNull())
{
target = T{}; // zero-construct an empty optional
return true;
}
}
if (json)
{
if (!conv.CanConvert(json))
{
throw TypeMismatchException{};
}
target = conv.FromJson(json);
return true;
}
return false;
}
// GetValue, forced return type, manual converter
template<typename T, typename Converter>
std::decay_t<T> GetValue(const Json::Value& json, Converter&& conv)
{
std::decay_t<T> local{};
GetValue(json, local, std::forward<Converter>(conv));
return local; // returns zero-initialized or value
}
// GetValueForKey, type-deduced, manual converter
template<typename T, typename Converter>
bool GetValueForKey(const Json::Value& json, std::string_view key, T& target, Converter&& conv)
{
if (auto found{ json.find(&*key.cbegin(), (&*key.cbegin()) + key.size()) })
{
try
{
return GetValue(*found, target, std::forward<Converter>(conv));
}
catch (...)
{
// Wrap any caught exceptions in one that preserves context.
throw KeyedException(key, std::current_exception());
}
}
return false;
}
// GetValueForKey, forced return type, manual converter
template<typename T, typename Converter>
std::decay_t<T> GetValueForKey(const Json::Value& json, std::string_view key, Converter&& conv)
{
std::decay_t<T> local{};
GetValueForKey(json, key, local, std::forward<Converter>(conv));
return local; // returns zero-initialized?
}
// GetValue, type-deduced, with automatic converter
template<typename T>
bool GetValue(const Json::Value& json, T& target)
{
return GetValue(json, target, ConversionTrait<typename Detail::DeduceOptional<T>::Type>{});
}
// GetValue, forced return type, with automatic converter
template<typename T>
std::decay_t<T> GetValue(const Json::Value& json)
{
std::decay_t<T> local{};
GetValue(json, local, ConversionTrait<typename Detail::DeduceOptional<T>::Type>{});
return local; // returns zero-initialized or value
}
// GetValueForKey, type-deduced, with automatic converter
template<typename T>
bool GetValueForKey(const Json::Value& json, std::string_view key, T& target)
{
return GetValueForKey(json, key, target, ConversionTrait<typename Detail::DeduceOptional<T>::Type>{});
}
// GetValueForKey, forced return type, with automatic converter
template<typename T>
std::decay_t<T> GetValueForKey(const Json::Value& json, std::string_view key)
{
return GetValueForKey<T>(json, key, ConversionTrait<typename Detail::DeduceOptional<T>::Type>{});
}
// Get multiple values for keys (json, k, &v, k, &v, k, &v, ...).
// Uses the default converter for each v.
// Careful: this can cause a template explosion.
constexpr void GetValuesForKeys(const Json::Value& /*json*/) {}
template<typename T, typename... Args>
void GetValuesForKeys(const Json::Value& json, std::string_view key1, T&& val1, Args&&... args)
{
GetValueForKey(json, key1, val1);
GetValuesForKeys(json, std::forward<Args>(args)...);
}
};
#define JSON_ENUM_MAPPER(...) \
template<> \
struct ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__> : \
public ::TerminalApp::JsonUtils::EnumMapper<__VA_ARGS__, ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__>>
#define JSON_FLAG_MAPPER(...) \
template<> \
struct ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__> : \
public ::TerminalApp::JsonUtils::FlagMapper<__VA_ARGS__, ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__>>
#define JSON_MAPPINGS(Count) \
static constexpr std::array<pair_type, Count> mappings

View File

@@ -9,6 +9,7 @@
#include <DefaultSettings.h>
#include "LegacyProfileGeneratorNamespaces.h"
#include "TerminalSettingsSerializationHelpers.h"
using namespace TerminalApp;
using namespace winrt::Microsoft::Terminal::Settings;
@@ -52,57 +53,6 @@ static constexpr std::string_view BackgroundImageAlignmentKey{ "backgroundImageA
static constexpr std::string_view RetroTerminalEffectKey{ "experimental.retroTerminalEffect" };
static constexpr std::string_view AntialiasingModeKey{ "antialiasingMode" };
// Possible values for closeOnExit
static constexpr std::string_view CloseOnExitAlways{ "always" };
static constexpr std::string_view CloseOnExitGraceful{ "graceful" };
static constexpr std::string_view CloseOnExitNever{ "never" };
// Possible values for Scrollbar state
static constexpr std::wstring_view AlwaysVisible{ L"visible" };
static constexpr std::wstring_view AlwaysHide{ L"hidden" };
// Possible values for Cursor Shape
static constexpr std::wstring_view CursorShapeVintage{ L"vintage" };
static constexpr std::wstring_view CursorShapeBar{ L"bar" };
static constexpr std::wstring_view CursorShapeUnderscore{ L"underscore" };
static constexpr std::wstring_view CursorShapeFilledbox{ L"filledBox" };
static constexpr std::wstring_view CursorShapeEmptybox{ L"emptyBox" };
// Possible values for Font Weight
static constexpr std::string_view FontWeightThin{ "thin" };
static constexpr std::string_view FontWeightExtraLight{ "extra-light" };
static constexpr std::string_view FontWeightLight{ "light" };
static constexpr std::string_view FontWeightSemiLight{ "semi-light" };
static constexpr std::string_view FontWeightNormal{ "normal" };
static constexpr std::string_view FontWeightMedium{ "medium" };
static constexpr std::string_view FontWeightSemiBold{ "semi-bold" };
static constexpr std::string_view FontWeightBold{ "bold" };
static constexpr std::string_view FontWeightExtraBold{ "extra-bold" };
static constexpr std::string_view FontWeightBlack{ "black" };
static constexpr std::string_view FontWeightExtraBlack{ "extra-black" };
// Possible values for Image Stretch Mode
static constexpr std::string_view ImageStretchModeNone{ "none" };
static constexpr std::string_view ImageStretchModeFill{ "fill" };
static constexpr std::string_view ImageStretchModeUniform{ "uniform" };
static constexpr std::string_view ImageStretchModeUniformTofill{ "uniformToFill" };
// Possible values for Image Alignment
static constexpr std::string_view ImageAlignmentCenter{ "center" };
static constexpr std::string_view ImageAlignmentLeft{ "left" };
static constexpr std::string_view ImageAlignmentTop{ "top" };
static constexpr std::string_view ImageAlignmentRight{ "right" };
static constexpr std::string_view ImageAlignmentBottom{ "bottom" };
static constexpr std::string_view ImageAlignmentTopLeft{ "topLeft" };
static constexpr std::string_view ImageAlignmentTopRight{ "topRight" };
static constexpr std::string_view ImageAlignmentBottomLeft{ "bottomLeft" };
static constexpr std::string_view ImageAlignmentBottomRight{ "bottomRight" };
// Possible values for TextAntialiasingMode
static constexpr std::wstring_view AntialiasingModeGrayscale{ L"grayscale" };
static constexpr std::wstring_view AntialiasingModeCleartype{ L"cleartype" };
static constexpr std::wstring_view AntialiasingModeAliased{ L"aliased" };
Profile::Profile() :
Profile(std::nullopt)
{
@@ -248,8 +198,7 @@ TerminalSettings Profile::CreateTerminalSettings(const std::unordered_map<std::w
if (_scrollbarState)
{
ScrollbarState result = ParseScrollbarState(_scrollbarState.value());
terminalSettings.ScrollState(result);
terminalSettings.ScrollState(_scrollbarState.value());
}
if (HasBackgroundImage())
@@ -350,11 +299,9 @@ bool Profile::ShouldBeLayered(const Json::Value& json) const
// First, check that GUIDs match. This is easy. If they don't match, they
// should _definitely_ not layer.
if (json.isMember(JsonKey(GuidKey)))
if (const auto otherGuid{ JsonUtils::GetValueForKey<std::optional<GUID>>(json, GuidKey) })
{
const auto guid{ json[JsonKey(GuidKey)] };
const auto otherGuid = Utils::GuidFromString(GetWstringFromJson(guid));
if (_guid.value() != otherGuid)
if (otherGuid != _guid) // optional compare takes care of this
{
return false;
}
@@ -368,16 +315,17 @@ bool Profile::ShouldBeLayered(const Json::Value& json) const
return false;
}
const auto& otherSource = json.isMember(JsonKey(SourceKey)) ? json[JsonKey(SourceKey)] : Json::Value::null;
std::optional<std::wstring> otherSource;
bool otherHadSource = JsonUtils::GetValueForKey(json, SourceKey, otherSource);
// For profiles with a `source`, also check the `source` property.
bool sourceMatches = false;
if (_source.has_value())
{
if (json.isMember(JsonKey(SourceKey)))
if (otherHadSource)
{
const auto otherSourceString = GetWstringFromJson(otherSource);
sourceMatches = otherSourceString == _source.value();
// If we have a source and the other has a source, compare them!
sourceMatches = otherSource == _source;
}
else
{
@@ -395,52 +343,13 @@ bool Profile::ShouldBeLayered(const Json::Value& json) const
}
else
{
// We do not have a source. The only way we match is if source is set to null or "".
if (otherSource.isNull() || (otherSource.isString() && otherSource == ""))
{
sourceMatches = true;
}
// We do not have a source. The only way we match is if source is unset or set to "".
sourceMatches = (!otherSource.has_value() || otherSource.value() == L"");
}
return sourceMatches;
}
// Method Description:
// - Helper function to convert a json value into a value of the Stretch enum.
// Calls into ParseImageStretchMode. Used with JsonUtils::GetOptionalValue.
// Arguments:
// - json: the Json::Value object to parse.
// Return Value:
// - An appropriate value from Windows.UI.Xaml.Media.Stretch
Media::Stretch Profile::_ConvertJsonToStretchMode(const Json::Value& json)
{
return Profile::ParseImageStretchMode(json.asString());
}
// Method Description:
// - Helper function to convert a json value into a value of the Stretch enum.
// Calls into ParseImageAlignment. Used with JsonUtils::GetOptionalValue.
// Arguments:
// - json: the Json::Value object to parse.
// Return Value:
// - A pair of HorizontalAlignment and VerticalAlignment
std::tuple<HorizontalAlignment, VerticalAlignment> Profile::_ConvertJsonToAlignment(const Json::Value& json)
{
return Profile::ParseImageAlignment(json.asString());
}
// Method Description:
// - Helper function to convert a json value into a bool.
// Used with JsonUtils::GetOptionalValue.
// Arguments:
// - json: the Json::Value object to parse.
// Return Value:
// - A bool
bool Profile::_ConvertJsonToBool(const Json::Value& json)
{
return json.asBool();
}
// Method Description:
// - Layer values from the given json object on top of the existing properties
// of this object. For any keys we're expecting to be able to parse in the
@@ -456,89 +365,45 @@ bool Profile::_ConvertJsonToBool(const Json::Value& json)
void Profile::LayerJson(const Json::Value& json)
{
// Profile-specific Settings
JsonUtils::GetWstring(json, NameKey, _name);
JsonUtils::GetOptionalGuid(json, GuidKey, _guid);
JsonUtils::GetBool(json, HiddenKey, _hidden);
JsonUtils::GetValueForKey(json, NameKey, _name);
JsonUtils::GetValueForKey(json, GuidKey, _guid);
JsonUtils::GetValueForKey(json, HiddenKey, _hidden);
// Core Settings
JsonUtils::GetOptionalColor(json, ForegroundKey, _defaultForeground);
JsonUtils::GetOptionalColor(json, BackgroundKey, _defaultBackground);
JsonUtils::GetOptionalColor(json, SelectionBackgroundKey, _selectionBackground);
JsonUtils::GetOptionalColor(json, CursorColorKey, _cursorColor);
JsonUtils::GetOptionalString(json, ColorSchemeKey, _schemeName);
JsonUtils::GetValueForKey(json, ForegroundKey, _defaultForeground);
JsonUtils::GetValueForKey(json, BackgroundKey, _defaultBackground);
JsonUtils::GetValueForKey(json, SelectionBackgroundKey, _selectionBackground);
JsonUtils::GetValueForKey(json, CursorColorKey, _cursorColor);
JsonUtils::GetValueForKey(json, ColorSchemeKey, _schemeName);
// TODO:MSFT:20642297 - Use a sentinel value (-1) for "Infinite scrollback"
JsonUtils::GetInt(json, HistorySizeKey, _historySize);
JsonUtils::GetBool(json, SnapOnInputKey, _snapOnInput);
JsonUtils::GetBool(json, AltGrAliasingKey, _altGrAliasing);
JsonUtils::GetUInt(json, CursorHeightKey, _cursorHeight);
if (json.isMember(JsonKey(CursorShapeKey)))
{
auto cursorShape{ json[JsonKey(CursorShapeKey)] };
_cursorShape = _ParseCursorShape(GetWstringFromJson(cursorShape));
}
JsonUtils::GetOptionalString(json, TabTitleKey, _tabTitle);
JsonUtils::GetValueForKey(json, HistorySizeKey, _historySize);
JsonUtils::GetValueForKey(json, SnapOnInputKey, _snapOnInput);
JsonUtils::GetValueForKey(json, AltGrAliasingKey, _altGrAliasing);
JsonUtils::GetValueForKey(json, CursorHeightKey, _cursorHeight);
JsonUtils::GetValueForKey(json, CursorShapeKey, _cursorShape);
JsonUtils::GetValueForKey(json, TabTitleKey, _tabTitle);
// Control Settings
JsonUtils::GetOptionalGuid(json, ConnectionTypeKey, _connectionType);
JsonUtils::GetWstring(json, CommandlineKey, _commandline);
JsonUtils::GetWstring(json, FontFaceKey, _fontFace);
JsonUtils::GetInt(json, FontSizeKey, _fontSize);
if (json.isMember(JsonKey(FontWeightKey)))
{
auto fontWeight{ json[JsonKey(FontWeightKey)] };
_fontWeight = _ParseFontWeight(fontWeight);
}
JsonUtils::GetDouble(json, AcrylicTransparencyKey, _acrylicTransparency);
JsonUtils::GetBool(json, UseAcrylicKey, _useAcrylic);
JsonUtils::GetBool(json, SuppressApplicationTitleKey, _suppressApplicationTitle);
if (json.isMember(JsonKey(CloseOnExitKey)))
{
auto closeOnExit{ json[JsonKey(CloseOnExitKey)] };
_closeOnExitMode = ParseCloseOnExitMode(closeOnExit);
}
JsonUtils::GetWstring(json, PaddingKey, _padding);
JsonUtils::GetOptionalString(json, ScrollbarStateKey, _scrollbarState);
JsonUtils::GetOptionalString(json, StartingDirectoryKey, _startingDirectory);
JsonUtils::GetOptionalString(json, IconKey, _icon);
JsonUtils::GetOptionalString(json, BackgroundImageKey, _backgroundImage);
JsonUtils::GetOptionalDouble(json, BackgroundImageOpacityKey, _backgroundImageOpacity);
JsonUtils::GetOptionalValue(json, BackgroundImageStretchModeKey, _backgroundImageStretchMode, &Profile::_ConvertJsonToStretchMode);
JsonUtils::GetOptionalValue(json, BackgroundImageAlignmentKey, _backgroundImageAlignment, &Profile::_ConvertJsonToAlignment);
JsonUtils::GetOptionalValue(json, RetroTerminalEffectKey, _retroTerminalEffect, Profile::_ConvertJsonToBool);
if (json.isMember(JsonKey(AntialiasingModeKey)))
{
auto antialiasingMode{ json[JsonKey(AntialiasingModeKey)] };
_antialiasingMode = ParseTextAntialiasingMode(GetWstringFromJson(antialiasingMode));
}
JsonUtils::GetValueForKey(json, FontWeightKey, _fontWeight);
JsonUtils::GetValueForKey(json, ConnectionTypeKey, _connectionType);
JsonUtils::GetValueForKey(json, CommandlineKey, _commandline);
JsonUtils::GetValueForKey(json, FontFaceKey, _fontFace);
JsonUtils::GetValueForKey(json, FontSizeKey, _fontSize);
JsonUtils::GetValueForKey(json, AcrylicTransparencyKey, _acrylicTransparency);
JsonUtils::GetValueForKey(json, UseAcrylicKey, _useAcrylic);
JsonUtils::GetValueForKey(json, SuppressApplicationTitleKey, _suppressApplicationTitle);
JsonUtils::GetValueForKey(json, CloseOnExitKey, _closeOnExitMode);
JsonUtils::GetValueForKey(json, PaddingKey, _padding);
JsonUtils::GetValueForKey(json, ScrollbarStateKey, _scrollbarState);
JsonUtils::GetValueForKey(json, StartingDirectoryKey, _startingDirectory);
JsonUtils::GetValueForKey(json, IconKey, _icon);
JsonUtils::GetValueForKey(json, BackgroundImageKey, _backgroundImage);
JsonUtils::GetValueForKey(json, BackgroundImageOpacityKey, _backgroundImageOpacity);
JsonUtils::GetValueForKey(json, BackgroundImageStretchModeKey, _backgroundImageStretchMode);
JsonUtils::GetValueForKey(json, BackgroundImageAlignmentKey, _backgroundImageAlignment);
JsonUtils::GetValueForKey(json, RetroTerminalEffectKey, _retroTerminalEffect);
JsonUtils::GetValueForKey(json, AntialiasingModeKey, _antialiasingMode);
}
void Profile::SetFontFace(std::wstring fontFace) noexcept
@@ -770,249 +635,6 @@ std::wstring Profile::EvaluateStartingDirectory(const std::wstring& directory)
}
}
// Method Description:
// - Helper function for converting a user-specified font weight value to its corresponding enum
// Arguments:
// - The value from the settings.json file
// Return Value:
// - The corresponding value which maps to the string provided by the user
winrt::Windows::UI::Text::FontWeight Profile::_ParseFontWeight(const Json::Value& json)
{
if (json.isUInt())
{
winrt::Windows::UI::Text::FontWeight weight;
weight.Weight = static_cast<uint16_t>(json.asUInt());
// We're only accepting variable values between 100 and 990 so we don't go too crazy.
if (weight.Weight >= 100 && weight.Weight <= 990)
{
return weight;
}
}
if (json.isString())
{
auto fontWeight = json.asString();
if (fontWeight == FontWeightThin)
{
return winrt::Windows::UI::Text::FontWeights::Thin();
}
else if (fontWeight == FontWeightExtraLight)
{
return winrt::Windows::UI::Text::FontWeights::ExtraLight();
}
else if (fontWeight == FontWeightLight)
{
return winrt::Windows::UI::Text::FontWeights::Light();
}
else if (fontWeight == FontWeightSemiLight)
{
return winrt::Windows::UI::Text::FontWeights::SemiLight();
}
else if (fontWeight == FontWeightNormal)
{
return winrt::Windows::UI::Text::FontWeights::Normal();
}
else if (fontWeight == FontWeightMedium)
{
return winrt::Windows::UI::Text::FontWeights::Medium();
}
else if (fontWeight == FontWeightSemiBold)
{
return winrt::Windows::UI::Text::FontWeights::SemiBold();
}
else if (fontWeight == FontWeightBold)
{
return winrt::Windows::UI::Text::FontWeights::Bold();
}
else if (fontWeight == FontWeightExtraBold)
{
return winrt::Windows::UI::Text::FontWeights::ExtraBold();
}
else if (fontWeight == FontWeightBlack)
{
return winrt::Windows::UI::Text::FontWeights::Black();
}
else if (fontWeight == FontWeightExtraBlack)
{
return winrt::Windows::UI::Text::FontWeights::ExtraBlack();
}
}
return winrt::Windows::UI::Text::FontWeights::Normal();
}
// Method Description:
// - Helper function for converting a user-specified closeOnExit value to its corresponding enum
// Arguments:
// - The value from the settings.json file
// Return Value:
// - The corresponding enum value which maps to the string provided by the user
CloseOnExitMode Profile::ParseCloseOnExitMode(const Json::Value& json)
{
if (json.isBool())
{
return json.asBool() ? CloseOnExitMode::Graceful : CloseOnExitMode::Never;
}
if (json.isString())
{
auto closeOnExit = json.asString();
if (closeOnExit == CloseOnExitAlways)
{
return CloseOnExitMode::Always;
}
else if (closeOnExit == CloseOnExitGraceful)
{
return CloseOnExitMode::Graceful;
}
else if (closeOnExit == CloseOnExitNever)
{
return CloseOnExitMode::Never;
}
}
return CloseOnExitMode::Graceful;
}
// Method Description:
// - Helper function for converting a user-specified scrollbar state to its corresponding enum
// Arguments:
// - The value from the settings.json file
// Return Value:
// - The corresponding enum value which maps to the string provided by the user
ScrollbarState Profile::ParseScrollbarState(const std::wstring& scrollbarState)
{
if (scrollbarState == AlwaysVisible)
{
return ScrollbarState::Visible;
}
else if (scrollbarState == AlwaysHide)
{
return ScrollbarState::Hidden;
}
else
{
return ScrollbarState::Visible;
}
}
// Method Description:
// - Helper function for converting a user-specified image stretch mode
// to the appropriate enum value
// Arguments:
// - The value from the settings.json file
// Return Value:
// - The corresponding enum value which maps to the string provided by the user
Media::Stretch Profile::ParseImageStretchMode(const std::string_view imageStretchMode)
{
if (imageStretchMode == ImageStretchModeNone)
{
return Media::Stretch::None;
}
else if (imageStretchMode == ImageStretchModeFill)
{
return Media::Stretch::Fill;
}
else if (imageStretchMode == ImageStretchModeUniform)
{
return Media::Stretch::Uniform;
}
else // Fall through to default behavior
{
return Media::Stretch::UniformToFill;
}
}
// Method Description:
// - Helper function for converting a user-specified image horizontal and vertical
// alignment to the appropriate enum values tuple
// Arguments:
// - The value from the settings.json file
// Return Value:
// - The corresponding enum values tuple which maps to the string provided by the user
std::tuple<HorizontalAlignment, VerticalAlignment> Profile::ParseImageAlignment(const std::string_view imageAlignment)
{
if (imageAlignment == ImageAlignmentTopLeft)
{
return std::make_tuple(HorizontalAlignment::Left,
VerticalAlignment::Top);
}
else if (imageAlignment == ImageAlignmentBottomLeft)
{
return std::make_tuple(HorizontalAlignment::Left,
VerticalAlignment::Bottom);
}
else if (imageAlignment == ImageAlignmentLeft)
{
return std::make_tuple(HorizontalAlignment::Left,
VerticalAlignment::Center);
}
else if (imageAlignment == ImageAlignmentTopRight)
{
return std::make_tuple(HorizontalAlignment::Right,
VerticalAlignment::Top);
}
else if (imageAlignment == ImageAlignmentBottomRight)
{
return std::make_tuple(HorizontalAlignment::Right,
VerticalAlignment::Bottom);
}
else if (imageAlignment == ImageAlignmentRight)
{
return std::make_tuple(HorizontalAlignment::Right,
VerticalAlignment::Center);
}
else if (imageAlignment == ImageAlignmentTop)
{
return std::make_tuple(HorizontalAlignment::Center,
VerticalAlignment::Top);
}
else if (imageAlignment == ImageAlignmentBottom)
{
return std::make_tuple(HorizontalAlignment::Center,
VerticalAlignment::Bottom);
}
else // Fall through to default alignment
{
return std::make_tuple(HorizontalAlignment::Center,
VerticalAlignment::Center);
}
}
// Method Description:
// - Helper function for converting a user-specified cursor style corresponding
// CursorStyle enum value
// Arguments:
// - cursorShapeString: The string value from the settings file to parse
// Return Value:
// - The corresponding enum value which maps to the string provided by the user
CursorStyle Profile::_ParseCursorShape(const std::wstring& cursorShapeString)
{
if (cursorShapeString == CursorShapeVintage)
{
return CursorStyle::Vintage;
}
else if (cursorShapeString == CursorShapeBar)
{
return CursorStyle::Bar;
}
else if (cursorShapeString == CursorShapeUnderscore)
{
return CursorStyle::Underscore;
}
else if (cursorShapeString == CursorShapeFilledbox)
{
return CursorStyle::FilledBox;
}
else if (cursorShapeString == CursorShapeEmptybox)
{
return CursorStyle::EmptyBox;
}
// default behavior for invalid data
return CursorStyle::Bar;
}
// Method Description:
// - If this profile never had a GUID set for it, generate a runtime GUID for
// the profile. If a profile had their guid manually set to {0}, this method
@@ -1078,17 +700,13 @@ GUID Profile::_GenerateGuidForProfile(const std::wstring& name, const std::optio
// - The json's `guid`, or a guid synthesized for it.
GUID Profile::GetGuidOrGenerateForJson(const Json::Value& json) noexcept
{
std::optional<GUID> guid{ std::nullopt };
JsonUtils::GetOptionalGuid(json, GuidKey, guid);
if (guid)
if (const auto guid{ JsonUtils::GetValueForKey<std::optional<GUID>>(json, GuidKey) })
{
return guid.value();
}
const auto name = GetWstringFromJson(json[JsonKey(NameKey)]);
std::optional<std::wstring> source{ std::nullopt };
JsonUtils::GetOptionalString(json, SourceKey, source);
const auto name{ JsonUtils::GetValueForKey<std::wstring>(json, NameKey) };
const auto source{ JsonUtils::GetValueForKey<std::optional<std::wstring>>(json, SourceKey) };
return Profile::_GenerateGuidForProfile(name, source);
}
@@ -1097,28 +715,3 @@ void Profile::SetRetroTerminalEffect(bool value) noexcept
{
_retroTerminalEffect = value;
}
// Method Description:
// - Helper function for converting a user-specified antialiasing mode
// corresponding TextAntialiasingMode enum value
// Arguments:
// - antialiasingMode: The string value from the settings file to parse
// Return Value:
// - The corresponding enum value which maps to the string provided by the user
TextAntialiasingMode Profile::ParseTextAntialiasingMode(const std::wstring& antialiasingMode)
{
if (antialiasingMode == AntialiasingModeCleartype)
{
return TextAntialiasingMode::Cleartype;
}
else if (antialiasingMode == AntialiasingModeAliased)
{
return TextAntialiasingMode::Aliased;
}
else if (antialiasingMode == AntialiasingModeGrayscale)
{
return TextAntialiasingMode::Grayscale;
}
// default behavior for invalid data
return TextAntialiasingMode::Grayscale;
}

View File

@@ -15,6 +15,7 @@ Author(s):
--*/
#pragma once
#include "ColorScheme.h"
#include "SettingsTypes.h"
// fwdecl unittest classes
namespace TerminalAppLocalTests
@@ -35,14 +36,7 @@ constexpr GUID RUNTIME_GENERATED_PROFILE_NAMESPACE_GUID = { 0xf65ddb7e, 0x706b,
namespace TerminalApp
{
class Profile;
enum class CloseOnExitMode
{
Never = 0,
Graceful,
Always
};
};
}
class TerminalApp::Profile final
{
@@ -107,24 +101,8 @@ public:
private:
static std::wstring EvaluateStartingDirectory(const std::wstring& directory);
static winrt::Microsoft::Terminal::Settings::ScrollbarState ParseScrollbarState(const std::wstring& scrollbarState);
static winrt::Windows::UI::Xaml::Media::Stretch ParseImageStretchMode(const std::string_view imageStretchMode);
static winrt::Windows::UI::Xaml::Media::Stretch _ConvertJsonToStretchMode(const Json::Value& json);
static std::tuple<winrt::Windows::UI::Xaml::HorizontalAlignment, winrt::Windows::UI::Xaml::VerticalAlignment> ParseImageAlignment(const std::string_view imageAlignment);
static std::tuple<winrt::Windows::UI::Xaml::HorizontalAlignment, winrt::Windows::UI::Xaml::VerticalAlignment> _ConvertJsonToAlignment(const Json::Value& json);
static winrt::Windows::UI::Text::FontWeight _ParseFontWeight(const Json::Value& json);
static CloseOnExitMode ParseCloseOnExitMode(const Json::Value& json);
static winrt::Microsoft::Terminal::Settings::CursorStyle _ParseCursorShape(const std::wstring& cursorShapeString);
static winrt::Microsoft::Terminal::Settings::TextAntialiasingMode ParseTextAntialiasingMode(const std::wstring& antialiasingMode);
static GUID _GenerateGuidForProfile(const std::wstring& name, const std::optional<std::wstring>& source) noexcept;
static bool _ConvertJsonToBool(const Json::Value& json);
std::optional<GUID> _guid{ std::nullopt };
std::optional<std::wstring> _source{ std::nullopt };
std::wstring _name;
@@ -159,7 +137,7 @@ private:
std::optional<winrt::Windows::UI::Xaml::Media::Stretch> _backgroundImageStretchMode;
std::optional<std::tuple<winrt::Windows::UI::Xaml::HorizontalAlignment, winrt::Windows::UI::Xaml::VerticalAlignment>> _backgroundImageAlignment;
std::optional<std::wstring> _scrollbarState;
std::optional<::winrt::Microsoft::Terminal::Settings::ScrollbarState> _scrollbarState;
CloseOnExitMode _closeOnExitMode;
std::wstring _padding;

View File

@@ -0,0 +1,28 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- SettingsTypes.h
Abstract:
- Types used in the settings model (non-exported)
--*/
#pragma once
namespace TerminalApp
{
enum class CloseOnExitMode
{
Never = 0,
Graceful,
Always
};
struct LaunchPosition
{
std::optional<int> x;
std::optional<int> y;
};
};

View File

@@ -0,0 +1,272 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- TerminalSettingsSerializationHelpers.h
Abstract:
- Specializations of the JsonUtils helpers for things that might end up in a
settings document.
--*/
#pragma once
#include "pch.h"
#include "JsonUtils.h"
#include "SettingsTypes.h"
#include <winrt/Microsoft.Terminal.Settings.h>
#include <winrt/TerminalApp.h>
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::CursorStyle)
{
static constexpr std::array<pair_type, 5> mappings = {
pair_type{ "bar", ValueType::Bar },
pair_type{ "vintage", ValueType::Vintage },
pair_type{ "underscore", ValueType::Underscore },
pair_type{ "filledBox", ValueType::FilledBox },
pair_type{ "emptyBox", ValueType::EmptyBox }
};
};
JSON_ENUM_MAPPER(::winrt::Windows::UI::Xaml::Media::Stretch)
{
static constexpr std::array<pair_type, 4> mappings = {
pair_type{ "uniformToFill", ValueType::UniformToFill },
pair_type{ "none", ValueType::None },
pair_type{ "fill", ValueType::Fill },
pair_type{ "uniform", ValueType::Uniform }
};
};
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::ScrollbarState)
{
static constexpr std::array<pair_type, 2> mappings = {
pair_type{ "visible", ValueType::Visible },
pair_type{ "hidden", ValueType::Hidden }
};
};
JSON_ENUM_MAPPER(std::tuple<::winrt::Windows::UI::Xaml::HorizontalAlignment, ::winrt::Windows::UI::Xaml::VerticalAlignment>)
{
// reduce repetition
using HA = ::winrt::Windows::UI::Xaml::HorizontalAlignment;
using VA = ::winrt::Windows::UI::Xaml::VerticalAlignment;
static constexpr std::array<pair_type, 9> mappings = {
pair_type{ "center", std::make_tuple(HA::Center, VA::Center) },
pair_type{ "topLeft", std::make_tuple(HA::Left, VA::Top) },
pair_type{ "bottomLeft", std::make_tuple(HA::Left, VA::Bottom) },
pair_type{ "left", std::make_tuple(HA::Left, VA::Center) },
pair_type{ "topRight", std::make_tuple(HA::Right, VA::Top) },
pair_type{ "bottomRight", std::make_tuple(HA::Right, VA::Bottom) },
pair_type{ "right", std::make_tuple(HA::Right, VA::Center) },
pair_type{ "top", std::make_tuple(HA::Center, VA::Top) },
pair_type{ "bottom", std::make_tuple(HA::Center, VA::Bottom) }
};
};
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::TextAntialiasingMode)
{
static constexpr std::array<pair_type, 3> mappings = {
pair_type{ "grayscale", ValueType::Grayscale },
pair_type{ "cleartype", ValueType::Cleartype },
pair_type{ "aliased", ValueType::Aliased }
};
};
// Type Description:
// - Helper for converting a user-specified closeOnExit value to its corresponding enum
JSON_ENUM_MAPPER(::TerminalApp::CloseOnExitMode)
{
JSON_MAPPINGS(3) = {
pair_type{ "always", ValueType::Always },
pair_type{ "graceful", ValueType::Graceful },
pair_type{ "never", ValueType::Never },
};
// Override mapping parser to add boolean parsing
CloseOnExitMode FromJson(const Json::Value& json)
{
if (json.isBool())
{
return json.asBool() ? ValueType::Graceful : ValueType::Never;
}
return EnumMapper::FromJson(json);
}
bool CanConvert(const Json::Value& json)
{
return EnumMapper::CanConvert(json) || json.isBool();
}
};
// This specialization isn't using JSON_ENUM_MAPPER because we need to have a different
// value type (unsinged int) and return type (FontWeight struct). JSON_ENUM_MAPPER
// expects that the value type _is_ the return type.
template<>
struct ::TerminalApp::JsonUtils::ConversionTrait<::winrt::Windows::UI::Text::FontWeight> :
public ::TerminalApp::JsonUtils::EnumMapper<
unsigned int,
::TerminalApp::JsonUtils::ConversionTrait<::winrt::Windows::UI::Text::FontWeight>>
{
// The original parser used the font weight getters Bold(), Normal(), etc.
// They were both cumbersome and *not constant expressions*
JSON_MAPPINGS(11) = {
pair_type{ "thin", 100u },
pair_type{ "extra-light", 200u },
pair_type{ "light", 300u },
pair_type{ "semi-light", 350u },
pair_type{ "normal", 400u },
pair_type{ "medium", 500u },
pair_type{ "semi-bold", 600u },
pair_type{ "bold", 700u },
pair_type{ "extra-bold", 800u },
pair_type{ "black", 900u },
pair_type{ "extra-black", 950u },
};
// Override mapping parser to add boolean parsing
auto FromJson(const Json::Value& json)
{
unsigned int value{ 400 };
if (json.isUInt())
{
value = json.asUInt();
}
else
{
value = BaseEnumMapper::FromJson(json);
}
::winrt::Windows::UI::Text::FontWeight weight{
static_cast<uint16_t>(std::clamp(value, 100u, 990u))
};
return weight;
}
bool CanConvert(const Json::Value& json)
{
return BaseEnumMapper::CanConvert(json) || json.isUInt();
}
};
JSON_ENUM_MAPPER(::winrt::Windows::UI::Xaml::ElementTheme)
{
JSON_MAPPINGS(3) = {
pair_type{ "system", ValueType::Default },
pair_type{ "light", ValueType::Light },
pair_type{ "dark", ValueType::Dark },
};
};
JSON_ENUM_MAPPER(::winrt::TerminalApp::LaunchMode)
{
JSON_MAPPINGS(3) = {
pair_type{ "default", ValueType::DefaultMode },
pair_type{ "maximized", ValueType::MaximizedMode },
pair_type{ "fullscreen", ValueType::FullscreenMode },
};
};
JSON_ENUM_MAPPER(::winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode)
{
JSON_MAPPINGS(3) = {
pair_type{ "equal", ValueType::Equal },
pair_type{ "titleLength", ValueType::SizeToContent },
pair_type{ "compact", ValueType::Compact },
};
};
// Type Description:
// - Helper for converting the initial position string into
// 2 coordinate values. We allow users to only provide one coordinate,
// thus, we use comma as the separator:
// (100, 100): standard input string
// (, 100), (100, ): if a value is missing, we set this value as a default
// (,): both x and y are set to default
// (abc, 100): if a value is not valid, we treat it as default
// (100, 100, 100): we only read the first two values, this is equivalent to (100, 100)
template<>
struct ::TerminalApp::JsonUtils::ConversionTrait<::TerminalApp::LaunchPosition>
{
::TerminalApp::LaunchPosition FromJson(const Json::Value& json)
{
::TerminalApp::LaunchPosition ret;
std::string initialPosition{ json.asString() };
static constexpr char singleCharDelim = ',';
std::stringstream tokenStream(initialPosition);
std::string token;
uint8_t initialPosIndex = 0;
// Get initial position values till we run out of delimiter separated values in the stream
// or we hit max number of allowable values (= 2)
// Non-numeral values or empty string will be caught as exception and we do not assign them
for (; std::getline(tokenStream, token, singleCharDelim) && (initialPosIndex < 2); initialPosIndex++)
{
try
{
int32_t position = std::stoi(token);
if (initialPosIndex == 0)
{
ret.x.emplace(position);
}
if (initialPosIndex == 1)
{
ret.y.emplace(position);
}
}
catch (...)
{
// Do nothing
}
}
return ret;
}
bool CanConvert(const Json::Value& json)
{
return json.isString();
}
};
// Possible Direction values
JSON_ENUM_MAPPER(::winrt::TerminalApp::Direction)
{
JSON_MAPPINGS(4) = {
pair_type{ "left", ValueType::Left },
pair_type{ "right", ValueType::Right },
pair_type{ "up", ValueType::Up },
pair_type{ "down", ValueType::Down },
};
};
// Possible SplitState values
JSON_ENUM_MAPPER(::winrt::TerminalApp::SplitState)
{
JSON_MAPPINGS(3) = {
pair_type{ "vertical", ValueType::Vertical },
pair_type{ "horizontal", ValueType::Horizontal },
pair_type{ "auto", ValueType::Automatic },
};
};
// Possible SplitType values
JSON_ENUM_MAPPER(::winrt::TerminalApp::SplitType)
{
JSON_MAPPINGS(1) = {
pair_type{ "duplicate", ValueType::Duplicate },
};
};
JSON_ENUM_MAPPER(::winrt::TerminalApp::SettingsTarget)
{
JSON_MAPPINGS(3) = {
pair_type{ "settingsFile", ValueType::SettingsFile },
pair_type{ "defaultsFile", ValueType::DefaultsFile },
pair_type{ "allFiles", ValueType::AllFiles },
};
};

View File

@@ -1,18 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "Utils.h"
// Method Description:
// - Constructs a wstring from a given Json::Value object. Reads the object as
// a std::string using asString, then builds an hstring from that std::string,
// then converts that hstring into a std::wstring.
// Arguments:
// - json: the Json::Value to parse as a string
// Return Value:
// - the wstring equivalent of the value in json
std::wstring GetWstringFromJson(const Json::Value& json)
{
return winrt::to_hstring(json.asString()).c_str();
}

View File

@@ -13,8 +13,6 @@ Author(s):
--*/
#pragma once
std::wstring GetWstringFromJson(const Json::Value& json);
// Method Description:
// - Create a std::string from a string_view. We do this because we can't look
// up a key in a Json::Value with a string_view directly, so instead we'll use

View File

@@ -109,6 +109,7 @@
<ClInclude Include="../JsonUtils.h" />
<ClInclude Include="../Utils.h" />
<ClInclude Include="../DefaultProfileUtils.h" />
<ClInclude Include="../TerminalSettingsSerializationHelpers.h" />
<ClInclude Include="../TerminalWarnings.h" />
<ClInclude Include="../IDynamicProfileGenerator.h" />
<ClInclude Include="../PowershellCoreProfileGenerator.h" />
@@ -178,8 +179,6 @@
<ClCompile Include="../CascadiaSettingsSerialization.cpp" />
<ClCompile Include="../AppKeyBindingsSerialization.cpp" />
<ClCompile Include="../KeyChordSerialization.cpp" />
<ClCompile Include="../JsonUtils.cpp" />
<ClCompile Include="../Utils.cpp" />
<ClCompile Include="../DefaultProfileUtils.cpp" />
<ClCompile Include="../PowershellCoreProfileGenerator.cpp" />
<ClCompile Include="../WslDistroGenerator.cpp" />

View File

@@ -8,7 +8,6 @@
</ItemGroup>
<ItemGroup>
<ClCompile Include="../init.cpp" />
<ClCompile Include="../Utils.cpp" />
<ClCompile Include="pch.cpp" />
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
<ClCompile Include="../AzureCloudShellGenerator.cpp">
@@ -50,9 +49,6 @@
<ClCompile Include="$(OpenConsoleDir)\dep\jsoncpp\jsoncpp.cpp">
<Filter>json</Filter>
</ClCompile>
<ClCompile Include="../JsonUtils.cpp">
<Filter>json</Filter>
</ClCompile>
<ClCompile Include="../Tab.cpp">
<Filter>tab</Filter>
</ClCompile>
@@ -92,6 +88,9 @@
<ClInclude Include="../GlobalAppSettings.h">
<Filter>settings</Filter>
</ClInclude>
<ClInclude Include="../TerminalSettingsSerializationHelpers.h">
<Filter>settings</Filter>
</ClInclude>
<ClInclude Include="../KeyChordSerialization.h">
<Filter>settings</Filter>
</ClInclude>
@@ -199,4 +198,4 @@
<Filter>app</Filter>
</ApplicationDefinition>
</ItemGroup>
</Project>
</Project>

View File

@@ -107,62 +107,68 @@ int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
// 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);
auto mainLoop = []() {
// !!! 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 AppHost 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.
AppHost host;
// Create the AppHost 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.
AppHost host;
// Initialize the xaml content. This must be called AFTER the
// WindowsXamlManager is initialized.
host.Initialize();
// Initialize the xaml content. This must be called AFTER the
// WindowsXamlManager is initialized.
host.Initialize();
MSG message;
MSG message;
while (GetMessage(&message, nullptr, 0, 0))
{
// GH#638 (Pressing F7 brings up both the history AND a caret browsing message)
// The Xaml input stack doesn't allow an application to suppress the "caret browsing"
// dialog experience triggered when you press F7. Official recommendation from the Xaml
// team is to catch F7 before we hand it off.
// AppLogic contains an ad-hoc implementation of event bubbling for a runtime classes
// implementing a custom IF7Listener interface.
// If the recipient of IF7Listener::OnF7Pressed suggests that the F7 press has, in fact,
// been handled we can discard the message before we even translate it.
if (_messageIsF7Keypress(message))
while (GetMessage(&message, nullptr, 0, 0))
{
if (host.OnDirectKeyEvent(VK_F7, true))
// GH#638 (Pressing F7 brings up both the history AND a caret browsing message)
// The Xaml input stack doesn't allow an application to suppress the "caret browsing"
// dialog experience triggered when you press F7. Official recommendation from the Xaml
// team is to catch F7 before we hand it off.
// AppLogic contains an ad-hoc implementation of event bubbling for a runtime classes
// implementing a custom IF7Listener interface.
// If the recipient of IF7Listener::OnF7Pressed suggests that the F7 press has, in fact,
// been handled we can discard the message before we even translate it.
if (_messageIsF7Keypress(message))
{
// The application consumed the F7. Don't let Xaml get it.
continue;
if (host.OnDirectKeyEvent(VK_F7, true))
{
// The application consumed the F7. Don't let Xaml get it.
continue;
}
}
}
// GH#6421 - System XAML will never send an Alt KeyUp event. So, similar
// to how we'll steal the F7 KeyDown above, we'll steal the Alt KeyUp
// here, and plumb it through.
if (_messageIsAltKeyup(message))
{
// Let's pass <Alt> to the application
if (host.OnDirectKeyEvent(VK_MENU, false))
// GH#6421 - System XAML will never send an Alt KeyUp event. So, similar
// to how we'll steal the F7 KeyDown above, we'll steal the Alt KeyUp
// here, and plumb it through.
if (_messageIsAltKeyup(message))
{
// The application consumed the Alt. Don't let Xaml get it.
continue;
// Let's pass <Alt> to the application
if (host.OnDirectKeyEvent(VK_MENU, false))
{
// The application consumed the Alt. Don't let Xaml get it.
continue;
}
}
}
TranslateMessage(&message);
DispatchMessage(&message);
}
TranslateMessage(&message);
DispatchMessage(&message);
}
};
std::thread t{ mainLoop };
mainLoop();
return 0;
}

View File

@@ -28,8 +28,6 @@ namespace TerminalAppUnitTests
TEST_METHOD(ParseSimpleColorScheme);
TEST_METHOD(ProfileGeneratesGuid);
TEST_METHOD(TestWrongValueType);
TEST_CLASS_SETUP(ClassSetup)
{
InitializeJsonReader();
@@ -169,58 +167,4 @@ namespace TerminalAppUnitTests
VERIFY_ARE_EQUAL(profile3.GetGuid(), nullGuid);
VERIFY_ARE_EQUAL(profile4.GetGuid(), cmdGuid);
}
void JsonTests::TestWrongValueType()
{
// This json blob has a whole bunch of settings with the wrong value
// types - strings for int values, ints for strings, floats for ints,
// etc. When we encounter data that's the wrong data type, we should
// gracefully ignore it, as opposed to throwing an exception, causing us
// to fail to load the settings at all.
const std::string settings0String{ R"(
{
"defaultProfile" : "{00000000-1111-0000-0000-000000000000}",
"profiles": [
{
"guid" : "{00000000-1111-0000-0000-000000000000}",
"acrylicOpacity" : "0.5",
"closeOnExit" : "true",
"fontSize" : "10",
"historySize" : 1234.5678,
"padding" : 20,
"snapOnInput" : "false",
"icon" : 4,
"backgroundImageOpacity": false,
"useAcrylic" : 14
}
]
})" };
const auto settings0Json = VerifyParseSucceeded(settings0String);
CascadiaSettings settings;
settings._ParseJsonString(settings0String, false);
// We should not throw an exception trying to parse the settings here.
settings.LayerJson(settings._userSettings);
VERIFY_ARE_EQUAL(1u, settings._profiles.size());
auto& profile = settings._profiles.at(0);
Profile defaults{};
VERIFY_ARE_EQUAL(defaults._acrylicTransparency, profile._acrylicTransparency);
VERIFY_ARE_EQUAL(defaults._closeOnExitMode, profile._closeOnExitMode);
VERIFY_ARE_EQUAL(defaults._fontSize, profile._fontSize);
VERIFY_ARE_EQUAL(defaults._historySize, profile._historySize);
// A 20 as an int can still be treated as a json string
VERIFY_ARE_EQUAL(L"20", profile._padding);
VERIFY_ARE_EQUAL(defaults._snapOnInput, profile._snapOnInput);
// 4 is a valid string value
VERIFY_ARE_EQUAL(L"4", profile._icon);
// false is not a valid optional<double>
VERIFY_IS_FALSE(profile._backgroundImageOpacity.has_value());
VERIFY_ARE_EQUAL(defaults._useAcrylic, profile._useAcrylic);
}
}

View File

@@ -3,7 +3,7 @@
#include "precomp.h"
#include "../TerminalApp/JsonUtilsNew.h"
#include "../TerminalApp/JsonUtils.h"
using namespace Microsoft::Console;
using namespace WEX::Logging;

View File

@@ -254,6 +254,145 @@ void VtIoTests::DtorTestStackAllocMany()
}
}
class MockRenderData : public IRenderData, IUiaData
{
public:
Microsoft::Console::Types::Viewport GetViewport() noexcept override
{
return Microsoft::Console::Types::Viewport{};
}
COORD GetTextBufferEndPosition() const noexcept override
{
return COORD{};
}
const TextBuffer& GetTextBuffer() noexcept override
{
FAIL_FAST_HR(E_NOTIMPL);
}
const FontInfo& GetFontInfo() noexcept override
{
FAIL_FAST_HR(E_NOTIMPL);
}
std::vector<Microsoft::Console::Types::Viewport> GetSelectionRects() noexcept override
{
return std::vector<Microsoft::Console::Types::Viewport>{};
}
void LockConsole() noexcept override
{
}
void UnlockConsole() noexcept override
{
}
const TextAttribute GetDefaultBrushColors() noexcept override
{
return TextAttribute{};
}
std::pair<COLORREF, COLORREF> GetAttributeColors(const TextAttribute& /*attr*/) const noexcept override
{
return std::make_pair(COLORREF{}, COLORREF{});
}
COORD GetCursorPosition() const noexcept override
{
return COORD{};
}
bool IsCursorVisible() const noexcept override
{
return false;
}
bool IsCursorOn() const noexcept override
{
return false;
}
ULONG GetCursorHeight() const noexcept override
{
return 42ul;
}
CursorType GetCursorStyle() const noexcept override
{
return CursorType::FullBox;
}
ULONG GetCursorPixelWidth() const noexcept override
{
return 12ul;
}
COLORREF GetCursorColor() const noexcept override
{
return COLORREF{};
}
bool IsCursorDoubleWidth() const override
{
return false;
}
bool IsScreenReversed() const noexcept override
{
return false;
}
const std::vector<RenderOverlay> GetOverlays() const noexcept override
{
return std::vector<RenderOverlay>{};
}
const bool IsGridLineDrawingAllowed() noexcept override
{
return false;
}
const std::wstring GetConsoleTitle() const noexcept override
{
return std::wstring{};
}
const bool IsSelectionActive() const override
{
return false;
}
const bool IsBlockSelection() const noexcept override
{
return false;
}
void ClearSelection() override
{
}
void SelectNewRegion(const COORD /*coordStart*/, const COORD /*coordEnd*/) override
{
}
const COORD GetSelectionAnchor() const noexcept
{
return COORD{};
}
const COORD GetSelectionEnd() const noexcept
{
return COORD{};
}
void ColorSelection(const COORD /*coordSelectionStart*/, const COORD /*coordSelectionEnd*/, const TextAttribute /*attr*/)
{
}
};
void VtIoTests::RendererDtorAndThread()
{
Log::Comment(NoThrowString().Format(
@@ -261,9 +400,10 @@ void VtIoTests::RendererDtorAndThread()
for (int i = 0; i < 16; ++i)
{
auto data = std::make_unique<MockRenderData>();
auto thread = std::make_unique<Microsoft::Console::Render::RenderThread>();
auto* pThread = thread.get();
auto pRenderer = std::make_unique<Microsoft::Console::Render::Renderer>(nullptr, nullptr, 0, std::move(thread));
auto pRenderer = std::make_unique<Microsoft::Console::Render::Renderer>(data.get(), nullptr, 0, std::move(thread));
VERIFY_SUCCEEDED(pThread->Initialize(pRenderer.get()));
// Sleep for a hot sec to make sure the thread starts before we enable painting
// If you don't, the thread might wait on the paint enabled event AFTER
@@ -286,9 +426,10 @@ void VtIoTests::RendererDtorAndThreadAndDx()
for (int i = 0; i < 16; ++i)
{
auto data = std::make_unique<MockRenderData>();
auto thread = std::make_unique<Microsoft::Console::Render::RenderThread>();
auto* pThread = thread.get();
auto pRenderer = std::make_unique<Microsoft::Console::Render::Renderer>(nullptr, nullptr, 0, std::move(thread));
auto pRenderer = std::make_unique<Microsoft::Console::Render::Renderer>(data.get(), nullptr, 0, std::move(thread));
VERIFY_SUCCEEDED(pThread->Initialize(pRenderer.get()));
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();

View File

@@ -15,6 +15,7 @@
#include "til/rectangle.h"
#include "til/bitmap.h"
#include "til/u8u16convert.h"
#include "til/spsc.h"
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{

643
src/inc/til/spsc.h Normal file
View File

@@ -0,0 +1,643 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
// til::spsc::details::arc requires std::atomic<size_type>::wait() and ::notify_one() and at the time of writing no
// STL supports these. Since both Windows and Linux offer a Futex implementation we can easily implement this though.
// On other platforms we fall back to using a std::condition_variable.
#if __cpp_lib_atomic_wait >= 201907
#define _TIL_SPSC_DETAIL_POSITION_IMPL_NATIVE 1
#elif defined(_WIN32_WINNT) && _WIN32_WINNT >= _WIN32_WINNT_WIN8
#define _TIL_SPSC_DETAIL_POSITION_IMPL_WIN 1
#elif __linux__
#define _TIL_SPSC_DETAIL_POSITION_IMPL_LINUX 1
#else
#define _TIL_SPSC_DETAIL_POSITION_IMPL_FALLBACK 1
#endif
// til: Terminal Implementation Library. Also: "Today I Learned".
// spsc: Single Producer Single Consumer. A SPSC queue/channel sends data from exactly one sender to one receiver.
namespace til::spsc
{
using size_type = uint32_t;
namespace details
{
static constexpr size_type position_mask = std::numeric_limits<size_type>::max() >> 2u; // 0b00111....
static constexpr size_type revolution_flag = 1u << (std::numeric_limits<size_type>::digits - 2u); // 0b01000....
static constexpr size_type drop_flag = 1u << (std::numeric_limits<size_type>::digits - 1u); // 0b10000....
struct block_initially_policy
{
using _spsc_policy = int;
static constexpr bool _block_forever = false;
};
struct block_forever_policy
{
using _spsc_policy = int;
static constexpr bool _block_forever = true;
};
template<typename WaitPolicy>
using enable_if_wait_policy_t = typename std::remove_reference_t<WaitPolicy>::_spsc_policy;
#if _TIL_SPSC_DETAIL_POSITION_IMPL_NATIVE
using atomic_size_type = std::atomic<size_type>;
#else
// atomic_size_type is a fallback if native std::atomic<size_type>::wait()
// and ::notify_one() methods are unavailable in the STL.
struct atomic_size_type
{
size_type load(std::memory_order order) const noexcept
{
return _value.load(order);
}
void store(size_type desired, std::memory_order order) noexcept
{
#if _TIL_SPSC_DETAIL_POSITION_IMPL_FALLBACK
// We must use a lock here to prevent us from modifying the value
// in between wait() reading the value and the thread being suspended.
std::lock_guard<std::mutex> lock{ _m };
#endif
_value.store(desired, order);
}
void wait(size_type old, [[maybe_unused]] std::memory_order order) const noexcept
{
#if _TIL_SPSC_DETAIL_POSITION_IMPL_WIN
#pragma warning(suppress : 26492) // Don't use const_cast to cast away const or volatile
WaitOnAddress(const_cast<std::atomic<size_type>*>(&_value), &old, sizeof(_value), INFINITE);
#elif _TIL_SPSC_DETAIL_POSITION_IMPL_LINUX
futex(FUTEX_WAIT_PRIVATE, old);
#elif _TIL_SPSC_DETAIL_POSITION_IMPL_FALLBACK
std::unique_lock<std::mutex> lock{ _m };
_cv.wait(lock, [&]() { return _value.load(order) != old; });
#endif
}
void notify_one() noexcept
{
#if _TIL_SPSC_DETAIL_POSITION_IMPL_WIN
WakeByAddressSingle(&_value);
#elif _TIL_SPSC_DETAIL_POSITION_IMPL_LINUX
futex(FUTEX_WAKE_PRIVATE, 1);
#elif _TIL_SPSC_DETAIL_POSITION_IMPL_FALLBACK
_cv.notify_one();
#endif
}
private:
#if _TIL_SPSC_DETAIL_POSITION_IMPL_LINUX
inline void futex(int futex_op, size_type val) const noexcept
{
// See: https://man7.org/linux/man-pages/man2/futex.2.html
static_assert(sizeof(std::atomic<size_type>) == 4);
syscall(SYS_futex, &_value, futex_op, val, nullptr, nullptr, 0);
}
#endif
std::atomic<size_type> _value{ 0 };
#if _TIL_SPSC_DETAIL_POSITION_IMPL_FALLBACK
private:
std::mutex _m;
std::condition_variable _cv;
#endif
};
#endif
template<typename T>
inline T* alloc_raw_memory(size_t size)
{
constexpr auto alignment = alignof(T);
if constexpr (alignment <= __STDCPP_DEFAULT_NEW_ALIGNMENT__)
{
return static_cast<T*>(::operator new(size));
}
else
{
return static_cast<T*>(::operator new(size, std::align_val_t(alignment)));
}
}
template<typename T>
inline void free_raw_memory(T* ptr) noexcept
{
constexpr auto alignment = alignof(T);
if constexpr (alignment <= __STDCPP_DEFAULT_NEW_ALIGNMENT__)
{
::operator delete(ptr);
}
else
{
::operator delete(ptr, std::align_val_t(alignment));
}
}
struct acquisition
{
// The index range [begin, end) is the range of slots in the array returned by
// arc<T>::data() that may be written to / read from respectively.
// If a range has been successfully acquired "end > begin" is true. end thus can't be 0.
size_type begin;
size_type end;
// Upon release() of an acquisition, next is the value that's written to the consumer/producer position.
// It's basically the same as end, but with the revolution flag mixed in.
// If end is equal to capacity, next will be 0 (mixed with the next revolution flag).
size_type next;
// If the other side of the queue hasn't been destroyed yet, alive will be true.
bool alive;
constexpr acquisition(size_type begin, size_type end, size_type next, bool alive) :
begin(begin),
end(end),
next(next),
alive(alive)
{
}
};
// The following assumes you know what ring/circular buffers are. You can read about them here:
// https://en.wikipedia.org/wiki/Circular_buffer
//
// Furthermore the implementation solves a problem known as the producer-consumer problem:
// https://en.wikipedia.org/wiki/Producer%E2%80%93consumer_problem
//
// arc follows the classic spsc design and manages a ring buffer with two positions: _producer and _consumer.
// They contain the position the producer / consumer will next write to / read from respectively.
// As usual with ring buffers, these positions are modulo to the _capacity of the underlying buffer.
// The producer's writable range is [_producer, _consumer) and the consumer's readable is [_consumer, _producer).
//
// After you wrote the numbers 0 to 6 into a queue of size 10, a typical state of the ring buffer might be:
// [ 0 | 1 | 2 | 3 | 4 | 5 | 6 | _ | _ | _ | _ ]
// ^ ^ ^
// _consumer = 0 _producer = 7 _capacity = 10
//
// As you can see the readable range currently is [_consumer, _producer) = [0, 7).
// The remaining writable range on the other hand is [_producer, _consumer) = [7, 0).
// Wait, what? [7, 0)? How does that work? As all positions are modulo _capacity, 0 mod 10 is the same as 10 mod 10.
// If we only want to read forward in the buffer [7, 0) is thus the same as [7, 10).
//
// If we read 3 items from the queue the contents will be:
// [ _ | _ | _ | 3 | 4 | 5 | 6 | _ | _ | _ | _ ]
// ^ ^
// _consumer = 3 _producer = 7
//
// Now the writable range is still [_producer, _consumer), but it wraps around the end of the ring buffer.
// In this case arc will split the range in two and return each separately in acquire().
// The first returned range will be [_producer, _capacity) and the second [0, _consumer).
// The same logic applies if the readable range wraps around the end of the ring buffer.
//
// As these are symmetric, the logic for acquiring and releasing ranges is the same for both sides.
// The producer will acquire() and release() ranges with its own position as "mine" and the consumer's
// position as "theirs". These arguments are correspondingly flipped for the consumer.
//
// As part of the producer-consumer problem, the producer cannot write more values ahead of the
// consumer than the buffer's capacity. Since both positions are modulo to the capacity we can only
// determine positional differences smaller than the capacity. Due to that both producer and
// consumer store a "revolution_flag" as the second highest bit within their positions.
// This bit is flipped each time the producer/consumer wrap around the end of the ring buffer.
// If the positions are identical, except for their "revolution_flag" value, the producer thus must
// be capacity-many positions ahead of the consumer and must wait until items have been consumed.
//
// Inversely the consumer must wait until the producer has written at least one value ahead.
// This can be detected by checking whether the positions are identical including the revolution_flag.
template<typename T>
struct arc
{
explicit arc(size_type capacity) noexcept :
_data(alloc_raw_memory<T>(size_t(capacity) * sizeof(T))),
_capacity(capacity)
{
}
~arc()
{
auto beg = _consumer.load(std::memory_order_acquire);
auto end = _producer.load(std::memory_order_acquire);
auto differentRevolution = ((beg ^ end) & revolution_flag) != 0;
beg &= position_mask;
end &= position_mask;
// The producer position will always be ahead of the consumer, but since we're dealing
// with a ring buffer the producer may be wrapped around the end of the buffer.
// We thus need to deal with 3 potential cases:
// * No valid data.
// If both positions including their revolution bits are identical.
// * Valid data in the middle of the ring buffer.
// If _producer > _consumer.
// * Valid data at both ends of the ring buffer.
// If the revolution bits differ, even if the positions are otherwise identical,
// which they might be if the channel contains exactly as many values as its capacity.
if (end > beg)
{
std::destroy(_data + beg, _data + end);
}
else if (differentRevolution)
{
std::destroy(_data, _data + end);
std::destroy(_data + beg, _data + _capacity);
}
free_raw_memory(_data);
}
void drop_producer()
{
drop(_producer);
}
void drop_consumer()
{
drop(_consumer);
}
acquisition producer_acquire(size_type slots, bool blocking) noexcept
{
return acquire(_producer, _consumer, revolution_flag, slots, blocking);
}
void producer_release(acquisition acquisition) noexcept
{
release(_producer, acquisition);
}
acquisition consumer_acquire(size_type slots, bool blocking) noexcept
{
return acquire(_consumer, _producer, 0, slots, blocking);
}
void consumer_release(acquisition acquisition) noexcept
{
release(_consumer, acquisition);
}
T* data() const noexcept
{
return _data;
}
private:
void drop(atomic_size_type& mine)
{
// Signal the other side we're dropped. See acquire() for the handling of the drop_flag.
// We don't need to use release ordering like release() does as each call to
// any of the producer/consumer methods already results in a call to release().
// Another release ordered write can't possibly synchronize any more data anyways at this point.
const auto myPos = mine.load(std::memory_order_relaxed);
mine.store(myPos | drop_flag, std::memory_order_relaxed);
mine.notify_one();
// The first time SPSCBase is dropped (destroyed) we'll set
// the flag to true and get false, causing us to return early.
// Only the second time we'll get true.
// --> The contents are only deleted when both sides have been dropped.
if (_eitherSideDropped.exchange(true, std::memory_order_relaxed))
{
delete this;
}
}
// NOTE: waitMask MUST be either 0 (consumer) or revolution_flag (producer).
acquisition acquire(atomic_size_type& mine, atomic_size_type& theirs, size_type waitMask, size_type slots, bool blocking) noexcept
{
size_type myPos = mine.load(std::memory_order_relaxed);
size_type theirPos;
while (true)
{
// This acquire read synchronizes with the release write in release().
theirPos = theirs.load(std::memory_order_acquire);
if ((myPos ^ theirPos) != waitMask)
{
break;
}
if (!blocking)
{
return {
0,
0,
0,
true,
};
}
theirs.wait(theirPos, std::memory_order_relaxed);
}
// If the other side's position contains a drop flag, as a X -> we need to...
// * producer -> stop immediately
// FYI: isProducer == (waitMask != 0).
// * consumer -> finish consuming all values and then stop
// We're finished if the only difference between our
// and the other side's position is the drop flag.
if ((theirPos & drop_flag) != 0 && (waitMask != 0 || (myPos ^ theirPos) == drop_flag))
{
return {
0,
0,
0,
false,
};
}
auto begin = myPos & position_mask;
auto end = theirPos & position_mask;
// [begin, end) is the writable/readable range for the producer/consumer.
// The following detects whether we'd be wrapping around the end of the ring buffer
// and splits the range into the first half [mine, _capacity).
// If acquire() is called again it'll return [0, theirs).
end = end > begin ? end : _capacity;
// Of course we also need to ensure to not return more than we've been asked for.
end = std::min(end, begin + slots);
// "next" will contain the value that's stored into "mine" when release() is called.
// It's basically the same as "end", but with the revolution flag spliced in.
// If we acquired the range [mine, _capacity) "end" will equal _capacity
// and thus wrap around the ring buffer. The next value for "mine" is thus the
// position zero | the flipped "revolution" (and 0 | x == x).
auto revolution = myPos & revolution_flag;
auto next = end != _capacity ? end | revolution : revolution ^ revolution_flag;
return {
begin,
end,
next,
true,
};
}
void release(atomic_size_type& mine, acquisition acquisition) noexcept
{
// This release write synchronizes with the acquire read in acquire().
mine.store(acquisition.next, std::memory_order_release);
mine.notify_one();
}
T* const _data;
const size_type _capacity;
std::atomic<bool> _eitherSideDropped{ false };
atomic_size_type _producer;
atomic_size_type _consumer;
};
inline void validate_size(size_t v)
{
if (v > static_cast<size_t>(position_mask))
{
throw std::overflow_error{ "size too large for spsc" };
}
}
}
// Block until at least one item has been written into the sender / read from the receiver.
inline constexpr details::block_initially_policy block_initially{};
// Block until all items have been written into the sender / read from the receiver.
inline constexpr details::block_forever_policy block_forever{};
template<typename T>
struct producer
{
explicit producer(details::arc<T>* arc) noexcept :
_arc(arc) {}
producer<T>(const producer<T>&) = delete;
producer<T>& operator=(const producer<T>&) = delete;
producer(producer<T>&& other) noexcept
{
drop();
_arc = std::exchange(other._arc, nullptr);
}
producer<T>& operator=(producer<T>&& other) noexcept
{
drop();
_arc = std::exchange(other._arc, nullptr);
}
~producer()
{
drop();
}
// emplace constructs an item in-place at the end of the queue.
// It returns true, if the item was successfully placed within the queue.
// The return value will be false, if the consumer is gone.
template<typename... Args>
bool emplace(Args&&... args) const
{
auto acquisition = _arc->producer_acquire(1, true);
if (!acquisition.end)
{
return false;
}
auto data = _arc->data();
auto begin = data + acquisition.begin;
new (begin) T(std::forward<Args>(args)...);
_arc->producer_release(acquisition);
return true;
}
template<typename InputIt>
std::pair<size_t, bool> push(InputIt first, InputIt last) const
{
return push_n(block_forever, first, std::distance(first, last));
}
// push writes the items between first and last into the queue.
// The amount of successfully written items is returned as the first pair field.
// The second pair field will be false if the consumer is gone.
template<typename WaitPolicy, typename InputIt, details::enable_if_wait_policy_t<WaitPolicy> = 0>
std::pair<size_t, bool> push(WaitPolicy&& policy, InputIt first, InputIt last) const
{
return push_n(std::forward<WaitPolicy>(policy), first, std::distance(first, last));
}
template<typename InputIt>
std::pair<size_t, bool> push_n(InputIt first, size_t count) const
{
return push_n(block_forever, first, count);
}
// push_n writes count items from first into the queue.
// The amount of successfully written items is returned as the first pair field.
// The second pair field will be false if the consumer is gone.
template<typename WaitPolicy, typename InputIt, details::enable_if_wait_policy_t<WaitPolicy> = 0>
std::pair<size_t, bool> push_n(WaitPolicy&&, InputIt first, size_t count) const
{
details::validate_size(count);
const auto data = _arc->data();
auto remaining = static_cast<size_type>(count);
auto blocking = true;
auto ok = true;
while (remaining != 0)
{
auto acquisition = _arc->producer_acquire(remaining, blocking);
if (!acquisition.end)
{
ok = acquisition.alive;
break;
}
const auto begin = data + acquisition.begin;
const auto got = acquisition.end - acquisition.begin;
std::uninitialized_copy_n(first, got, begin);
first += got;
remaining -= got;
_arc->producer_release(acquisition);
if constexpr (!std::remove_reference_t<WaitPolicy>::_block_forever)
{
blocking = false;
}
}
return { count - remaining, ok };
}
private:
void drop()
{
if (_arc)
{
_arc->drop_producer();
}
}
details::arc<T>* _arc = nullptr;
};
template<typename T>
struct consumer
{
explicit consumer(details::arc<T>* arc) noexcept :
_arc(arc) {}
consumer<T>(const consumer<T>&) = delete;
consumer<T>& operator=(const consumer<T>&) = delete;
consumer(consumer<T>&& other) noexcept
{
drop();
_arc = std::exchange(other._arc, nullptr);
}
consumer<T>& operator=(consumer<T>&& other) noexcept
{
drop();
_arc = std::exchange(other._arc, nullptr);
}
~consumer()
{
drop();
}
// pop returns the next item in the queue, or std::nullopt if the producer is gone.
std::optional<T> pop() const
{
auto acquisition = _arc->consumer_acquire(1, true);
if (!acquisition.end)
{
return std::nullopt;
}
auto data = _arc->data();
auto begin = data + acquisition.begin;
auto item = std::move(*begin);
std::destroy_at(begin);
_arc->consumer_release(acquisition);
return item;
}
template<typename OutputIt>
std::pair<size_t, bool> pop_n(OutputIt first, size_t count) const
{
return pop_n(block_forever, first, count);
}
// pop_n reads up to count items into first.
// The amount of successfully read items is returned as the first pair field.
// The second pair field will be false if the consumer is gone.
template<typename WaitPolicy, typename OutputIt, details::enable_if_wait_policy_t<WaitPolicy> = 0>
std::pair<size_t, bool> pop_n(WaitPolicy&&, OutputIt first, size_t count) const
{
details::validate_size(count);
const auto data = _arc->data();
auto remaining = static_cast<size_type>(count);
auto blocking = true;
auto ok = true;
while (remaining != 0)
{
auto acquisition = _arc->consumer_acquire(remaining, blocking);
if (!acquisition.end)
{
ok = acquisition.alive;
break;
}
auto beg = data + acquisition.begin;
auto end = data + acquisition.end;
auto got = acquisition.end - acquisition.begin;
first = std::move(beg, end, first);
std::destroy(beg, end);
remaining -= got;
_arc->consumer_release(acquisition);
if constexpr (!std::remove_reference_t<WaitPolicy>::_block_forever)
{
blocking = false;
}
}
return { count - remaining, ok };
}
private:
void drop()
{
if (_arc)
{
_arc->drop_consumer();
}
}
details::arc<T>* _arc = nullptr;
};
// channel returns a bounded, lock-free, single-producer, single-consumer
// FIFO queue ("channel") with the given maximum capacity.
template<typename T>
std::pair<producer<T>, consumer<T>> channel(uint32_t capacity)
{
if (capacity == 0)
{
throw std::invalid_argument{ "invalid capacity" };
}
const auto arc = new details::arc<T>(capacity);
return { std::piecewise_construct, std::forward_as_tuple(arc), std::forward_as_tuple(arc) };
}
}

View File

@@ -26,13 +26,12 @@ Renderer::Renderer(IRenderData* pData,
_In_reads_(cEngines) IRenderEngine** const rgpEngines,
const size_t cEngines,
std::unique_ptr<IRenderThread> thread) :
_pData(pData),
_pData(THROW_HR_IF_NULL(E_INVALIDARG, pData)),
_pThread{ std::move(thread) },
_destructing{ false },
_clusterBuffer{}
_clusterBuffer{},
_viewport{ pData->GetViewport() }
{
_srViewportPrevious = { 0 };
for (size_t i = 0; i < cEngines; i++)
{
IRenderEngine* engine = rgpEngines[i];
@@ -208,7 +207,7 @@ void Renderer::TriggerSystemRedraw(const RECT* const prcDirtyClient)
// - <none>
void Renderer::TriggerRedraw(const Viewport& region)
{
Viewport view = _pData->GetViewport();
Viewport view = _viewport;
SMALL_RECT srUpdateRegion = region.ToExclusive();
if (view.TrimToViewport(&srUpdateRegion))
@@ -357,7 +356,7 @@ void Renderer::TriggerSelection()
// - True if something changed and we scrolled. False otherwise.
bool Renderer::_CheckViewportAndScroll()
{
SMALL_RECT const srOldViewport = _srViewportPrevious;
SMALL_RECT const srOldViewport = _viewport.ToInclusive();
SMALL_RECT const srNewViewport = _pData->GetViewport().ToInclusive();
COORD coordDelta;
@@ -369,7 +368,7 @@ bool Renderer::_CheckViewportAndScroll()
LOG_IF_FAILED(engine->UpdateViewport(srNewViewport));
}
_srViewportPrevious = srNewViewport;
_viewport = Viewport::FromInclusive(srNewViewport);
// If we're keeping some buffers between calls, let them know about the viewport size
// so they can prepare the buffers for changes to either preallocate memory at once

View File

@@ -120,7 +120,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT _PerformScrolling(_In_ IRenderEngine* const pEngine);
SMALL_RECT _srViewportPrevious;
Microsoft::Console::Types::Viewport _viewport;
static constexpr float _shrinkThreshold = 0.8f;
std::vector<Cluster> _clusterBuffer;

View File

@@ -0,0 +1,195 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "WexTestClass.h"
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
struct drop_indicator
{
explicit drop_indicator(int& counter) noexcept :
_counter(&counter) {}
drop_indicator(const drop_indicator&) = delete;
drop_indicator& operator=(const drop_indicator&) = delete;
drop_indicator(drop_indicator&& other) noexcept
{
_counter = std::exchange(other._counter, nullptr);
}
drop_indicator& operator=(drop_indicator&& other) noexcept
{
_counter = std::exchange(other._counter, nullptr);
}
~drop_indicator()
{
if (_counter)
{
++*_counter;
}
}
private:
int* _counter = nullptr;
};
template<typename T>
void drop(T&& val)
{
auto _ = std::move(val);
}
class SPSCTests
{
BEGIN_TEST_CLASS(SPSCTests)
TEST_CLASS_PROPERTY(L"TestTimeout", L"0:0:10") // 10s timeout
END_TEST_CLASS()
TEST_METHOD(DropEmptyTest);
TEST_METHOD(DropSameRevolutionTest);
TEST_METHOD(DropDifferentRevolutionTest);
TEST_METHOD(IntegrationTest);
};
void SPSCTests::DropEmptyTest()
{
auto [tx, rx] = til::spsc::channel<drop_indicator>(5);
int counter = 0;
for (int i = 0; i < 5; ++i)
{
tx.emplace(counter);
}
VERIFY_ARE_EQUAL(counter, 0);
for (int i = 0; i < 5; ++i)
{
rx.pop();
}
VERIFY_ARE_EQUAL(counter, 5);
for (int i = 0; i < 3; ++i)
{
tx.emplace(counter);
}
VERIFY_ARE_EQUAL(counter, 5);
drop(tx);
VERIFY_ARE_EQUAL(counter, 5);
for (int i = 0; i < 3; ++i)
{
rx.pop();
}
VERIFY_ARE_EQUAL(counter, 8);
drop(rx);
VERIFY_ARE_EQUAL(counter, 8);
}
void SPSCTests::DropSameRevolutionTest()
{
auto [tx, rx] = til::spsc::channel<drop_indicator>(5);
int counter = 0;
for (int i = 0; i < 5; ++i)
{
tx.emplace(counter);
}
VERIFY_ARE_EQUAL(counter, 0);
drop(tx);
VERIFY_ARE_EQUAL(counter, 0);
for (int i = 0; i < 3; ++i)
{
rx.pop();
}
VERIFY_ARE_EQUAL(counter, 3);
drop(rx);
VERIFY_ARE_EQUAL(counter, 5);
}
void SPSCTests::DropDifferentRevolutionTest()
{
auto [tx, rx] = til::spsc::channel<drop_indicator>(5);
int counter = 0;
for (int i = 0; i < 4; ++i)
{
tx.emplace(counter);
}
VERIFY_ARE_EQUAL(counter, 0);
for (int i = 0; i < 3; ++i)
{
rx.pop();
}
VERIFY_ARE_EQUAL(counter, 3);
for (int i = 0; i < 4; ++i)
{
tx.emplace(counter);
}
VERIFY_ARE_EQUAL(counter, 3);
// At this point we emplace()d 8 items and pop()ed 3 in a channel with a capacity of 5.
// Both producer and consumer positions will be 3 and only differ in their revolution flag.
// This ensures that the arc<T> destructor works even if the
// two positions within the circular buffer are identical (modulo the capacity).
drop(tx);
VERIFY_ARE_EQUAL(counter, 3);
drop(rx);
VERIFY_ARE_EQUAL(counter, 8);
}
void SPSCTests::IntegrationTest()
{
auto [tx, rx] = til::spsc::channel<int>(7);
std::thread t([tx = std::move(tx)]() {
std::array<int, 11> buffer{};
std::generate(buffer.begin(), buffer.end(), [v = 0]() mutable { return v++; });
for (int i = 0; i < 37; ++i)
{
tx.emplace(i);
}
for (int i = 0; i < 3; ++i)
{
tx.push(buffer.begin(), buffer.end());
}
});
std::array<int, 11> buffer{};
for (int i = 0; i < 3; ++i)
{
rx.pop_n(buffer.data(), buffer.size());
for (int j = 0; j < 11; ++j)
{
VERIFY_ARE_EQUAL(i * 11 + j, buffer[j]);
}
}
for (int i = 33; i < 37; ++i)
{
auto actual = rx.pop();
VERIFY_ARE_EQUAL(i, actual);
}
for (int i = 0; i < 33; ++i)
{
auto expected = i % 11;
auto actual = rx.pop();
VERIFY_ARE_EQUAL(expected, actual);
}
t.join();
}

View File

@@ -22,6 +22,7 @@
<ClCompile Include="..\precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="SPSCTests.cpp" />
<ClCompile Include="u8u16convertTests.cpp" />
</ItemGroup>
<ItemGroup>

View File

@@ -15,6 +15,7 @@
<ClCompile Include="OperatorTests.cpp" />
<ClCompile Include="MathTests.cpp" />
<ClCompile Include="BaseTests.cpp" />
<ClCompile Include="SPSCTests.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\precomp.h" />

View File

@@ -0,0 +1,288 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "AppHost.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::Controls;
using namespace winrt::Windows::UI::Xaml::Hosting;
using namespace winrt::Windows::Foundation::Numerics;
using namespace ::Microsoft::Console;
using namespace ::Microsoft::Console::Types;
AppHost::AppHost() noexcept :
_window{ nullptr }
{
_window = std::make_unique<IslandWindow>();
// Tell the window to callback to us when it's about to handle a WM_CREATE
auto pfn = std::bind(&AppHost::_HandleCreateWindow,
this,
std::placeholders::_1,
std::placeholders::_2);
_window->SetCreateCallback(pfn);
_window->MakeWindow();
}
AppHost::~AppHost()
{
// destruction order is important for proper teardown here
_window = 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:
// - <none>
// Return Value:
// - <none>
void AppHost::Initialize()
{
_window->Initialize();
////////////////////////////////////////////////////////////////////////////
// Initialize the UI
////////////////////////////////////////////////////////////////////////////
_rootGrid = Grid();
_swapchainsGrid = Grid();
_swp0 = SwapChainPanel();
_swp1 = SwapChainPanel();
_swp2 = SwapChainPanel();
_swp3 = SwapChainPanel();
// Set up the content of the application. If the app has a custom titlebar,
// set that content as well.
{
auto firstRowDef = Controls::RowDefinition();
firstRowDef.Height(GridLengthHelper::FromValueAndType(1, GridUnitType::Star));
auto secondRowDef = Controls::RowDefinition();
secondRowDef.Height(GridLengthHelper::FromValueAndType(9, GridUnitType::Star));
_rootGrid.RowDefinitions().Append(firstRowDef);
_rootGrid.RowDefinitions().Append(secondRowDef);
}
{
auto firstRowDef = Controls::RowDefinition();
firstRowDef.Height(GridLengthHelper::FromValueAndType(1, GridUnitType::Star));
auto secondRowDef = Controls::RowDefinition();
secondRowDef.Height(GridLengthHelper::FromValueAndType(1, GridUnitType::Star));
_swapchainsGrid.RowDefinitions().Append(firstRowDef);
_swapchainsGrid.RowDefinitions().Append(secondRowDef);
auto firstColDef = Controls::ColumnDefinition();
firstColDef.Width(GridLengthHelper::FromValueAndType(1, GridUnitType::Star));
auto secondColDef = Controls::ColumnDefinition();
secondColDef.Width(GridLengthHelper::FromValueAndType(1, GridUnitType::Star));
_swapchainsGrid.ColumnDefinitions().Append(firstColDef);
_swapchainsGrid.ColumnDefinitions().Append(secondColDef);
}
_rootGrid.Children().Append(_swapchainsGrid);
Controls::Grid::SetRow(_swapchainsGrid, 1);
{
Media::SolidColorBrush solidColor{};
til::color newBgColor{ 0xFFFF0000 };
solidColor.Color(newBgColor);
_rootGrid.Background(solidColor);
}
{
Media::SolidColorBrush solidColor{};
til::color newBgColor{ 0xFF00FF00 };
solidColor.Color(newBgColor);
_swapchainsGrid.Background(solidColor);
}
winrt::Windows::UI::Xaml::Thickness newMargin = ThicknessHelper::FromUniformLength(4);
_swp0.Margin(newMargin);
_swp1.Margin(newMargin);
_swp2.Margin(newMargin);
_swp3.Margin(newMargin);
_rootGrid.Children().Append(_swp0);
_rootGrid.Children().Append(_swp1);
_rootGrid.Children().Append(_swp2);
_rootGrid.Children().Append(_swp3);
Controls::Grid::SetRow(_swp0, 0);
Controls::Grid::SetColumn(_swp0, 0);
Controls::Grid::SetRow(_swp1, 0);
Controls::Grid::SetColumn(_swp1, 1);
Controls::Grid::SetRow(_swp2, 1);
Controls::Grid::SetColumn(_swp2, 0);
Controls::Grid::SetRow(_swp3, 1);
Controls::Grid::SetColumn(_swp3, 1);
_window->SetContent(_rootGrid);
////////////////////////////////////////////////////////////////////////////
_createHost();
_window->OnAppInitialized();
}
winrt::fire_and_forget AppHost::_createHost()
{
{
auto nativePanel2 = _swp0.as<ISwapChainPanelNative2>();
nativePanel2;
}
co_await winrt::resume_background();
{
auto nativePanel2 = _swp0.as<ISwapChainPanelNative2>();
nativePanel2;
}
auto host0 = _manager.CreateHost();
host0.Attach(_swp0);
co_await winrt::resume_foreground(_swp0.Dispatcher());
_swp0_layoutUpdatedRevoker = _swp0.LayoutUpdated(winrt::auto_revoke, [this, host0](auto /*s*/, auto /*e*/) {
// This event fires every time the layout changes, but it is always the last one to fire
// in any layout change chain. That gives us great flexibility in finding the right point
// at which to initialize our renderer (and our terminal).
// Any earlier than the last layout update and we may not know the terminal's starting size.
host0.BeginRendering();
// if (_InitializeTerminal())
// {
// Only let this succeed once.
_swp0_layoutUpdatedRevoker.revoke();
// }
});
}
// Method Description:
// - Resize the window we're about to create to the appropriate dimensions, as
// specified in the settings. This will be called during the handling of
// WM_CREATE. We'll load the settings for the app, then get the proposed size
// of the terminal from the app. Using that proposed size, we'll resize the
// window we're creating, so that it'll match the values in the settings.
// Arguments:
// - hwnd: The HWND of the window we're about to create.
// - proposedRect: The location and size of the window that we're about to
// create. We'll use this rect to determine which monitor the window is about
// to appear on.
// - launchMode: A LaunchMode enum reference that indicates the launch mode
// Return Value:
// - None
void AppHost::_HandleCreateWindow(const HWND hwnd, RECT proposedRect)
{
// Acquire the actual initial position
winrt::Windows::Foundation::Point initialPosition{ (float)proposedRect.left, (float)proposedRect.top };
proposedRect.left = gsl::narrow_cast<long>(initialPosition.X);
proposedRect.top = gsl::narrow_cast<long>(initialPosition.Y);
long adjustedHeight = 0;
long adjustedWidth = 0;
// Find nearest monitor.
HMONITOR hmon = MonitorFromRect(&proposedRect, MONITOR_DEFAULTTONEAREST);
// Get nearest monitor information
MONITORINFO monitorInfo;
monitorInfo.cbSize = sizeof(MONITORINFO);
GetMonitorInfo(hmon, &monitorInfo);
// This API guarantees that dpix and dpiy will be equal, but neither is an
// optional parameter so give two UINTs.
UINT dpix = USER_DEFAULT_SCREEN_DPI;
UINT dpiy = USER_DEFAULT_SCREEN_DPI;
// If this fails, we'll use the default of 96.
GetDpiForMonitor(hmon, MDT_EFFECTIVE_DPI, &dpix, &dpiy);
// We need to check if the top left point of the titlebar of the window is within any screen
RECT offScreenTestRect;
offScreenTestRect.left = proposedRect.left;
offScreenTestRect.top = proposedRect.top;
offScreenTestRect.right = offScreenTestRect.left + 1;
offScreenTestRect.bottom = offScreenTestRect.top + 1;
bool isTitlebarIntersectWithMonitors = false;
EnumDisplayMonitors(
nullptr, &offScreenTestRect, [](HMONITOR, HDC, LPRECT, LPARAM lParam) -> BOOL {
auto intersectWithMonitor = reinterpret_cast<bool*>(lParam);
*intersectWithMonitor = true;
// Continue the enumeration
return FALSE;
},
reinterpret_cast<LPARAM>(&isTitlebarIntersectWithMonitors));
if (!isTitlebarIntersectWithMonitors)
{
// If the title bar is out-of-screen, we set the initial position to
// the top left corner of the nearest monitor
proposedRect.left = monitorInfo.rcWork.left;
proposedRect.top = monitorInfo.rcWork.top;
}
winrt::Windows::Foundation::Size initialSize{ 800, 600 };
const short islandWidth = Utils::ClampToShortMax(
static_cast<long>(ceil(initialSize.Width)), 1);
const short islandHeight = Utils::ClampToShortMax(
static_cast<long>(ceil(initialSize.Height)), 1);
// Get the size of a window we'd need to host that client rect. This will
// add the titlebar space.
const auto nonClientSize = _window->GetTotalNonClientExclusiveSize(dpix);
adjustedWidth = islandWidth + nonClientSize.cx;
adjustedHeight = islandHeight + nonClientSize.cy;
const COORD origin{ gsl::narrow<short>(proposedRect.left),
gsl::narrow<short>(proposedRect.top) };
const COORD dimensions{ Utils::ClampToShortMax(adjustedWidth, 1),
Utils::ClampToShortMax(adjustedHeight, 1) };
const auto newPos = Viewport::FromDimensions(origin, dimensions);
bool succeeded = SetWindowPos(hwnd,
nullptr,
newPos.Left(),
newPos.Top(),
newPos.Width(),
newPos.Height(),
SWP_NOACTIVATE | SWP_NOZORDER);
// Refresh the dpi of HWND because the dpi where the window will launch may be different
// at this time
_window->RefreshCurrentDPI();
// If we can't resize the window, that's really okay. We can just go on with
// the originally proposed window size.
LOG_LAST_ERROR_IF(!succeeded);
}
// Method Description:
// - Called when the app wants to change its theme. We'll forward this to the
// IslandWindow, so it can update the root UI element of the entire XAML tree.
// Arguments:
// - sender: unused
// - arg: the ElementTheme to use as the new theme for the UI
// Return Value:
// - <none>
void AppHost::_UpdateTheme(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::UI::Xaml::ElementTheme& arg)
{
_window->OnApplicationThemeChanged(arg);
}

View File

@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "IslandWindow.h"
#include <winrt/ScratchWinRTServer.h>
#include "HostManager.h"
class AppHost
{
public:
AppHost() noexcept;
virtual ~AppHost();
void Initialize();
private:
bool _useNonClientArea{ false };
std::unique_ptr<IslandWindow> _window;
winrt::ScratchIsland::HostManager _manager;
void _HandleCreateWindow(const HWND hwnd, RECT proposedRect);
void _UpdateTheme(const winrt::Windows::Foundation::IInspectable&,
const winrt::Windows::UI::Xaml::ElementTheme& arg);
winrt::fire_and_forget _createHost();
winrt::Windows::UI::Xaml::Controls::Grid _rootGrid{ nullptr };
winrt::Windows::UI::Xaml::Controls::Grid _swapchainsGrid{ nullptr };
winrt::Windows::UI::Xaml::Controls::SwapChainPanel _swp0{ nullptr };
winrt::Windows::UI::Xaml::Controls::SwapChainPanel _swp1{ nullptr };
winrt::Windows::UI::Xaml::Controls::SwapChainPanel _swp2{ nullptr };
winrt::Windows::UI::Xaml::Controls::SwapChainPanel _swp3{ nullptr };
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _swp0_layoutUpdatedRevoker;
};

View File

@@ -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 <wil/resource.h>
template<typename T>
class BaseWindow
{
public:
virtual ~BaseWindow() = 0;
static T* GetThisFromHandle(HWND const window) noexcept
{
return reinterpret_cast<T*>(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<CREATESTRUCT*>(lparam);
T* that = static_cast<T*>(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<RECT*>(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<float>(dpi) / static_cast<float>(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:
// - <none>
void UpdateTitle(std::wstring_view newTitle)
{
_title = newTitle;
PostMessageW(_window.get(), CM_UPDATE_TITLE, 0, reinterpret_cast<LPARAM>(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<T>;
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<LONG_PTR>(this));
EnableNonClientDpiScaling(_window.get());
_currentDpi = GetDpiForWindow(_window.get());
return DefWindowProc(_window.get(), WM_NCCREATE, wParam, lParam);
};
};
template<typename T>
inline BaseWindow<T>::~BaseWindow()
{
}

View File

@@ -0,0 +1,76 @@
#include "pch.h"
#include "HostManager.h"
#include "HostManager.g.cpp"
#include "../../types/inc/utils.hpp"
using namespace winrt;
using namespace winrt::Windows::Foundation;
using namespace ::Microsoft::Console;
namespace winrt::ScratchIsland::implementation
{
HostManager::HostManager()
{
_hosts = winrt::single_threaded_observable_vector<ScratchWinRTServer::HostClass>();
}
Collections::IObservableVector<ScratchWinRTServer::HostClass> HostManager::Hosts()
{
return _hosts;
}
static void _createHostClassProcess(const winrt::guid& g)
{
auto guidStr{ Utils::GuidToString(g) };
std::wstring commandline{ fmt::format(L"ScratchWinRTServer.exe {}", guidStr) };
STARTUPINFO siOne{ 0 };
siOne.cb = sizeof(STARTUPINFOW);
wil::unique_process_information piOne;
auto succeeded = CreateProcessW(
nullptr,
commandline.data(),
nullptr, // lpProcessAttributes
nullptr, // lpThreadAttributes
false, // bInheritHandles
CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags
nullptr, // lpEnvironment
nullptr, // startingDirectory
&siOne, // lpStartupInfo
&piOne // lpProcessInformation
);
if (!succeeded)
{
printf("Failed to create host process\n");
return;
}
// Ooof this is dumb, but we need a sleep here to make the server starts.
// That's _sub par_. Maybe we could use the host's stdout to have them emit
// a byte when they're set up?
Sleep(1000);
// TODO MONDAY - It seems like it takes conhost too long to start up to
// host the ScratchWinRTServer that even a sleep 100 is too short. However,
// any longer, and XAML will just crash, because some frame took too long.
// So we _need_ to do the "have the server explicitly tell us it's ready"
// thing, and maybe also do it on a bg thread (and signal to the UI thread
// that it can attach now)
}
ScratchWinRTServer::HostClass HostManager::CreateHost()
{
// 1. Generate a GUID.
winrt::guid g{ Utils::CreateGuid() };
// 2. Spawn a Server.exe, with the guid on the commandline
_createHostClassProcess(g);
auto host = create_instance<winrt::ScratchWinRTServer::HostClass>(g, CLSCTX_LOCAL_SERVER);
THROW_IF_NULL_ALLOC(host);
_hosts.Append(host);
return host;
}
}

View File

@@ -0,0 +1,65 @@
#pragma once
#include "HostManager.g.h"
// 50dba6cd-4ddb-4b12-8363-5e06f5d0082c
static constexpr GUID HostManager_clsid{
0x50dba6cd,
0x4ddb,
0x4b12,
{ 0x83, 0x63, 0x5e, 0x06, 0xf5, 0xd0, 0x08, 0x2c }
};
namespace winrt::ScratchIsland::implementation
{
struct HostManager : public HostManagerT<HostManager>
{
HostManager();
Windows::Foundation::Collections::IObservableVector<ScratchWinRTServer::HostClass> Hosts();
ScratchWinRTServer::HostClass CreateHost();
private:
Windows::Foundation::Collections::IObservableVector<ScratchWinRTServer::HostClass> _hosts{ nullptr };
};
}
namespace winrt::ScratchIsland::factory_implementation
{
struct HostManager : HostManagerT<HostManager, implementation::HostManager>
{
};
}
// I bet all this could be a macro.
// I MORE be that this is all done by the factory_implementation stuff, isn't it...
struct HostManagerFactory : winrt::implements<HostManagerFactory, IClassFactory>
{
HRESULT __stdcall CreateInstance(IUnknown* outer, GUID const& iid, void** result) noexcept final
{
*result = nullptr;
if (outer)
{
return CLASS_E_NOAGGREGATION;
}
return winrt::make<winrt::ScratchIsland::implementation::HostManager>().as(iid, result);
}
HRESULT __stdcall LockServer(BOOL) noexcept final
{
return S_OK;
}
static void RegisterHostManager()
{
DWORD registrationHostManager{};
winrt::check_hresult(CoRegisterClassObject(HostManager_clsid,
winrt::make<HostManagerFactory>().get(),
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
&registrationHostManager));
printf("registrationHostManager:%d\n", registrationHostManager);
}
};

View File

@@ -0,0 +1,12 @@
namespace ScratchIsland
{
[default_interface] runtimeclass HostManager // : IScratchInterface
{
HostManager();
Windows.Foundation.Collections.IObservableVector<ScratchWinRTServer.HostClass> Hosts { get; };
ScratchWinRTServer.HostClass CreateHost();
};
}

View File

@@ -0,0 +1,605 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "IslandWindow.h"
#include "../types/inc/Viewport.hpp"
#include "resource.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_ISLAND_CLASS"
IslandWindow::IslandWindow() noexcept :
_interopWindowHandle{ nullptr },
_rootGrid{ nullptr },
_source{ nullptr },
_pfnCreateCallback{ nullptr }
{
}
IslandWindow::~IslandWindow()
{
_source.Close();
}
// Method Description:
// - Create the actual window that we'll use for the application.
// Arguments:
// - <none>
// Return Value:
// - <none>
void IslandWindow::MakeWindow() noexcept
{
WNDCLASS wc{};
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hInstance = reinterpret_cast<HINSTANCE>(&__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(_alwaysOnTop ? WS_EX_TOPMOST : 0,
wc.lpszClassName,
L"Scratch Island",
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:
// - <none>
// Return Value:
// - <none>
void IslandWindow::Close()
{
PostQuitMessage(0);
}
// Method Description:
// - Set a callback to be called when we process a WM_CREATE message. This gives
// the AppHost a chance to resize the window to the proper size.
// Arguments:
// - pfn: a function to be called during the handling of WM_CREATE. It takes two
// parameters:
// * HWND: the HWND of the window that's being created.
// * RECT: The position on the screen that the system has proposed for our
// window.
// Return Value:
// - <none>
void IslandWindow::SetCreateCallback(std::function<void(const HWND, const RECT)> pfn) noexcept
{
_pfnCreateCallback = pfn;
}
// 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:
// - <none>
void IslandWindow::_HandleCreateWindow(const WPARAM, const LPARAM lParam) noexcept
{
// Get proposed window rect from create structure
CREATESTRUCTW* pcs = reinterpret_cast<CREATESTRUCTW*>(lParam);
RECT rc;
rc.left = pcs->x;
rc.top = pcs->y;
rc.right = rc.left + pcs->cx;
rc.bottom = rc.top + pcs->cy;
if (_pfnCreateCallback)
{
_pfnCreateCallback(_window.get(), rc);
}
int nCmdShow = SW_SHOW;
ShowWindow(_window.get(), nCmdShow);
UpdateWindow(_window.get());
}
// Method Description:
// - Handles a WM_SIZING message, which occurs when user drags a window border
// or corner. It intercepts this resize action and applies 'snapping' i.e.
// aligns the terminal's size to its cell grid. We're given the window size,
// which we then adjust based on the terminal's properties (like font size).
// Arguments:
// - wParam: Specifies which edge of the window is being dragged.
// - lParam: Pointer to the requested window rectangle (this is, the one that
// originates from current drag action). It also acts as the return value
// (it's a ref parameter).
// Return Value:
// - <none>
LRESULT IslandWindow::_OnSizing(const WPARAM /*wParam*/, const LPARAM /*lParam*/)
{
// If we haven't been given the callback that would adjust the dimension,
// then we can't do anything, so just bail out.
return FALSE;
}
void IslandWindow::Initialize()
{
const bool initialized = (_interopWindowHandle != nullptr);
_source = DesktopWindowXamlSource{};
auto interop = _source.as<IDesktopWindowXamlSourceNative>();
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 IslandWindow::OnSize(const UINT width, const UINT height)
{
// update the interop window size
SetWindowPos(_interopWindowHandle, nullptr, 0, 0, width, height, SWP_SHOWWINDOW);
if (_rootGrid)
{
const auto size = GetLogicalSize();
_rootGrid.Width(size.Width);
_rootGrid.Height(size.Height);
}
}
[[nodiscard]] LRESULT IslandWindow::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; // eat the message
}
}
case WM_NCLBUTTONDOWN:
case WM_NCLBUTTONUP:
case WM_NCMBUTTONDOWN:
case WM_NCMBUTTONUP:
case WM_NCRBUTTONDOWN:
case WM_NCRBUTTONUP:
case WM_NCXBUTTONDOWN:
case WM_NCXBUTTONUP:
{
// If we clicked in the titlebar, raise an event so the app host can
// dispatch an appropriate event.
_DragRegionClickedHandlers();
break;
}
case WM_MENUCHAR:
{
// GH#891: return this LRESULT here to prevent the app from making a
// 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_SIZING:
{
return _OnSizing(wparam, lparam);
}
case WM_CLOSE:
{
// // If the user wants to close the app by clicking 'X' button,
// // we hand off the close experience to the app layer. If all the tabs
// // are closed, the window will be closed as well.
// _windowCloseButtonClickedHandler();
// return 0;
Close();
}
case WM_MOUSEWHEEL:
try
{
// This whole handler is a hack for GH#979.
//
// On some laptops, their trackpads won't scroll inactive windows
// _ever_. With our entire window just being one giant XAML Island, the
// touchpad driver thinks our entire window is inactive, and won't
// scroll the XAML island. On those types of laptops, we'll get a
// WM_MOUSEWHEEL here, in our root window, when the trackpad scrolls.
// We're going to take that message and manually plumb it through to our
// TermControl's, or anything else that implements IMouseWheelListener.
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms645617(v=vs.85).aspx
// Important! Do not use the LOWORD or HIWORD macros to extract the x-
// and y- coordinates of the cursor position because these macros return
// incorrect results on systems with multiple monitors. Systems with
// multiple monitors can have negative x- and y- coordinates, and LOWORD
// and HIWORD treat the coordinates as unsigned quantities.
const til::point eventPoint{ GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) };
// This mouse event is relative to the display origin, not the window. Convert here.
const til::rectangle windowRect{ GetWindowRect() };
const auto origin = windowRect.origin();
const auto relative = eventPoint - origin;
// Convert to logical scaling before raising the event.
const auto real = relative / GetCurrentDpiScale();
const short wheelDelta = static_cast<short>(HIWORD(wparam));
// Raise an event, so any listeners can handle the mouse wheel event manually.
_MouseScrolledHandlers(real, wheelDelta);
return 0;
}
CATCH_LOG();
}
// TODO: handle messages here...
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 IslandWindow::OnResize(const UINT width, const UINT height)
{
if (_interopWindowHandle)
{
OnSize(width, height);
}
}
// Method Description:
// - Called when the window is minimized to the taskbar.
void IslandWindow::OnMinimize()
{
// TODO GH#1989 Stop rendering island content when the app is minimized.
}
// Method Description:
// - Called when the window is restored from having been minimized.
void IslandWindow::OnRestore()
{
// TODO GH#1989 Stop rendering island content when the app is minimized.
}
void IslandWindow::SetContent(winrt::Windows::UI::Xaml::UIElement content)
{
_rootGrid.Children().Clear();
_rootGrid.Children().Append(content);
}
// Method Description:
// - Gets the difference between window and client area size.
// Arguments:
// - dpi: dpi of a monitor on which the window is placed
// Return Value
// - The size difference
SIZE IslandWindow::GetTotalNonClientExclusiveSize(const UINT dpi) const noexcept
{
const auto windowStyle = static_cast<DWORD>(GetWindowLong(_window.get(), GWL_STYLE));
RECT islandFrame{};
// If we failed to get the correct window size for whatever reason, log
// the error and go on. We'll use whatever the control proposed as the
// size of our window, which will be at least close.
LOG_IF_WIN32_BOOL_FALSE(AdjustWindowRectExForDpi(&islandFrame, windowStyle, false, 0, dpi));
return {
islandFrame.right - islandFrame.left,
islandFrame.bottom - islandFrame.top
};
}
void IslandWindow::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:
// - <none>
void IslandWindow::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);
}
// Method Description:
// - Updates our focus mode state. See _SetIsBorderless for more details.
// Arguments:
// - <none>
// Return Value:
// - <none>
void IslandWindow::FocusModeChanged(const bool focusMode)
{
// Do nothing if the value was unchanged.
if (focusMode == _borderless)
{
return;
}
_SetIsBorderless(focusMode);
}
// Method Description:
// - Updates our fullscreen state. See _SetIsFullscreen for more details.
// Arguments:
// - <none>
// Return Value:
// - <none>
void IslandWindow::FullscreenChanged(const bool fullscreen)
{
// Do nothing if the value was unchanged.
if (fullscreen == _fullscreen)
{
return;
}
_SetIsFullscreen(fullscreen);
}
// Method Description:
// - Enter or exit the "always on top" state. Before the window is created, this
// value will later be used when we create the window to create the window on
// top of all others. After the window is created, it will either enter the
// group of topmost windows, or exit the group of topmost windows.
// Arguments:
// - alwaysOnTop: whether we should be entering or exiting always on top mode.
// Return Value:
// - <none>
void IslandWindow::SetAlwaysOnTop(const bool alwaysOnTop)
{
_alwaysOnTop = alwaysOnTop;
const auto hwnd = GetHandle();
if (hwnd)
{
SetWindowPos(hwnd,
_alwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST,
0, // the window dimensions are unused, because we're passing SWP_NOSIZE
0,
0,
0,
SWP_NOMOVE | SWP_NOSIZE);
}
}
// From GdiEngine::s_SetWindowLongWHelper
void _SetWindowLongWHelper(const HWND hWnd, const int nIndex, const LONG dwNewLong) noexcept
{
// SetWindowLong has strange error handling. On success, it returns the
// previous Window Long value and doesn't modify the Last Error state. To
// deal with this, we set the last error to 0/S_OK first, call it, and if
// the previous long was 0, we check if the error was non-zero before
// reporting. Otherwise, we'll get an "Error: The operation has completed
// successfully." and there will be another screenshot on the internet
// making fun of Windows. See:
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms633591(v=vs.85).aspx
SetLastError(0);
LONG const lResult = SetWindowLongW(hWnd, nIndex, dwNewLong);
if (0 == lResult)
{
LOG_LAST_ERROR_IF(0 != GetLastError());
}
}
// Method Description:
// - This is a helper to figure out what the window styles should be, given the
// current state of flags like borderless mode and fullscreen mode.
// Arguments:
// - <none>
// Return Value:
// - a LONG with the appropriate flags set for our current window mode, to be used with GWL_STYLE
LONG IslandWindow::_getDesiredWindowStyle() const
{
auto windowStyle = GetWindowLongW(GetHandle(), GWL_STYLE);
// If we're both fullscreen and borderless, fullscreen mode takes precedence.
if (_fullscreen)
{
// When moving to fullscreen, remove WS_OVERLAPPEDWINDOW, which specifies
// styles for non-fullscreen windows (e.g. caption bar), and add the
// WS_POPUP style to allow us to size ourselves to the monitor size.
// Do the reverse when restoring from fullscreen.
// Doing these modifications to that window will cause a vista-style
// window frame to briefly appear when entering and exiting fullscreen.
WI_ClearFlag(windowStyle, WS_BORDER);
WI_ClearFlag(windowStyle, WS_SIZEBOX);
WI_ClearAllFlags(windowStyle, WS_OVERLAPPEDWINDOW);
WI_SetFlag(windowStyle, WS_POPUP);
return windowStyle;
}
else if (_borderless)
{
// When moving to borderless, remove WS_OVERLAPPEDWINDOW, which
// specifies styles for non-fullscreen windows (e.g. caption bar), and
// add the WS_BORDER and WS_SIZEBOX styles. This allows us to still have
// a small resizing frame, but without a full titlebar, nor caption
// buttons.
WI_ClearAllFlags(windowStyle, WS_OVERLAPPEDWINDOW);
WI_ClearFlag(windowStyle, WS_POPUP);
WI_SetFlag(windowStyle, WS_BORDER);
WI_SetFlag(windowStyle, WS_SIZEBOX);
return windowStyle;
}
// Here, we're not in either fullscreen or borderless mode. Return to
// WS_OVERLAPPEDWINDOW.
WI_ClearFlag(windowStyle, WS_POPUP);
WI_ClearFlag(windowStyle, WS_BORDER);
WI_ClearFlag(windowStyle, WS_SIZEBOX);
WI_SetAllFlags(windowStyle, WS_OVERLAPPEDWINDOW);
return windowStyle;
}
// Method Description:
// - Enable or disable focus mode. When entering focus mode, we'll
// need to manually hide the entire titlebar.
// - When we're entering focus we need to do some additional modification
// of our window styles. However, the NonClientIslandWindow very explicitly
// _doesn't_ need to do these steps.
// Arguments:
// - borderlessEnabled: If true, we're entering focus mode. If false, we're leaving.
// Return Value:
// - <none>
void IslandWindow::_SetIsBorderless(const bool borderlessEnabled)
{
_borderless = borderlessEnabled;
HWND const hWnd = GetHandle();
// First, modify regular window styles as appropriate
auto windowStyle = _getDesiredWindowStyle();
_SetWindowLongWHelper(hWnd, GWL_STYLE, windowStyle);
// Now modify extended window styles as appropriate
// When moving to fullscreen, remove the window edge style to avoid an
// ugly border when not focused.
auto exWindowStyle = GetWindowLongW(hWnd, GWL_EXSTYLE);
WI_UpdateFlag(exWindowStyle, WS_EX_WINDOWEDGE, !_fullscreen);
_SetWindowLongWHelper(hWnd, GWL_EXSTYLE, exWindowStyle);
// Resize the window, with SWP_FRAMECHANGED, to trigger user32 to
// recalculate the non/client areas
const til::rectangle windowPos{ GetWindowRect() };
SetWindowPos(GetHandle(),
HWND_TOP,
windowPos.left<int>(),
windowPos.top<int>(),
windowPos.width<int>(),
windowPos.height<int>(),
SWP_SHOWWINDOW | SWP_FRAMECHANGED);
}
// Method Description:
// - Controls setting us into or out of fullscreen mode. Largely taken from
// Window::SetIsFullscreen in conhost.
// - When entering fullscreen mode, we'll save the current window size and
// location, and expand to take the entire monitor size. When leaving, we'll
// use that saved size to restore back to.
// Arguments:
// - fullscreenEnabled true if we should enable fullscreen mode, false to disable.
// Return Value:
// - <none>
void IslandWindow::_SetIsFullscreen(const bool fullscreenEnabled)
{
// It is possible to enter _SetIsFullscreen even if we're already in full
// screen. Use the old is in fullscreen flag to gate checks that rely on the
// current state.
const auto oldIsInFullscreen = _fullscreen;
_fullscreen = fullscreenEnabled;
HWND const hWnd = GetHandle();
// First, modify regular window styles as appropriate
auto windowStyle = _getDesiredWindowStyle();
_SetWindowLongWHelper(hWnd, GWL_STYLE, windowStyle);
// Now modify extended window styles as appropriate
// When moving to fullscreen, remove the window edge style to avoid an
// ugly border when not focused.
auto exWindowStyle = GetWindowLongW(hWnd, GWL_EXSTYLE);
WI_UpdateFlag(exWindowStyle, WS_EX_WINDOWEDGE, !_fullscreen);
_SetWindowLongWHelper(hWnd, GWL_EXSTYLE, exWindowStyle);
// When entering/exiting fullscreen mode, we also need to backup/restore the
// current window size, and resize the window to match the new state.
_BackupWindowSizes(oldIsInFullscreen);
_ApplyWindowSize();
}
// Method Description:
// - Used in entering/exiting fullscreen mode. Saves the current window size,
// and the full size of the monitor, for use in _ApplyWindowSize.
// - Taken from conhost's Window::_BackupWindowSizes
// Arguments:
// - fCurrentIsInFullscreen: true if we're currently in fullscreen mode.
// Return Value:
// - <none>
void IslandWindow::_BackupWindowSizes(const bool fCurrentIsInFullscreen)
{
if (_fullscreen)
{
// Note: the current window size depends on the current state of the
// window. So don't back it up if we're already in full screen.
if (!fCurrentIsInFullscreen)
{
_nonFullscreenWindowSize = GetWindowRect();
}
// get and back up the current monitor's size
HMONITOR const hCurrentMonitor = MonitorFromWindow(GetHandle(), MONITOR_DEFAULTTONEAREST);
MONITORINFO currMonitorInfo;
currMonitorInfo.cbSize = sizeof(currMonitorInfo);
if (GetMonitorInfo(hCurrentMonitor, &currMonitorInfo))
{
_fullscreenWindowSize = currMonitorInfo.rcMonitor;
}
}
}
// Method Description:
// - Applys the appropriate window size for transitioning to/from fullscreen mode.
// - Taken from conhost's Window::_ApplyWindowSize
// Arguments:
// - <none>
// Return Value:
// - <none>
void IslandWindow::_ApplyWindowSize()
{
const auto newSize = _fullscreen ? _fullscreenWindowSize : _nonFullscreenWindowSize;
LOG_IF_WIN32_BOOL_FALSE(SetWindowPos(GetHandle(),
HWND_TOP,
newSize.left,
newSize.top,
newSize.right - newSize.left,
newSize.bottom - newSize.top,
SWP_FRAMECHANGED));
}
DEFINE_EVENT(IslandWindow, DragRegionClicked, _DragRegionClickedHandlers, winrt::delegate<>);
DEFINE_EVENT(IslandWindow, WindowCloseButtonClicked, _windowCloseButtonClickedHandler, winrt::delegate<>);

View File

@@ -0,0 +1,77 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "BaseWindow.h"
#include "../../cascadia/inc/cppwinrt_utils.h"
class IslandWindow :
public BaseWindow<IslandWindow>
{
public:
IslandWindow() noexcept;
virtual ~IslandWindow() 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 SIZE GetTotalNonClientExclusiveSize(const UINT dpi) const noexcept;
virtual void Initialize();
void SetCreateCallback(std::function<void(const HWND, const RECT)> pfn) noexcept;
void FocusModeChanged(const bool focusMode);
void FullscreenChanged(const bool fullscreen);
void SetAlwaysOnTop(const bool alwaysOnTop);
#pragma endregion
DECLARE_EVENT(DragRegionClicked, _DragRegionClickedHandlers, winrt::delegate<>);
DECLARE_EVENT(WindowCloseButtonClicked, _windowCloseButtonClickedHandler, winrt::delegate<>);
WINRT_CALLBACK(MouseScrolled, winrt::delegate<void(til::point, int32_t)>);
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;
std::function<void(const HWND, const RECT)> _pfnCreateCallback;
void _HandleCreateWindow(const WPARAM wParam, const LPARAM lParam) noexcept;
[[nodiscard]] LRESULT _OnSizing(const WPARAM wParam, const LPARAM lParam);
bool _borderless{ false };
bool _fullscreen{ false };
bool _alwaysOnTop{ false };
RECT _fullscreenWindowSize;
RECT _nonFullscreenWindowSize;
virtual void _SetIsBorderless(const bool borderlessEnabled);
virtual void _SetIsFullscreen(const bool fullscreenEnabled);
void _BackupWindowSizes(const bool currentIsInFullscreen);
void _ApplyWindowSize();
LONG _getDesiredWindowStyle() const;
private:
// This minimum width allows for width the tabs fit
static constexpr long minimumWidth = 460L;
};

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<!-- This file is copied into ut_app/TerminalApp.Unit.Tests.manifest as part
of the pre-build step for that project. Changes should only be made to the
WindowsTerminal version of the file. -->
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 1903 -->
<!-- See https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/xaml-islands -->
<!-- "maxversiontested" is CASE SENSITIVE. Do not change this.-->
<maxversiontested Id="10.0.18362.0"/>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
</assembly>

View File

@@ -0,0 +1,94 @@
// 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"
/////////////////////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE
BEGIN
IDS_ERROR_DIALOG_TITLE "Error"
IDS_HELP_DIALOG_TITLE "Help"
IDS_ERROR_ARCHITECTURE_FORMAT
"Windows Terminal is designed to run on your system's native architecture (%s).\nYou are currently using the %s version.\n\nPlease use the version of Windows Terminal 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

View File

@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" />
<PropertyGroup Label="Globals">
<ProjectGuid>{23a1f736-cd19-4196-980f-84bcd50cf783}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>ScratchIsland</RootNamespace>
<ProjectName>ScratchIsland</ProjectName>
<TargetName>ScratchIsland</TargetName>
<ConfigurationType>Application</ConfigurationType>
<OpenConsoleUniversalApp>false</OpenConsoleUniversalApp>
<ApplicationType>Windows Store</ApplicationType>
<WindowsStoreApp>true</WindowsStoreApp>
<WindowsAppContainer>false</WindowsAppContainer>
<!-- IMPORTANT! Xaml Islands only works on >= 17709 -->
<!-- IMPORTANT! cppwinrt.pre.props specifies 17134 -->
<WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>
<!-- DON'T REDIRECT OUR OUTPUT -->
<NoOutputRedirection>true</NoOutputRedirection>
</PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<ItemDefinitionGroup>
<ClCompile>
<SDLCheck>true</SDLCheck>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(OpenConsoleDir)\src\inc;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\Console;$(OpenConsoleDir)\dep\Win32K;$(OpenConsoleDir)\dep\gsl\include;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
</ClCompile>
<ClCompile>
<!-- Manually include the generated TerminalCore header's path, because
adding a project reference will confuse msbuild, because TerminalCore
isn't a dll, it's a lib, and cppwinrt won't include a lib's header -->
<AdditionalIncludeDirectories>"$(OpenConsoleDir)src\cascadia\TerminalCore\lib\Generated Files";%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalDependencies>gdi32.lib;dwmapi.lib;Shcore.lib;UxTheme.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<PropertyGroup>
<GenerateManifest>true</GenerateManifest>
<EmbedManifest>true</EmbedManifest>
</PropertyGroup>
<!-- Source Files -->
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="AppHost.h" />
<ClInclude Include="BaseWindow.h" />
<ClInclude Include="IslandWindow.h" />
<ClInclude Include="HostManager.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="main.cpp" />
<ClCompile Include="AppHost.cpp" />
<ClCompile Include="IslandWindow.cpp" />
<ClCompile Include="HostManager.cpp" />
</ItemGroup>
<ItemGroup>
<Midl Include="HostManager.idl" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="ScratchIsland.rc" />
</ItemGroup>
<ItemGroup>
<Manifest Include="ScratchIsland.manifest" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<!-- Dependencies -->
<ItemGroup>
<!-- Even though we do have proper recursive dependencies, we want to keep some of these here
so that the AppX Manifest contains their activatable classes. -->
<!-- <ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalSettings\TerminalSettings.vcxproj" /> -->
<!-- <ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\TerminalControl.vcxproj" /> -->
<!-- <ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalConnection\TerminalConnection.vcxproj" /> -->
<!-- <ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\TerminalApp.vcxproj" /> -->
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj" />
<ProjectReference Include="..\ScratchWinRTServer\ScratchWinRTServer.vcxproj">
<Project>{d46d9547-f085-4645-b8f7-e8cd21559ab4}</Project>
</ProjectReference>
</ItemGroup>
<!--
This ItemGroup and the Globals PropertyGroup below it are required in order
to enable F5 debugging for the unpackaged application
-->
<ItemGroup>
<PropertyPageSchema Include="$(VCTargetsPath)$(LangID)\debugger_general.xml" />
<PropertyPageSchema Include="$(VCTargetsPath)$(LangID)\debugger_local_windows.xml" />
</ItemGroup>
<PropertyGroup Label="Globals">
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
</PropertyGroup>
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
<Import Project="..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.1-rc\build\native\Microsoft.VCRTForwarders.140.targets" Condition="Exists('..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.1-rc\build\native\Microsoft.VCRTForwarders.140.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.1-rc\build\native\Microsoft.VCRTForwarders.140.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.1-rc\build\native\Microsoft.VCRTForwarders.140.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Terminal.ThemeHelpers.0.2.200324001\build\native\Terminal.ThemeHelpers.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Terminal.ThemeHelpers.0.2.200324001\build\native\Terminal.ThemeHelpers.targets'))" />
</Target>
<!-- Override GetPackagingOutputs to roll up all our dependencies.
This ensures that when the WAP packaging project asks what files go into
the package, we tell it.
This is a heavily stripped version of the one in Microsoft.*.AppxPackage.targets.
-->
<PropertyGroup>
<_ContinueOnError Condition="'$(BuildingProject)' == 'true'">true</_ContinueOnError>
<_ContinueOnError Condition="'$(BuildingProject)' != 'true'">false</_ContinueOnError>
</PropertyGroup>
<Target Name="GetPackagingOutputs" Returns="@(PackagingOutputs)">
<MSBuild
Projects="@(ProjectReferenceWithConfiguration)"
Targets="GetPackagingOutputs"
BuildInParallel="$(BuildInParallel)"
Properties="%(ProjectReferenceWithConfiguration.SetConfiguration); %(ProjectReferenceWithConfiguration.SetPlatform)"
Condition="'@(ProjectReferenceWithConfiguration)' != ''
and '%(ProjectReferenceWithConfiguration.BuildReference)' == 'true'
and '%(ProjectReferenceWithConfiguration.ReferenceOutputAssembly)' == 'true'"
ContinueOnError="$(_ContinueOnError)">
<Output TaskParameter="TargetOutputs" ItemName="_PackagingOutputsFromOtherProjects"/>
</MSBuild>
<ItemGroup>
<PackagingOutputs Include="@(_PackagingOutputsFromOtherProjects)" />
</ItemGroup>
<!-- **BEGIN VC LIBS HACK** -->
<PropertyGroup>
<ReasonablePlatform Condition="'$(Platform)'=='Win32'">x86</ReasonablePlatform>
<ReasonablePlatform Condition="'$(ReasonablePlatform)'==''">$(Platform)</ReasonablePlatform>
</PropertyGroup>
<ItemGroup Condition="'$(ScratchIslandOfficialBuild)'=='true'">
<!-- Add all the CRT libs as content; these must be inside a Target as they are wildcards. -->
<_OpenConsoleVCLibToCopy Include="$(VCToolsRedistInstallDir)\$(ReasonablePlatform)\Microsoft.VC142.CRT\*.dll" />
<PackagingOutputs Include="@(_OpenConsoleVCLibToCopy)">
<ProjectName>$(ProjectName)</ProjectName>
<OutputGroup>BuiltProjectOutputGroup</OutputGroup>
<TargetPath>%(Filename)%(Extension)</TargetPath>
</PackagingOutputs>
</ItemGroup>
<!-- **END VC LIBS HACK** -->
</Target>
<!-- <Import Project="$(OpenConsoleDir)\build\rules\GenerateSxsManifestsFromWinmds.targets" /> -->
<Import Project="..\..\..\packages\Terminal.ThemeHelpers.0.2.200324001\build\native\Terminal.ThemeHelpers.targets" Condition="Exists('..\..\..\packages\Terminal.ThemeHelpers.0.2.200324001\build\native\Terminal.ThemeHelpers.targets')" />
</Project>

View File

@@ -0,0 +1,54 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "AppHost.h"
#include "resource.h"
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;
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);
// 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 AppHost 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.
AppHost 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;
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.200316.3" targetFramework="native" />
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.0.0" targetFramework="native" />
<package id="Microsoft.UI.Xaml" version="2.5.0-prerelease.200609001" targetFramework="native" />
<package id="Microsoft.VCRTForwarders.140" version="1.0.1-rc" targetFramework="native" />
<package id="Terminal.ThemeHelpers" version="0.2.200324001" targetFramework="native" />
</packages>

View File

@@ -0,0 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"

View File

@@ -0,0 +1,75 @@
/*++
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 <algorithm> conflict
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <unknwn.h>
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#include <windows.h>
#include <UIAutomation.h>
#include <cstdlib>
#include <cstring>
#include <shellscalingapi.h>
#include <windowsx.h>
// 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 <wil/cppwinrt.h>
// Needed just for XamlIslands to work at all:
#include <winrt/Windows.system.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/windows.ui.core.h>
#include <winrt/Windows.UI.Xaml.Hosting.h>
#include <windows.ui.xaml.hosting.desktopwindowxamlsource.h>
// Additional headers for various xaml features. We need:
// * Controls for grid
// * Media for ScaleTransform
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.ui.xaml.media.h>
#include <windows.ui.xaml.media.dxinterop.h>
#include <wil/resource.h>
#include <wil/win32_helpers.h>
// Including TraceLogging essentials for the binary
#include <TraceLoggingProvider.h>
#include <winmeta.h>
TRACELOGGING_DECLARE_PROVIDER(g_hWindowsTerminalProvider);
#include <telemetry\ProjectTelemetry.h>
#include <TraceLoggingActivity.h>
// For commandline argument processing
#include <shellapi.h>
#include <processenv.h>
#include "til.h"

View File

@@ -0,0 +1,24 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by WindowsTerminal.rc
//
#define IDI_APPICON 101
#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

View File

@@ -0,0 +1,69 @@
#include "pch.h"
#include "HostManager.h"
#include "HostManager.g.cpp"
#include "../../types/inc/utils.hpp"
using namespace winrt;
using namespace winrt::Windows::Foundation;
using namespace ::Microsoft::Console;
namespace winrt::ScratchWinRTClient::implementation
{
HostManager::HostManager()
{
_hosts = winrt::single_threaded_observable_vector<ScratchWinRTServer::HostClass>();
}
Collections::IObservableVector<ScratchWinRTServer::HostClass> HostManager::Hosts()
{
return _hosts;
}
static void _createHostClassProcess(const winrt::guid& g)
{
auto guidStr{ Utils::GuidToString(g) };
std::wstring commandline{ fmt::format(L"ScratchWinRTServer.exe {}", guidStr) };
STARTUPINFO siOne{ 0 };
siOne.cb = sizeof(STARTUPINFOW);
wil::unique_process_information piOne;
auto succeeded = CreateProcessW(
nullptr,
commandline.data(),
nullptr, // lpProcessAttributes
nullptr, // lpThreadAttributes
false, // bInheritHandles
CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags
nullptr, // lpEnvironment
nullptr, // startingDirectory
&siOne, // lpStartupInfo
&piOne // lpProcessInformation
);
if (!succeeded)
{
printf("Failed to create host process\n");
return;
}
// Ooof this is dumb, but we need a sleep here to make the server starts.
// That's _sub par_. Maybe we could use the host's stdout to have them emit
// a byte when they're set up?
Sleep(100);
}
ScratchWinRTServer::HostClass HostManager::CreateHost()
{
// 1. Generate a GUID.
winrt::guid g{ Utils::CreateGuid() };
// 2. Spawn a Server.exe, with the guid on the commandline
_createHostClassProcess(g);
auto host = create_instance<winrt::ScratchWinRTServer::HostClass>(g, CLSCTX_LOCAL_SERVER);
THROW_IF_NULL_ALLOC(host);
_hosts.Append(host);
return host;
}
}

View File

@@ -0,0 +1,65 @@
#pragma once
#include "HostManager.g.h"
// 50dba6cd-4ddb-4b12-8363-5e06f5d0082c
static constexpr GUID HostManager_clsid{
0x50dba6cd,
0x4ddb,
0x4b12,
{ 0x83, 0x63, 0x5e, 0x06, 0xf5, 0xd0, 0x08, 0x2c }
};
namespace winrt::ScratchWinRTClient::implementation
{
struct HostManager : public HostManagerT<HostManager>
{
HostManager();
Windows::Foundation::Collections::IObservableVector<ScratchWinRTServer::HostClass> Hosts();
ScratchWinRTServer::HostClass CreateHost();
private:
Windows::Foundation::Collections::IObservableVector<ScratchWinRTServer::HostClass> _hosts{ nullptr };
};
}
namespace winrt::ScratchWinRTClient::factory_implementation
{
struct HostManager : HostManagerT<HostManager, implementation::HostManager>
{
};
}
// I bet all this could be a macro.
// I MORE be that this is all done by the factory_implementation stuff, isn't it...
struct HostManagerFactory : winrt::implements<HostManagerFactory, IClassFactory>
{
HRESULT __stdcall CreateInstance(IUnknown* outer, GUID const& iid, void** result) noexcept final
{
*result = nullptr;
if (outer)
{
return CLASS_E_NOAGGREGATION;
}
return winrt::make<winrt::ScratchWinRTClient::implementation::HostManager>().as(iid, result);
}
HRESULT __stdcall LockServer(BOOL) noexcept final
{
return S_OK;
}
static void RegisterHostManager()
{
DWORD registrationHostManager{};
winrt::check_hresult(CoRegisterClassObject(HostManager_clsid,
winrt::make<HostManagerFactory>().get(),
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
&registrationHostManager));
printf("registrationHostManager:%d\n", registrationHostManager);
}
};

View File

@@ -0,0 +1,12 @@
namespace ScratchWinRTClient
{
[default_interface] runtimeclass HostManager // : IScratchInterface
{
HostManager();
Windows.Foundation.Collections.IObservableVector<ScratchWinRTServer.HostClass> Hosts { get; };
ScratchWinRTServer.HostClass CreateHost();
};
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<!--
To customize common C++/WinRT project properties:
* right-click the project node
* expand the Common Properties item
* select the C++/WinRT property page
For more advanced scenarios, and complete documentation, please see:
https://github.com/Microsoft/cppwinrt/tree/master/nuget
-->
<PropertyGroup />
<ItemDefinitionGroup />
</Project>

View File

@@ -0,0 +1,154 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200609.3\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200609.3\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<CppWinRTOptimized>true</CppWinRTOptimized>
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
<CppWinRTGenerateWindowsMetadata>true</CppWinRTGenerateWindowsMetadata>
<MinimalCoreWin>true</MinimalCoreWin>
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{06382349-d62a-4c7d-a7d3-9ca817eae092}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>ScratchWinRTClient</RootNamespace>
<WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0.18362.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformMinVersion>10.0.17134.0</WindowsTargetPlatformMinVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v140</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<UseDebugLibraries>true</UseDebugLibraries>
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="PropertySheet.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
<PreprocessorDefinitions>_CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<WarningLevel>Level4</WarningLevel>
<AdditionalOptions>%(AdditionalOptions) /permissive- /bigobj</AdditionalOptions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateWindowsMetadata>false</GenerateWindowsMetadata>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Platform)'=='Win32'">
<ClCompile>
<PreprocessorDefinitions>WIN32;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateWindowsMetadata>false</GenerateWindowsMetadata>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="HostManager.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="HostManager.cpp" />
</ItemGroup>
<ItemGroup>
<Midl Include="HostManager.idl" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="PropertySheet.props" />
<Text Include="readme.txt">
<DeploymentContent>false</DeploymentContent>
</Text>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ScratchWinRTServer\ScratchWinRTServer.vcxproj">
<Project>{d46d9547-f085-4645-b8f7-e8cd21559ab4}</Project>
</ProjectReference>
<!-- <ProjectReference Include="..\ScratchWinRTServer\ScratchWinRTServer.vcxproj">
<Project>{d46d9547-f085-4645-b8f7-e8cd21559ab4}</Project>
</ProjectReference> -->
<ProjectReference Include="$(SolutionDir)src\types\lib\types.vcxproj" />
</ItemGroup>
<!-- ====================== Compiler & Linker Flags ===================== -->
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\Win32K;$(SolutionDir)\dep\gsl\include;$(SolutionDir)\dep\wil\include;$(SolutionDir)\oss\chromium;$(SolutionDir)\oss\fmt\include;$(SolutionDir)\oss\dynamic_bitset;$(SolutionDir)\oss\libpopcnt;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
<!-- <AdditionalIncludeDirectories>$(SolutionDir)\src\inc;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories> -->
<!-- Manually disable unreachable code warning, because jconcpp has a ton of that. -->
</ClCompile>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200609.3\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200609.3\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200609.3\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200609.3\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200609.3\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200609.3\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="PropertySheet.props" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Text Include="readme.txt" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,379 @@
#include "pch.h"
#include <conio.h>
#include "HostManager.h"
#include <winrt/ScratchWinRTServer.h>
#include "../../types/inc/utils.hpp"
#include "../ScratchWinRTServer/IMyComInterface.h"
using namespace winrt;
using namespace winrt::Windows::Foundation;
using namespace ::Microsoft::Console;
// DAA16D7F-EF66-4FC9-B6F2-3E5B6D924576
static constexpr GUID MyStringable_clsid{
0xdaa16d7f,
0xef66,
0x4fc9,
{ 0xb6, 0xf2, 0x3e, 0x5b, 0x6d, 0x92, 0x45, 0x76 }
};
// EAA16D7F-EF66-4FC9-B6F2-3E5B6D924576
static constexpr GUID ScratchStringable_clsid{
0xeaa16d7f,
0xef66,
0x4fc9,
{ 0xb6, 0xf2, 0x3e, 0x5b, 0x6d, 0x92, 0x45, 0x76 }
};
// FAA16D7F-EF66-4FC9-B6F2-3E5B6D924576
static constexpr GUID ScratchClass_clsid{
0xfaa16d7f,
0xef66,
0x4fc9,
{ 0xb6, 0xf2, 0x3e, 0x5b, 0x6d, 0x92, 0x45, 0x76 }
};
void createExistingObjectApp(int /*argc*/, char** argv)
{
winrt::guid guidFromCmdline{};
std::string guidString{ argv[1] };
auto canConvert = guidString.length() == 38 && guidString.front() == '{' && guidString.back() == '}';
if (canConvert)
{
std::wstring wideGuidStr{ til::u8u16(guidString) };
printf("\x1b[90mSERVER: Found GUID:%ls\x1b[m\n", wideGuidStr.c_str());
GUID result{};
THROW_IF_FAILED(IIDFromString(wideGuidStr.c_str(), &result));
guidFromCmdline = result;
}
if (guidFromCmdline == winrt::guid{})
{
printf("client did not recieve GUID, early returning.");
return; // -1;
}
auto host = create_instance<winrt::ScratchWinRTServer::HostClass>(guidFromCmdline, CLSCTX_LOCAL_SERVER);
if (!host)
{
printf("Could not get the existing HostClass\n");
return;
}
// The DoCount could be anything, depending on which of the hosts we're creating.
printf("DoCount: %d (Expected: ?)\n",
host.DoCount());
}
void createHostClassProcess(const winrt::guid& g)
{
auto guidStr{ Utils::GuidToString(g) };
std::wstring commandline{ fmt::format(L"ScratchWinRTServer.exe {}", guidStr) };
STARTUPINFO siOne{ 0 };
siOne.cb = sizeof(STARTUPINFOW);
wil::unique_process_information piOne;
auto succeeded = CreateProcessW(
nullptr,
commandline.data(),
nullptr, // lpProcessAttributes
nullptr, // lpThreadAttributes
false, // bInheritHandles
CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags
nullptr, // lpEnvironment
nullptr, // startingDirectory
&siOne, // lpStartupInfo
&piOne // lpProcessInformation
);
if (!succeeded)
{
printf("Failed to create first host process\n");
return;
}
// Ooof this is dumb, but we need a sleep here to make the server starts.
// That's _sub par_. Maybe we could use the host's stdout to have them emit
// a byte when they're set up?
Sleep(100);
}
void scratchApp()
{
printf("scratchApp\n");
// 1. Generate a GUID.
// 2. Spawn a Server.exe, with the guid on the commandline
// 3. Make an instance of that GUID, as a HostClass
// 4. Call HostClass::DoTheThing, and get the count with HostClass::DoCount [1]
// 5. Make another instance of HostClass, and get the count with HostClass::DoCount. It should be the same. [1, 1]
// 6. On the second HostClass, call DoTheThing. Verify that both instances have the same DoCount. [2. 2]
// 7. Create a second Server.exe, and create a Third HostClass, using that GUID.
// 8. Call DoTheThing on the third, and verify the counts of all three instances. [2, 2, 1]
// 9. QUESTION: Does releasing the first instance leave the first object alive, since the second instance still points at it?
// 1. Generate a GUID.
winrt::guid firstGuid{ Utils::CreateGuid() };
// 2. Spawn a Server.exe, with the guid on the commandline
createHostClassProcess(firstGuid);
// 3. Make an instance of that GUID, as a HostClass
printf("Trying to directly create a HostClass...\n");
auto firstHost = create_instance<winrt::ScratchWinRTServer::HostClass>(firstGuid, CLSCTX_LOCAL_SERVER);
if (!firstHost)
{
printf("Could not get the first HostClass\n");
return;
}
printf("DoCount: %d (Expected: 0)\n",
firstHost.DoCount());
// 4. Call HostClass::DoTheThing, and get the count with HostClass::DoCount [1]
firstHost.DoTheThing();
printf("DoCount: %d (Expected: 1)\n",
firstHost.DoCount());
// 5. Make another instance of HostClass, and get the count with HostClass::DoCount. It should be the same. [1, 1]
auto secondHost = create_instance<winrt::ScratchWinRTServer::HostClass>(firstGuid, CLSCTX_LOCAL_SERVER);
if (!secondHost)
{
printf("Could not get the second HostClass\n");
return;
}
printf("DoCount: [%d, %d] (Expected: [1, 1])\n",
firstHost.DoCount(),
secondHost.DoCount());
// 6. On the second HostClass, call DoTheThing. Verify that both instances have the same DoCount. [2. 2]
secondHost.DoTheThing();
printf("DoCount: [%d, %d] (Expected: [2, 2])\n",
firstHost.DoCount(),
secondHost.DoCount());
// 7. Create a second Server.exe, and create a Third HostClass, using that GUID.
winrt::guid secondGuid{ Utils::CreateGuid() };
createHostClassProcess(secondGuid);
auto thirdHost = create_instance<winrt::ScratchWinRTServer::HostClass>(secondGuid, CLSCTX_LOCAL_SERVER);
if (!thirdHost)
{
printf("Could not get the third HostClass\n");
return;
}
printf("DoCount: [%d, %d, %d] (Expected: [2, 2, 0])\n",
firstHost.DoCount(),
secondHost.DoCount(),
thirdHost.DoCount());
// 8. Call DoTheThing on the third, and verify the counts of all three instances. [2, 2, 1]
thirdHost.DoTheThing();
printf("DoCount: [%d, %d, %d] (Expected: [2, 2, 1])\n",
firstHost.DoCount(),
secondHost.DoCount(),
thirdHost.DoCount());
}
static void printHosts(const ScratchWinRTClient::HostManager& manager)
{
int index = 0;
for (const auto& h : manager.Hosts())
{
auto guidStr{ Utils::GuidToString(h.Id()) };
printf("Host[%d]: DoCount=%d %ls\n", index, h.DoCount(), guidStr.c_str());
index++;
}
if (index == 0)
{
printf("<No hosts>\n");
}
}
void managerApp()
{
ScratchWinRTClient::HostManager manager;
printHosts(manager);
printf("Create host 0:\n");
auto host0 = manager.CreateHost();
printHosts(manager);
printf("Create host 1:\n");
auto host1 = manager.CreateHost();
host1.DoTheThing();
printHosts(manager);
printf("Create host 2:\n");
auto host2 = manager.CreateHost();
host2.DoTheThing();
host2.DoTheThing();
printHosts(manager);
printf("Create host 3:\n");
auto host3 = manager.CreateHost();
host3.DoTheThing();
host3.DoTheThing();
host3.DoTheThing();
printHosts(manager);
printf("increment host 0:\n");
struct Foo : winrt::implements<Foo, IMyComInterface>
{
HRESULT __stdcall Call() noexcept override
{
return S_OK;
}
};
auto f = winrt::make<Foo>();
try
{
auto b = f.as<IMyComInterface>();
winrt::check_hresult(b->Call());
printf("The Foo works just _fine_\n");
}
catch (hresult_error const& e)
{
printf("Error just doing the Foo thing: %ls\n", e.message().c_str());
}
try
{
// winrt::com_ptr<IMyComInterface> f;
//f.copy_from(host0);
// auto unk = winrt::get_unknown(host0);
// DebugBreak();
// This obviously doesn't work, because winrt::Server::HostClass doesn't
// implement IMyComInterface. The implementation does!
// auto mci = host0.as<IMyComInterface>();
// if (mci)
// if (mci)
// {
// mci->Call();
// }
// Again, this doesn't work because the compiler doesn't know that
// Server::impl::HostClass implements IMyComInterface
// winrt::com_ptr<IMyComInterface> c;
// c.copy_from(winrt::get_self<IMyComInterface>(host0));
// if (c)
// {
// c->Call();
// }
// Unsurprisingly, none of the follwoing works either:
// auto pvoid = winrt::put_abi(host0);
// printf("Step 1\n");
// // winrt::com_ptr<::IUnknown> unk{};
// ::IUnknown* iunk = (::IUnknown*)*pvoid;
// printf("Step 2\n");
// winrt::com_ptr<::IUnknown> unk; // { &iunk };
// unk.copy_from(iunk);
// printf("Step 3\n");
// auto mci = unk.as<IMyComInterface>();
// if (mci)
// {
// mci->Call();
// printf("Step 4\n");
// }
// printf("Step 5\n");
// IMyComInterface* mciraw;
// unk->QueryInterface(__uuidof(IMyComInterface), (void**)&mciraw);
// if (mci)
// {
// mci->Call();
// printf("Step 6\n");
// }
// printf("Step 7\n");
}
catch (hresult_error const& e)
{
printf("Error converting to the IMyComInterface: %ls\n", e.message().c_str());
}
// host0.DoTheThing();
// host0.DoTheThing();
// host0.DoTheThing();
// host0.DoTheThing();
printHosts(manager);
return;
// bool exitRequested = false;
// while (!exitRequested)
// {
// printf("-----------------------------\n");
// printf("input a command (l, i, c, q): ");
// const auto ch = _getch();
// printf("\n");
// if (ch == 'l')
// {
// printHosts(manager);
// }
// else if (ch == 'i')
// {
// printf("input a host to increment: ");
// const auto ch2 = _getch();
// if (ch2 >= '0' && ch2 <= '9')
// {
// uint32_t index = ((int)(ch2)) - ((int)('0'));
// if (index < manager.Hosts().Size())
// {
// manager.Hosts().GetAt(index).DoTheThing();
// printHosts(manager);
// }
// }
// }
// else if (ch == 'c')
// {
// printf("Creating a new host\n");
// manager.CreateHost();
// printHosts(manager);
// }
// else if (ch == 'q')
// {
// exitRequested = true;
// }
// }
}
int main(int /*argc*/, char** /*argv*/)
{
auto hOut = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD dwMode = 0;
GetConsoleMode(hOut, &dwMode);
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(hOut, dwMode);
init_apartment();
// try
// {
// // If a GUID was passed on the commandline, then try to instead make an instance of that class.
// if (argc > 1)
// {
// createExistingObjectApp(argc, argv);
// }
// else
// {
// scratchApp();
// }
// }
// catch (hresult_error const& e)
// {
// printf("Error: %ls\n", e.message().c_str());
// }
try
{
managerApp();
}
catch (hresult_error const& e)
{
printf("Error: %ls\n", e.message().c_str());
}
// printf("Press Enter me when you're done.");
// getchar();
printf("Exiting client");
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.200609.3" targetFramework="native" />
</packages>

View File

@@ -0,0 +1 @@
#include "pch.h"

View File

@@ -0,0 +1,20 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#define BLOCK_TIL
#include <wil/cppwinrt.h>
#undef max
#undef min
#include "LibraryIncludes.h"
#include <Unknwn.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Media.h>
#include <windows.ui.xaml.media.dxinterop.h>
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#include "til.h"

View File

@@ -0,0 +1,30 @@
========================================================================
C++/WinRT ScratchWinRTClient Project Overview
========================================================================
This project demonstrates how to get started consuming Windows Runtime
classes directly from standard C++, using platform projection headers
generated from Windows SDK metadata files.
Steps to generate and consume SDK platform projection:
1. Build project initally to generate platform projection headers into
your Generated Files folder.
2. Include a projection namespace header in your pch.h, such as
<winrt/Windows.Foundation.h>.
3. Consume winrt namespace and any Windows Runtime namespaces, such as
winrt::Windows::Foundation, from source code.
4. Initialize apartment via init_apartment() and consume winrt classes.
Steps to generate and consume a projection from third party metadata:
1. Add a WinMD reference by right-clicking the References project node
and selecting "Add Reference...". In the Add References dialog,
browse to the component WinMD you want to consume and add it.
2. Build the project once to generate projection headers for the
referenced WinMD file under the "Generated Files" subfolder.
3. As above, include projection headers in pch or source code
to consume projected Windows Runtime classes.
========================================================================
Learn more about C++/WinRT here:
http://aka.ms/cppwinrt/
========================================================================

View File

@@ -0,0 +1,374 @@
#include "pch.h"
#include <argb.h>
#include <DefaultSettings.h>
#include "HostClass.h"
#include "HostClass.g.cpp"
#include <wrl.h>
extern std::mutex m;
extern std::condition_variable cv;
extern bool dtored;
using namespace ::Microsoft::Console::Types;
using namespace ::Microsoft::Terminal::Core;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Input;
using namespace winrt::Windows::UI::Xaml::Automation::Peers;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::UI::ViewManagement;
using namespace winrt::Windows::UI::Input;
using namespace winrt::Windows::System;
using namespace winrt::Microsoft::Terminal::Settings;
// using namespace winrt::Windows::ApplicationModel::DataTransfer;
namespace winrt::ScratchWinRTServer::implementation
{
HostClass::HostClass(const winrt::guid& g) :
_id{ g },
_desiredFont{ DEFAULT_FONT_FACE, 0, DEFAULT_FONT_WEIGHT, { 0, DEFAULT_FONT_SIZE }, CP_UTF8 },
_actualFont{ DEFAULT_FONT_FACE, 0, DEFAULT_FONT_WEIGHT, { 0, DEFAULT_FONT_SIZE }, CP_UTF8, false }
{
_terminal = std::make_unique<::Microsoft::Terminal::Core::Terminal>();
_settings = winrt::Microsoft::Terminal::Settings::TerminalSettings();
auto fontSize = _settings.FontSize();
const auto newSize = std::max<short>(gsl::narrow_cast<short>(fontSize), 1);
const auto fontFace = _settings.FontFace();
const auto fontWeight = _settings.FontWeight();
_actualFont = { fontFace, 0, fontWeight.Weight, { 0, newSize }, CP_UTF8, false };
_desiredFont = { _actualFont };
}
HostClass::~HostClass()
{
printf("~HostClass()\n");
std::unique_lock<std::mutex> lk(m);
dtored = true;
cv.notify_one();
}
int HostClass::DoCount()
{
return _DoCount;
}
void HostClass::DoTheThing()
{
_DoCount++;
}
winrt::guid HostClass::Id()
{
return _id;
}
HRESULT __stdcall HostClass::Call()
{
_DoCount += 4;
return S_OK;
}
void HostClass::Attach(Windows::UI::Xaml::Controls::SwapChainPanel panel)
{
// // _panel = panel;
// ::Microsoft::WRL::ComPtr<ISwapChainPanelNative2> panelNative;
// reinterpret_cast<IUnknown*>(&panel)->QueryInterface(IID_PPV_ARGS(&panelNative));
// // panelNative->SetSwapChainHandle(m_swapChainHandle);
// // auto nativePanel2 = panel.try_as<ISwapChainPanelNative2>();
// // if (nativePanel2)
// if (panelNative)
// {
// printf("Got the nativePanel2\n");
// }
// else
// {
// printf("why why why\n");
// }
// // nativePanel2;
// panelNative;
// winrt::com_ptr<ISwapChainPanelNative2> native;
winrt::com_ptr<IUnknown> iunk = panel.try_as<IUnknown>();
if (iunk)
{
printf("Got the IUnknown\n");
////////////////////////////////////////////////////////////////////
auto nativePanel2 = iunk.try_as<ISwapChainPanelNative2>();
if (nativePanel2)
{
printf("Got the nativePanel2\n");
}
else
{
printf("Couldn't try_as to get the ISwapChainPanelNative2\n");
}
nativePanel2;
////////////////////////////////////////////////////////////////////
::Microsoft::WRL::ComPtr<ISwapChainPanelNative2> panelNative;
iunk->QueryInterface(IID_PPV_ARGS(&panelNative));
if (panelNative)
{
printf("Got the panelNative\n");
}
else
{
printf("Couldn't QueryInterface to get the ISwapChainPanelNative2\n");
}
////////////////////////////////////////////////////////////////////
IUnknown* foo = iunk.get();
foo->QueryInterface(IID_PPV_ARGS(&panelNative));
if (panelNative)
{
printf("Got the panelNative\n");
}
else
{
printf("This didn't work either\n");
}
////////////////////////////////////////////////////////////////////
}
else
{
printf("Couldn't try_as to get the IUnknown\n");
}
iunk;
// // DO NOT UNDER ANY CIRCUMSTANCE DO THIS
//
// winrt::Windows::UI::Xaml::Media::SolidColorBrush solidColor{};
// til::color newBgColor{ 0x8F000000 };
// solidColor.Color(newBgColor);
// _panel.Background(solidColor);
//
// ANYTHING WE DO TO THE SWAPCHAINPANEL on this thread is NOT the UI thread. It can't _possibly_ be.
}
void HostClass::BeginRendering()
{
// IDXGISwapChain1* swapChain; // = _getSwapchainFromMyRenderer();
// // DANGER: I'm fairly certain that this needs to be called on the
// // `SwapChainPanel`s UI thread. So we may be slightly out of luck here.
// // Unless we can just
// // co_await winrt::resume_foreground(_panel.Dispatcher());
// // But that's a thread in another process!
// auto nativePanel = _panel.as<ISwapChainPanelNative>();
// nativePanel->SetSwapChain(swapChain);
// Holy crap look at:
// ISwapChainPanelNative2::SetSwapChainHandle method
// https://docs.microsoft.com/en-us/windows/win32/api/windows.ui.xaml.media.dxinterop/nf-windows-ui-xaml-media-dxinterop-iswapchainpanelnative2-setswapchainhandle
//
// SetSwapChain(HANDLE swapChainHandle) allows a swap chain to be
// rendered by referencing a shared handle to the swap chain. This
// enables scenarios where a swap chain is created in one process and
// needs to be passed to another process.
//
// XAML supports setting a DXGI swap chain as the content of a
// SwapChainPanel element. Apps accomplish this by querying for the
// ISwapChainPanelNative interface from a SwapChainPanel instance and
// calling SetSwapChain(IDXGISwapChain *swapChain).
//
// This process works for pointers to in process swap chains. However,
// this doesnt work for VoIP apps, which use a two-process model to
// enable continuing calls on a background process when a foreground
// process is suspended or shut down. This two-process implementation
// requires the ability to pass a shared handle to a swap chain, rather
// than a pointer, created on the background process to the foreground
// process to be rendered in a XAML SwapChainPanel in the foreground
// app.
// I _believe_ this will work something like:
//
// HANDLE hSwapChain;
// auto nativePanel2 = _panel.as<ISwapChainPanelNative2>();
// nativePanel2->SetSwapChainHandle(hSwapChain);
//
// But I'm not sure yet.
_InitializeTerminal();
}
void HostClass::RenderEngineSwapChainChanged()
{
// This event is only registered during terminal initialization,
// so we don't need to check _initializedTerminal.
// We also don't lock for things that come back from the renderer.
auto chainHandle = _renderEngine->GetSwapChainHandle();
auto weakThis{ get_weak() };
// co_await winrt::resume_foreground(Dispatcher());
if (auto control{ weakThis.get() })
{
_AttachDxgiSwapChainToXaml(chainHandle);
}
}
void HostClass::_AttachDxgiSwapChainToXaml(HANDLE swapChainHandle)
{
auto nativePanel = _panel.as<ISwapChainPanelNative2>();
nativePanel->SetSwapChainHandle(swapChainHandle);
}
bool HostClass::_InitializeTerminal()
{
{ // scope for terminalLock
auto terminalLock = _terminal->LockForWriting();
if (_initializedTerminal)
{
return false;
}
// const auto actualWidth = _panel.ActualWidth();
// const auto actualHeight = _panel.ActualHeight();
// const auto windowWidth = actualWidth * _panel.CompositionScaleX(); // Width() and Height() are NaN?
// const auto windowHeight = actualHeight * _panel.CompositionScaleY();
const auto actualWidth = 400; //_panel.ActualWidth();
const auto actualHeight = 150; //_panel.ActualHeight();
const auto windowWidth = 400.0; // actualWidth * _panel.CompositionScaleX(); // Width() and Height() are NaN?
const auto windowHeight = 150.0; // actualHeight * _panel.CompositionScaleY();
if (windowWidth == 0 || windowHeight == 0)
{
return false;
}
// First create the render thread.
// Then stash a local pointer to the render thread so we can initialize it and enable it
// to paint itself *after* we hand off its ownership to the renderer.
// We split up construction and initialization of the render thread object this way
// because the renderer and render thread have circular references to each other.
auto renderThread = std::make_unique<::Microsoft::Console::Render::RenderThread>();
auto* const localPointerToThread = renderThread.get();
// Now create the renderer and initialize the render thread.
_renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(_terminal.get(), nullptr, 0, std::move(renderThread));
::Microsoft::Console::Render::IRenderTarget& renderTarget = *_renderer;
// _renderer->SetRendererEnteredErrorStateCallback([weakThis = get_weak()]() {
// if (auto strongThis{ weakThis.get() })
// {
// strongThis->_RendererEnteredErrorState();
// }
// });
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
// Set up the DX Engine
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
_renderer->AddRenderEngine(dxEngine.get());
// Initialize our font with the renderer
// We don't have to care about DPI. We'll get a change message immediately if it's not 96
// and react accordingly.
// _UpdateFont(true); // <-- TODO
_renderer->TriggerFontChange(96, _desiredFont, _actualFont); // "UpdateFont"
const COORD windowSize{ static_cast<short>(windowWidth), static_cast<short>(windowHeight) };
// Fist set up the dx engine with the window size in pixels.
// Then, using the font, get the number of characters that can fit.
// Resize our terminal connection to match that size, and initialize the terminal with that size.
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, windowSize);
THROW_IF_FAILED(dxEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() }));
// Update DxEngine's SelectionBackground
// dxEngine->SetSelectionBackground(_settings.SelectionBackground());
const auto vp = dxEngine->GetViewportInCharacters(viewInPixels);
const auto width = vp.Width();
const auto height = vp.Height();
// _connection.Resize(height, width); // <-- TODO
// Override the default width and height to match the size of the swapChainPanel
// _settings.InitialCols(width); // <-- TODO
// _settings.InitialRows(height); // <-- TODO
_terminal->CreateFromSettings(_settings, renderTarget); // <-- TODO
dxEngine->SetRetroTerminalEffects(false);
dxEngine->SetForceFullRepaintRendering(false);
dxEngine->SetSoftwareRendering(false);
// // Update DxEngine's AntialiasingMode
// switch (_settings.AntialiasingMode())
// {
// case TextAntialiasingMode::Cleartype:
// dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
// break;
// case TextAntialiasingMode::Aliased:
// dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
// break;
// case TextAntialiasingMode::Grayscale:
// default:
// dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
// break;
// }
// // GH#5098: Inform the engine of the opacity of the default text background.
// if (_settings.UseAcrylic())
// {
// dxEngine->SetDefaultTextBackgroundOpacity(::base::saturated_cast<float>(_settings.TintOpacity()));
// }
THROW_IF_FAILED(dxEngine->Enable());
_renderEngine = std::move(dxEngine);
_AttachDxgiSwapChainToXaml(_renderEngine->GetSwapChainHandle());
// Tell the DX Engine to notify us when the swap chain changes.
// We do this after we initially set the swapchain so as to avoid unnecessary callbacks (and locking problems)
_renderEngine->SetCallback(std::bind(&HostClass::RenderEngineSwapChainChanged, this));
// auto bottom = _terminal->GetViewport().BottomExclusive();
// auto bufferHeight = bottom;
// ScrollBar().Maximum(bufferHeight - bufferHeight);
// ScrollBar().Minimum(0);
// ScrollBar().Value(0);
// ScrollBar().ViewportSize(bufferHeight);
localPointerToThread->EnablePainting();
// // Set up blinking cursor
// int blinkTime = GetCaretBlinkTime();
// if (blinkTime != INFINITE)
// {
// // Create a timer
// DispatcherTimer cursorTimer;
// cursorTimer.Interval(std::chrono::milliseconds(blinkTime));
// cursorTimer.Tick({ get_weak(), &TermControl::_CursorTimerTick });
// cursorTimer.Start();
// _cursorTimer.emplace(std::move(cursorTimer));
// }
// else
// {
// // The user has disabled cursor blinking
// _cursorTimer = std::nullopt;
// }
// // import value from WinUser (convert from milli-seconds to micro-seconds)
// _multiClickTimer = GetDoubleClickTime() * 1000;
// // Focus the control here. If we do it during control initialization, then
// // focus won't actually get passed to us. I believe this is because
// // we're not technically a part of the UI tree yet, so focusing us
// // becomes a no-op.
// this->Focus(FocusState::Programmatic);
// _initializedTerminal = true;
} // scope for TerminalLock
// Start the connection outside of lock, because it could
// start writing output immediately.
// _connection.Start(); // <-- TODO
// Likewise, run the event handlers outside of lock (they could
// be reentrant)
// _InitializedHandlers(*this, nullptr);
return true;
}
}

View File

@@ -0,0 +1,56 @@
#pragma once
#include "HostClass.g.h"
#include "IMyComInterface.h"
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
#include <winrt/Microsoft.Terminal.Settings.h>
#include "../../renderer/base/Renderer.hpp"
#include "../../renderer/dx/DxRenderer.hpp"
#include "../../renderer/uia/UiaRenderer.hpp"
#include "../../cascadia/TerminalCore/Terminal.hpp"
namespace winrt::ScratchWinRTServer::implementation
{
struct HostClass : HostClassT<HostClass, IMyComInterface>
{
HostClass(const winrt::guid& g);
~HostClass();
void DoTheThing();
int DoCount();
winrt::guid Id();
HRESULT __stdcall Call() override;
void Attach(Windows::UI::Xaml::Controls::SwapChainPanel panel);
void BeginRendering();
void RenderEngineSwapChainChanged();
private:
int _DoCount{ 0 };
winrt::guid _id;
Windows::UI::Xaml::Controls::SwapChainPanel _panel{ nullptr };
bool _initializedTerminal{ false };
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _connection;
std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal;
FontInfoDesired _desiredFont;
FontInfo _actualFont;
std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer;
std::unique_ptr<::Microsoft::Console::Render::DxEngine> _renderEngine;
winrt::Microsoft::Terminal::Settings::IControlSettings _settings;
void _AttachDxgiSwapChainToXaml(HANDLE swapChainHandle);
bool _InitializeTerminal();
};
}
namespace winrt::ScratchWinRTServer::factory_implementation
{
struct HostClass : HostClassT<HostClass, implementation::HostClass>
{
};
}

View File

@@ -0,0 +1,20 @@
namespace ScratchWinRTServer
{
[default_interface] runtimeclass HostClass // : IScratchInterface
{
HostClass(Guid g);
void DoTheThing();
Int32 DoCount { get; };
Guid Id { get; };
void Attach(Windows.UI.Xaml.Controls.SwapChainPanel panel);
void BeginRendering();
// Adding a Windows.UI.Xaml element will crash the server when the
// server tries to instantiate the element without a XAML host.
//
// Windows.UI.Xaml.Controls.Button MyButton { get; };
};
}

View File

@@ -0,0 +1,6 @@
#pragma once
struct __declspec(uuid("28578d33-c090-4279-9669-dbeea3f24bb0")) IMyComInterface : ::IUnknown
{
virtual HRESULT __stdcall Call() = 0;
};

View File

@@ -0,0 +1,9 @@
// [
// uuid("BB64926F-1A4D-470D-BB8A-3D2CC4B035E4"),
// object,
// local
// ] interface IMyComInterface : IUnknown
// {
// HRESULT MyMethod();
// };

View File

@@ -0,0 +1,11 @@
namespace ScratchWinRTServer
{
// [default_interface]
interface IScratchInterface
{
String DoTheThing();
};
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<!--
To customize common C++/WinRT project properties:
* right-click the project node
* expand the Common Properties item
* select the C++/WinRT property page
For more advanced scenarios, and complete documentation, please see:
https://github.com/Microsoft/cppwinrt/tree/master/nuget
-->
<PropertyGroup />
<ItemDefinitionGroup />
</Project>

View File

@@ -0,0 +1,11 @@
#include "pch.h"
#include "ScratchClass.h"
#include "ScratchClass.g.cpp"
namespace winrt::ScratchWinRTServer::implementation
{
ScratchClass::ScratchClass()
{
}
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include "ScratchClass.g.h"
namespace winrt::ScratchWinRTServer::implementation
{
struct ScratchClass : public ScratchClassT<ScratchClass>
{
ScratchClass();
hstring DoTheThing()
{
return L"Hello there";
}
};
}
namespace winrt::ScratchWinRTServer::factory_implementation
{
struct ScratchClass : ScratchClassT<ScratchClass, implementation::ScratchClass>
{
};
}

View File

@@ -0,0 +1,15 @@
namespace ScratchWinRTServer
{
[default_interface] runtimeclass ScratchClass // : IScratchInterface
{
ScratchClass();
String DoTheThing();
// Adding a Windows.UI.Xaml element will crash the server when the
// server tries to instantiate the element without a XAML host.
//
// Windows.UI.Xaml.Controls.Button MyButton { get; };
};
}

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 1903 -->
<!-- See https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/xaml-islands -->
<!-- "maxversiontested" is CASE SENSITIVE. Do not change this.-->
<maxversiontested Id="10.0.18362.0"/>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
<file name="ScratchWinRTServer.exe" hashalg="SHA1">
<activatableClass name="ScratchWinRTServer.ScratchClass" threadingModel="both" xmlns="urn:schemas-microsoft-com:winrt.v1"></activatableClass>
</file>
<file name="TerminalSettings.dll" hashalg="SHA1">
<activatableClass name="Microsoft.Terminal.Settings.TerminalSettings" threadingModel="both" xmlns="urn:schemas-microsoft-com:winrt.v1"></activatableClass>
</file>
</assembly>

View File

@@ -0,0 +1,179 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200609.3\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200609.3\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<CppWinRTOptimized>true</CppWinRTOptimized>
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
<CppWinRTGenerateWindowsMetadata>true</CppWinRTGenerateWindowsMetadata>
<MinimalCoreWin>true</MinimalCoreWin>
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{d46d9547-f085-4645-b8f7-e8cd21559ab4}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>ScratchWinRTServer</RootNamespace>
<WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0.18362.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformMinVersion>10.0.17134.0</WindowsTargetPlatformMinVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v140</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<UseDebugLibraries>true</UseDebugLibraries>
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="PropertySheet.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
<PreprocessorDefinitions>_CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<WarningLevel>Level4</WarningLevel>
<AdditionalOptions>%(AdditionalOptions) /permissive- /bigobj</AdditionalOptions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateWindowsMetadata>false</GenerateWindowsMetadata>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Platform)'=='Win32'">
<ClCompile>
<PreprocessorDefinitions>WIN32;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateWindowsMetadata>false</GenerateWindowsMetadata>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="ScratchClass.h" />
<ClInclude Include="HostClass.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="ScratchClass.cpp" />
<ClCompile Include="HostClass.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="PropertySheet.props" />
<Text Include="readme.txt">
<DeploymentContent>false</DeploymentContent>
</Text>
</ItemGroup>
<ItemGroup>
<Midl Include="IScratchInterface.idl" />
<Midl Include="ScratchClass.idl" />
<Midl Include="HostClass.idl" />
<!-- <Midl Include="IMyComInterface.idl" /> -->
</ItemGroup>
<ItemGroup>
<Manifest Include="ScratchWinRTServer.manifest" />
</ItemGroup>
<ItemGroup>
<!-- <ProjectReference Include="..\ScratchWinRTServer\ScratchWinRTServer.vcxproj">
<Project>{d46d9547-f085-4645-b8f7-e8cd21559ab4}</Project>
</ProjectReference> -->
<ProjectReference Include="$(SolutionDir)src\types\lib\types.vcxproj" />
<ProjectReference Include="$(SolutionDir)src\buffer\out\lib\bufferout.vcxproj" />
<ProjectReference Include="$(SolutionDir)src\renderer\base\lib\base.vcxproj" />
<ProjectReference Include="$(SolutionDir)src\renderer\dx\lib\dx.vcxproj" />
<ProjectReference Include="$(SolutionDir)src\renderer\uia\lib\uia.vcxproj" />
<ProjectReference Include="$(SolutionDir)src\terminal\parser\lib\parser.vcxproj" />
<ProjectReference Include="$(SolutionDir)src\terminal\input\lib\terminalinput.vcxproj" />
<ProjectReference Include="$(SolutionDir)src\cascadia\TerminalSettings\TerminalSettings.vcxproj">
<Private>false</Private>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
</ProjectReference>
<ProjectReference Include="$(SolutionDir)src\cascadia\TerminalCore\lib\TerminalCore-lib.vcxproj" />
<ProjectReference Include="$(SolutionDir)src\cascadia\TerminalConnection\TerminalConnection.vcxproj">
<Private>false</Private>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
</ProjectReference>
</ItemGroup>
<!-- ====================== Compiler & Linker Flags ===================== -->
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\Win32K;$(SolutionDir)\dep\gsl\include;$(SolutionDir)\dep\wil\include;$(SolutionDir)\oss\chromium;$(SolutionDir)\oss\fmt\include;$(SolutionDir)\oss\dynamic_bitset;$(SolutionDir)\oss\libpopcnt;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
<!-- <AdditionalIncludeDirectories>$(SolutionDir)\src\inc;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories> -->
<!-- Manually disable unreachable code warning, because jconcpp has a ton of that. -->
</ClCompile>
<Link>
<AdditionalDependencies>onecoreuap_apiset.lib;d3dcompiler.lib;dwmapi.lib;uxtheme.lib;shlwapi.lib;ntdll.lib;dcomp.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200609.3\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200609.3\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200609.3\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200609.3\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200609.3\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200609.3\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="PropertySheet.props" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Text Include="readme.txt" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,203 @@
#include "pch.h"
#include <winrt/ScratchWinRTServer.h>
#include "ScratchClass.h"
#include "HostClass.h"
using namespace winrt;
using namespace winrt::Windows::Foundation;
struct ScratchStringable : implements<ScratchStringable, IStringable, IClosable, winrt::ScratchWinRTServer::IScratchInterface>
{
hstring ToString()
{
return L"Hello from server, ScratchStringable";
}
hstring DoTheThing()
{
return L"Zhu Li! Do the thing!";
}
void Close() { printf("Closed ScratchStringable\n"); }
};
struct MyStringable : implements<MyStringable, IStringable>
{
hstring ToString()
{
return L"Hello from server, MyStringable";
}
};
struct MyStringableFactory : implements<MyStringableFactory, IClassFactory>
{
HRESULT __stdcall CreateInstance(IUnknown* outer, GUID const& iid, void** result) noexcept final
{
*result = nullptr;
if (outer)
{
return CLASS_E_NOAGGREGATION;
}
printf("Created MyStringable\n");
return make<MyStringable>().as(iid, result);
}
HRESULT __stdcall LockServer(BOOL) noexcept final
{
return S_OK;
}
};
struct ScratchStringableFactory : implements<ScratchStringableFactory, IClassFactory>
{
HRESULT __stdcall CreateInstance(IUnknown* outer, GUID const& iid, void** result) noexcept final
{
*result = nullptr;
if (outer)
{
return CLASS_E_NOAGGREGATION;
}
printf("Created ScratchStringable\n");
return make<ScratchStringable>().as(iid, result);
}
HRESULT __stdcall LockServer(BOOL) noexcept final
{
return S_OK;
}
};
struct ScratchClassFactory : implements<ScratchClassFactory, IClassFactory>
{
HRESULT __stdcall CreateInstance(IUnknown* outer, GUID const& iid, void** result) noexcept final
{
*result = nullptr;
if (outer)
{
return CLASS_E_NOAGGREGATION;
}
printf("\x1b[90mSERVER: Created ScratchClass\x1b[m\n");
return make<ScratchWinRTServer::implementation::ScratchClass>().as(iid, result);
}
HRESULT __stdcall LockServer(BOOL) noexcept final
{
return S_OK;
}
};
std::mutex m;
std::condition_variable cv;
bool dtored = false;
winrt::weak_ref<ScratchWinRTServer::implementation::HostClass> g_weak{ nullptr };
struct HostClassFactory : implements<HostClassFactory, IClassFactory>
{
HostClassFactory(winrt::guid g) :
_guid{ g } {};
HRESULT __stdcall CreateInstance(IUnknown* outer, GUID const& iid, void** result) noexcept final
{
*result = nullptr;
if (outer)
{
return CLASS_E_NOAGGREGATION;
}
if (!g_weak)
{
auto strong = make_self<ScratchWinRTServer::implementation::HostClass>(_guid);
g_weak = (*strong).get_weak();
return strong.as(iid, result);
}
else
{
auto strong = g_weak.get();
return strong.as(iid, result);
}
}
HRESULT __stdcall LockServer(BOOL) noexcept final
{
return S_OK;
}
private:
winrt::guid _guid;
};
// DAA16D7F-EF66-4FC9-B6F2-3E5B6D924576
static constexpr GUID MyStringable_clsid{
0xdaa16d7f,
0xef66,
0x4fc9,
{ 0xb6, 0xf2, 0x3e, 0x5b, 0x6d, 0x92, 0x45, 0x76 }
};
// EAA16D7F-EF66-4FC9-B6F2-3E5B6D924576
static constexpr GUID ScratchStringable_clsid{
0xeaa16d7f,
0xef66,
0x4fc9,
{ 0xb6, 0xf2, 0x3e, 0x5b, 0x6d, 0x92, 0x45, 0x76 }
};
// FAA16D7F-EF66-4FC9-B6F2-3E5B6D924576
static constexpr GUID ScratchClass_clsid{
0xfaa16d7f,
0xef66,
0x4fc9,
{ 0xb6, 0xf2, 0x3e, 0x5b, 0x6d, 0x92, 0x45, 0x76 }
};
int main(int argc, char** argv)
{
auto hOut = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD dwMode = 0;
GetConsoleMode(hOut, &dwMode);
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(hOut, dwMode);
printf("\x1b[90mSERVER: args:[");
if (argc > 0)
{
for (int i = 0; i < argc; i++)
{
printf("%s,", argv[i]);
}
}
printf("]\x1b[m\n");
winrt::guid guidFromCmdline{};
if (argc > 1)
{
std::string guidString{ argv[1] };
auto canConvert = guidString.length() == 38 && guidString.front() == '{' && guidString.back() == '}';
if (canConvert)
{
std::wstring wideGuidStr{ til::u8u16(guidString) };
printf("\x1b[90mSERVER: Found GUID:%ls\x1b[m\n", wideGuidStr.c_str());
GUID result{};
THROW_IF_FAILED(IIDFromString(wideGuidStr.c_str(), &result));
guidFromCmdline = result;
}
}
if (guidFromCmdline == winrt::guid{})
{
printf("did not recieve GUID, early returning.");
return -1;
}
init_apartment();
DWORD registrationHostClass{};
check_hresult(CoRegisterClassObject(guidFromCmdline,
make<HostClassFactory>(guidFromCmdline).get(),
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
&registrationHostClass));
printf("%d\n", registrationHostClass);
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [] { return dtored; });
// printf("\x1b[90mSERVER: Press Enter me when you're done serving.\x1b[m");
// getchar();
printf("\x1b[90mSERVER: exiting %s\n\x1b[m", argv[1]);
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.200609.3" targetFramework="native" />
</packages>

View File

@@ -0,0 +1 @@
#include "pch.h"

View File

@@ -0,0 +1,35 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#define BLOCK_TIL
#include <wil/cppwinrt.h>
#undef max
#undef min
#include "LibraryIncludes.h"
#include <Unknwn.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Media.h>
#include <windows.ui.xaml.media.dxinterop.h>
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#include "til.h"
#include <dxgi.h>
#include <dxgi1_2.h>
#include <dxgi1_3.h>
#include <d3d11.h>
#include <d2d1.h>
#include <d2d1_1.h>
#include <d2d1_2.h>
#include <d2d1_3.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <dwrite_1.h>
#include <dwrite_2.h>
#include <dwrite_3.h>

View File

@@ -0,0 +1,30 @@
========================================================================
C++/WinRT ScratchWinRTServer Project Overview
========================================================================
This project demonstrates how to get started consuming Windows Runtime
classes directly from standard C++, using platform projection headers
generated from Windows SDK metadata files.
Steps to generate and consume SDK platform projection:
1. Build project initally to generate platform projection headers into
your Generated Files folder.
2. Include a projection namespace header in your pch.h, such as
<winrt/Windows.Foundation.h>.
3. Consume winrt namespace and any Windows Runtime namespaces, such as
winrt::Windows::Foundation, from source code.
4. Initialize apartment via init_apartment() and consume winrt classes.
Steps to generate and consume a projection from third party metadata:
1. Add a WinMD reference by right-clicking the References project node
and selecting "Add Reference...". In the Add References dialog,
browse to the component WinMD you want to consume and add it.
2. Build the project once to generate projection headers for the
referenced WinMD file under the "Generated Files" subfolder.
3. As above, include projection headers in pch or source code
to consume projected Windows Runtime classes.
========================================================================
Learn more about C++/WinRT here:
http://aka.ms/cppwinrt/
========================================================================

View File

@@ -116,8 +116,12 @@ set "__PROJECT_NAME=!_OUTPUT!"
rem If we're trying to clean build, make sure to update the target here.
if "%_MSBUILD_TARGET%" == "Build" (
set __MSBUILD_TARGET=%__PROJECT_NAME%
) else if "%_MSBUILD_TARGET%" == "Clean,Build" (
) else if "%_MSBUILD_TARGET%" == "Clean;Build" (
set __MSBUILD_TARGET=%__PROJECT_NAME%:Rebuild
) else (
echo.
echo Oops... build bug in the neighborhood of configuring a build target.
echo.
)
rem This statement will propagate our internal variables up to the calling
rem scope. Because they're all on one line, the value of our local variables