Compare commits

..

37 Commits

Author SHA1 Message Date
Dustin L. Howett
0b05df6cb3 Migrate spelling-0.0.21 changes from main 2020-11-04 12:34:31 -06:00
Dustin L. Howett
43e838de9a Migrate spelling-0.0.19 changes from main 2020-11-04 12:34:31 -06:00
Mike Griese
7c3378a835 Merge remote-tracking branch 'origin/main' into dev/migrie/oop-window-content-1
# Conflicts:
#	src/cascadia/WindowsTerminal/main.cpp
#	src/renderer/dx/DxRenderer.hpp
2020-11-04 12:34:31 -06:00
Dustin L. Howett
4eeaddc583 Make Tab an unsealed runtimeclass (and rename it to TabBase) (#8153)
In preparation for the Settings UI, we needed to make some changes to
Tab to abstract out shared, common functionality between different types
of tab. This is the result of that work. All code references to the
settings have been removed or reverted.

Contains changes from #8053, #7802.

The messages below only make sense in the context of the Settings UI,
which this pull request does not bring in. They do, however, provide
valuable information.

From #7802 (@leonMSFT):

> This PR's goal was to add an option to the `OpenSettings` keybinding to
> open the Settings UI in a tab. In order to implement that, a couple of
> changes had to be made to `Tab`, specifically:
>
> - Introduce a tab interface named `ITab`
> - Create/Rename two new Tab classes that implement `ITab` called
>   `SettingsTab` and `TerminalTab`
>

From #8053:

> `TerminalTab` and `SettingsTab` share some implementation details. The
> close submenu introduced in #7728 is a good example of functionality
> that is consistent across all tabs. This PR transforms `ITab` from an
> interface, into an [unsealed runtime class] to de-duplicate some
> functionality. Most of the logic from `SettingsTab` was moved there
> because I expect the default behavior of a tab to resemble the
> `SettingsTab` over a `TerminalTab`.
>
> ## References
> Verified that Close submenu work was transferred over (#7728, #7961, #8010).
>
> ## Validation Steps Performed
> Check close submenu on first/last tab when multiple tabs are open.
>
> Closes #7969
>
> [unsealed runtime class]: https://docs.microsoft.com/en-us/uwp/midl-3/intro#base-classes

Co-authored-by: Carlos Zamora <carlos.zamora@microsoft.com>

Co-authored-by: Leon Liang <lelian@microsoft.com>
Co-authored-by: Carlos Zamora <carlos.zamora@microsoft.com>
2020-11-04 10:15:05 -08:00
Mike Griese
8f3ad5dcd9 Merge remote-tracking branch 'origin/master' into dev/migrie/oop-scratch-4 2020-07-30 12:13:45 -05:00
Mike Griese
982d30467b some dead code cleanup 2020-07-30 11:06:59 -05:00
Mike Griese
bb2920ec01 THIS WORKS 2020-07-22 16:43:50 -05:00
Mike Griese
ecf99fac23 This doesn't crash immediately on resize, so it might be okay, right? 2020-07-22 16:30:29 -05:00
Mike Griese
c86038926d we're failing to find the registration for ISwapChainPanelNative2, which is probably B A D. I'm gonna fork here and try and do the 'create the swapchain on the window process then hand it to the child' thing 2020-07-22 11:19:12 -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
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
66 changed files with 3738 additions and 1459 deletions

View File

@@ -313,6 +313,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}"
ProjectSection(ProjectDependencies) = postProject
{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} = {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}
{CA5CAD1A-ABCD-429C-B551-8562EC954746} = {CA5CAD1A-ABCD-429C-B551-8562EC954746}
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}
{48D21369-3D7B-4431-9967-24E81292CF62} = {48D21369-3D7B-4431-9967-24E81292CF62}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ScratchIsland", "src\tools\ScratchIsland\ScratchIsland.vcxproj", "{23A1F736-CD19-4196-980F-84BCD50CF783}"
ProjectSection(ProjectDependencies) = postProject
{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
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Terminal.Settings.Model.Lib", "src\cascadia\TerminalSettingsModel\Microsoft.Terminal.Settings.ModelLib.vcxproj", "{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}"
@@ -1985,6 +1998,90 @@ 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
{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
{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
@@ -2169,6 +2266,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}
{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {59840756-302F-44DF-AA47-441A9D673202}
{CA5CAD1A-082C-4476-9F33-94B339494076} = {59840756-302F-44DF-AA47-441A9D673202}

View File

@@ -7,7 +7,7 @@
#include "../TerminalApp/MinMaxCloseControl.h"
#include "../TerminalApp/TabRowControl.h"
#include "../TerminalApp/ShortcutActionDispatch.h"
#include "../TerminalApp/Tab.h"
#include "../TerminalApp/TerminalTab.h"
#include "../CppWinrtTailored.h"
using namespace Microsoft::Console;
@@ -250,8 +250,8 @@ namespace TerminalAppLocalTests
// In the real app, this isn't a problem, but doesn't happen
// reliably in the unit tests.
Log::Comment(L"Ensure we set the first tab as the selected one.");
auto tab{ page->_GetStrongTabImpl(0) };
page->_tabView.SelectedItem(tab->GetTabViewItem());
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
page->_tabView.SelectedItem(tab->TabViewItem());
page->_UpdatedSelectedTab(0);
});
VERIFY_SUCCEEDED(result);
@@ -453,7 +453,7 @@ namespace TerminalAppLocalTests
result = RunOnUIThread([&page]() {
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetStrongTabImpl(0);
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(1, tab->GetLeafPaneCount());
});
VERIFY_SUCCEEDED(result);
@@ -463,7 +463,7 @@ namespace TerminalAppLocalTests
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetStrongTabImpl(0);
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(2, tab->GetLeafPaneCount());
});
VERIFY_SUCCEEDED(result);
@@ -481,7 +481,7 @@ namespace TerminalAppLocalTests
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetStrongTabImpl(0);
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(2,
tab->GetLeafPaneCount(),
L"We should gracefully do nothing here - the profile no longer exists.");
@@ -562,7 +562,7 @@ namespace TerminalAppLocalTests
ActionEventArgs eventArgs{ args };
// eventArgs.Args(args);
page->_HandleSplitPane(nullptr, eventArgs);
auto firstTab = page->_GetStrongTabImpl(0);
auto firstTab = page->_GetTerminalTabImpl(0);
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
VERIFY_IS_FALSE(firstTab->IsZoomed());
@@ -573,7 +573,7 @@ namespace TerminalAppLocalTests
result = RunOnUIThread([&page]() {
ActionEventArgs eventArgs{};
page->_HandleTogglePaneZoom(nullptr, eventArgs);
auto firstTab = page->_GetStrongTabImpl(0);
auto firstTab = page->_GetTerminalTabImpl(0);
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
VERIFY_IS_TRUE(firstTab->IsZoomed());
});
@@ -583,7 +583,7 @@ namespace TerminalAppLocalTests
result = RunOnUIThread([&page]() {
ActionEventArgs eventArgs{};
page->_HandleTogglePaneZoom(nullptr, eventArgs);
auto firstTab = page->_GetStrongTabImpl(0);
auto firstTab = page->_GetTerminalTabImpl(0);
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
VERIFY_IS_FALSE(firstTab->IsZoomed());
});
@@ -600,7 +600,7 @@ namespace TerminalAppLocalTests
SplitPaneArgs args{ SplitType::Duplicate };
ActionEventArgs eventArgs{ args };
page->_HandleSplitPane(nullptr, eventArgs);
auto firstTab = page->_GetStrongTabImpl(0);
auto firstTab = page->_GetTerminalTabImpl(0);
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
VERIFY_IS_FALSE(firstTab->IsZoomed());
@@ -614,7 +614,7 @@ namespace TerminalAppLocalTests
page->_HandleTogglePaneZoom(nullptr, eventArgs);
auto firstTab = page->_GetStrongTabImpl(0);
auto firstTab = page->_GetTerminalTabImpl(0);
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
VERIFY_IS_TRUE(firstTab->IsZoomed());
});
@@ -628,7 +628,7 @@ namespace TerminalAppLocalTests
page->_HandleMoveFocus(nullptr, eventArgs);
auto firstTab = page->_GetStrongTabImpl(0);
auto firstTab = page->_GetTerminalTabImpl(0);
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
VERIFY_IS_FALSE(firstTab->IsZoomed());
});
@@ -645,7 +645,7 @@ namespace TerminalAppLocalTests
SplitPaneArgs args{ SplitType::Duplicate };
ActionEventArgs eventArgs{ args };
page->_HandleSplitPane(nullptr, eventArgs);
auto firstTab = page->_GetStrongTabImpl(0);
auto firstTab = page->_GetTerminalTabImpl(0);
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
VERIFY_IS_FALSE(firstTab->IsZoomed());
@@ -659,7 +659,7 @@ namespace TerminalAppLocalTests
page->_HandleTogglePaneZoom(nullptr, eventArgs);
auto firstTab = page->_GetStrongTabImpl(0);
auto firstTab = page->_GetTerminalTabImpl(0);
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
VERIFY_IS_TRUE(firstTab->IsZoomed());
});
@@ -672,7 +672,7 @@ namespace TerminalAppLocalTests
page->_HandleClosePane(nullptr, eventArgs);
auto firstTab = page->_GetStrongTabImpl(0);
auto firstTab = page->_GetTerminalTabImpl(0);
VERIFY_IS_FALSE(firstTab->IsZoomed());
});
VERIFY_SUCCEEDED(result);
@@ -683,7 +683,7 @@ namespace TerminalAppLocalTests
Log::Comment(L"Check to ensure there's only one pane left.");
result = RunOnUIThread([&page]() {
auto firstTab = page->_GetStrongTabImpl(0);
auto firstTab = page->_GetTerminalTabImpl(0);
VERIFY_ARE_EQUAL(1, firstTab->GetLeafPaneCount());
VERIFY_IS_FALSE(firstTab->IsZoomed());
});

View File

@@ -130,21 +130,26 @@ namespace winrt::TerminalApp::implementation
void TerminalPage::_HandleTogglePaneZoom(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
auto activeTab = _GetFocusedTab();
// Don't do anything if there's only one pane. It's already zoomed.
if (activeTab && activeTab->GetLeafPaneCount() > 1)
if (auto focusedTab = _GetFocusedTab())
{
// First thing's first, remove the current content from the UI
// tree. This is important, because we might be leaving zoom, and if
// a pane is zoomed, then it's currently in the UI tree, and should
// be removed before it's re-added in Pane::Restore
_tabContent.Children().Clear();
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
{
// Don't do anything if there's only one pane. It's already zoomed.
if (activeTab && activeTab->GetLeafPaneCount() > 1)
{
// First thing's first, remove the current content from the UI
// tree. This is important, because we might be leaving zoom, and if
// a pane is zoomed, then it's currently in the UI tree, and should
// be removed before it's re-added in Pane::Restore
_tabContent.Children().Clear();
// Togging the zoom on the tab will cause the tab to inform us of
// the new root Content for this tab.
activeTab->ToggleZoom();
// Togging the zoom on the tab will cause the tab to inform us of
// the new root Content for this tab.
activeTab->ToggleZoom();
}
}
}
args.Handled(true);
}
@@ -323,16 +328,19 @@ namespace winrt::TerminalApp::implementation
args.Handled(false);
if (const auto& realArgs = args.ActionArgs().try_as<SetColorSchemeArgs>())
{
if (auto activeTab = _GetFocusedTab())
if (auto focusedTab = _GetFocusedTab())
{
if (auto activeControl = activeTab->GetActiveTerminalControl())
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
{
if (const auto scheme = _settings.GlobalSettings().ColorSchemes().TryLookup(realArgs.SchemeName()))
if (auto activeControl = activeTab->GetActiveTerminalControl())
{
auto controlSettings = activeControl.Settings().as<TerminalSettings>();
controlSettings->ApplyColorScheme(scheme);
activeControl.UpdateSettings(*controlSettings);
args.Handled(true);
if (const auto scheme = _settings.GlobalSettings().ColorSchemes().TryLookup(realArgs.SchemeName()))
{
auto controlSettings = activeControl.Settings().as<TerminalSettings>();
controlSettings->ApplyColorScheme(scheme);
activeControl.UpdateSettings(*controlSettings);
args.Handled(true);
}
}
}
}
@@ -352,16 +360,18 @@ namespace winrt::TerminalApp::implementation
}
}
auto activeTab = _GetFocusedTab();
if (activeTab)
if (auto focusedTab = _GetFocusedTab())
{
if (tabColor.has_value())
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
{
activeTab->SetRuntimeTabColor(tabColor.value());
}
else
{
activeTab->ResetRuntimeTabColor();
if (tabColor.has_value())
{
activeTab->SetRuntimeTabColor(tabColor.value());
}
else
{
activeTab->ResetRuntimeTabColor();
}
}
}
args.Handled(true);
@@ -370,10 +380,12 @@ namespace winrt::TerminalApp::implementation
void TerminalPage::_HandleOpenTabColorPicker(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
auto activeTab = _GetFocusedTab();
if (activeTab)
if (auto focusedTab = _GetFocusedTab())
{
activeTab->ActivateColorPicker();
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
{
activeTab->ActivateColorPicker();
}
}
args.Handled(true);
}
@@ -388,16 +400,18 @@ namespace winrt::TerminalApp::implementation
title = realArgs.Title();
}
auto activeTab = _GetFocusedTab();
if (activeTab)
if (auto focusedTab = _GetFocusedTab())
{
if (title.has_value())
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
{
activeTab->SetTabText(title.value());
}
else
{
activeTab->ResetTabText();
if (title.has_value())
{
activeTab->SetTabText(title.value());
}
else
{
activeTab->ResetTabText();
}
}
}
args.Handled(true);
@@ -406,10 +420,12 @@ namespace winrt::TerminalApp::implementation
void TerminalPage::_HandleOpenTabRenamer(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
auto activeTab = _GetFocusedTab();
if (activeTab)
if (auto focusedTab = _GetFocusedTab())
{
activeTab->ActivateTabRenamer();
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
{
activeTab->ActivateTabRenamer();
}
}
args.Handled(true);
}

View File

@@ -4,8 +4,6 @@
#pragma once
#include "AppLogic.g.h"
#include "Tab.h"
#include "TerminalPage.h"
#include "Jumplist.h"
#include "../../cascadia/inc/cppwinrt_utils.h"

View File

@@ -1,18 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "ShortcutActionDispatch.idl";
namespace TerminalApp
{
[default_interface] runtimeclass Tab : Windows.UI.Xaml.Data.INotifyPropertyChanged
{
String Title { get; };
Windows.UI.Xaml.Controls.IconSource IconSource { get; };
Microsoft.Terminal.Settings.Model.Command SwitchToTabCommand { get; };
UInt32 TabViewIndex { get; };
Windows.UI.Xaml.UIElement Content { get; };
void SetDispatch(ShortcutActionDispatch dispatch);
}
}

View File

@@ -0,0 +1,148 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include <LibraryResources.h>
#include "TabBase.h"
#include "TabBase.g.cpp"
using namespace winrt;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Microsoft::Terminal::TerminalControl;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace winrt::Windows::System;
namespace winrt
{
namespace MUX = Microsoft::UI::Xaml;
namespace WUX = Windows::UI::Xaml;
}
namespace winrt::TerminalApp::implementation
{
WUX::FocusState TabBase::FocusState() const noexcept
{
return _focusState;
}
// Method Description:
// - Prepares this tab for being removed from the UI hierarchy
void TabBase::Shutdown()
{
Content(nullptr);
_ClosedHandlers(nullptr, nullptr);
}
// Method Description:
// - Creates a context menu attached to the tab.
// Currently contains elements allowing the user to close the selected tab
// Arguments:
// - <none>
// Return Value:
// - <none>
void TabBase::_CreateContextMenu()
{
auto weakThis{ get_weak() };
// Close
Controls::MenuFlyoutItem closeTabMenuItem;
Controls::FontIcon closeSymbol;
closeSymbol.FontFamily(Media::FontFamily{ L"Segoe MDL2 Assets" });
closeSymbol.Glyph(L"\xE8BB");
closeTabMenuItem.Click([weakThis](auto&&, auto&&) {
if (auto tab{ weakThis.get() })
{
tab->_ClosedHandlers(nullptr, nullptr);
}
});
closeTabMenuItem.Text(RS_(L"TabClose"));
closeTabMenuItem.Icon(closeSymbol);
// Build the menu
Controls::MenuFlyout newTabFlyout;
newTabFlyout.Items().Append(_CreateCloseSubMenu());
newTabFlyout.Items().Append(closeTabMenuItem);
TabViewItem().ContextFlyout(newTabFlyout);
}
// Method Description:
// - Creates a sub-menu containing menu items to close multiple tabs
// Arguments:
// - <none>
// Return Value:
// - the created MenuFlyoutSubItem
Controls::MenuFlyoutSubItem TabBase::_CreateCloseSubMenu()
{
auto weakThis{ get_weak() };
// Close tabs after
_closeTabsAfterMenuItem.Click([weakThis](auto&&, auto&&) {
if (auto tab{ weakThis.get() })
{
tab->_CloseTabsAfter();
}
});
_closeTabsAfterMenuItem.Text(RS_(L"TabCloseAfter"));
// Close other tabs
_closeOtherTabsMenuItem.Click([weakThis](auto&&, auto&&) {
if (auto tab{ weakThis.get() })
{
tab->_CloseOtherTabs();
}
});
_closeOtherTabsMenuItem.Text(RS_(L"TabCloseOther"));
Controls::MenuFlyoutSubItem closeSubMenu;
closeSubMenu.Text(RS_(L"TabCloseSubMenu"));
closeSubMenu.Items().Append(_closeTabsAfterMenuItem);
closeSubMenu.Items().Append(_closeOtherTabsMenuItem);
return closeSubMenu;
}
// Method Description:
// - Enable the Close menu items based on tab index and total number of tabs
// Arguments:
// - <none>
// Return Value:
// - <none>
void TabBase::_EnableCloseMenuItems()
{
// close other tabs is enabled only if there are other tabs
_closeOtherTabsMenuItem.IsEnabled(TabViewNumTabs() > 1);
// close tabs after is enabled only if there are other tabs on the right
_closeTabsAfterMenuItem.IsEnabled(TabViewIndex() < TabViewNumTabs() - 1);
}
void TabBase::_CloseTabsAfter()
{
CloseTabsAfterArgs args{ _TabViewIndex };
ActionAndArgs closeTabsAfter{ ShortcutAction::CloseTabsAfter, args };
_dispatch.DoAction(closeTabsAfter);
}
void TabBase::_CloseOtherTabs()
{
CloseOtherTabsArgs args{ _TabViewIndex };
ActionAndArgs closeOtherTabs{ ShortcutAction::CloseOtherTabs, args };
_dispatch.DoAction(closeOtherTabs);
}
void TabBase::UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs)
{
TabViewIndex(idx);
TabViewNumTabs(numTabs);
_EnableCloseMenuItems();
SwitchToTabCommand().Action().Args().as<SwitchToTabArgs>().TabIndex(idx);
}
void TabBase::SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch)
{
_dispatch = dispatch;
}
}

View File

@@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "inc/cppwinrt_utils.h"
#include "TabBase.g.h"
// fwdecl unittest classes
namespace TerminalAppLocalTests
{
class TabTests;
};
namespace winrt::TerminalApp::implementation
{
struct TabBase : TabBaseT<TabBase>
{
public:
virtual void Focus(winrt::Windows::UI::Xaml::FocusState focusState) = 0;
winrt::Windows::UI::Xaml::FocusState FocusState() const noexcept;
virtual void Shutdown();
void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch);
void UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs);
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
// The TabViewIndex is the index this Tab object resides in TerminalPage's _tabs vector.
// This is needed since Tab is going to be managing its own SwitchToTab command.
GETSET_PROPERTY(uint32_t, TabViewIndex, 0);
// The TabViewNumTabs is the number of Tab objects in TerminalPage's _tabs vector.
GETSET_PROPERTY(uint32_t, TabViewNumTabs, 0);
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Title, _PropertyChangedHandlers);
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Icon, _PropertyChangedHandlers);
OBSERVABLE_GETSET_PROPERTY(winrt::Microsoft::Terminal::Settings::Model::Command, SwitchToTabCommand, _PropertyChangedHandlers, nullptr);
GETSET_PROPERTY(winrt::Microsoft::UI::Xaml::Controls::TabViewItem, TabViewItem, nullptr);
OBSERVABLE_GETSET_PROPERTY(winrt::Windows::UI::Xaml::FrameworkElement, Content, _PropertyChangedHandlers, nullptr);
protected:
winrt::Windows::UI::Xaml::FocusState _focusState{ winrt::Windows::UI::Xaml::FocusState::Unfocused };
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeOtherTabsMenuItem{};
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeTabsAfterMenuItem{};
winrt::TerminalApp::ShortcutActionDispatch _dispatch;
virtual void _CreateContextMenu();
winrt::Windows::UI::Xaml::Controls::MenuFlyoutSubItem _CreateCloseSubMenu();
void _EnableCloseMenuItems();
void _CloseTabsAfter();
void _CloseOtherTabs();
friend class ::TerminalAppLocalTests::TabTests;
};
}

View File

@@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "ShortcutActionDispatch.idl";
namespace TerminalApp
{
unsealed runtimeclass TabBase : Windows.UI.Xaml.Data.INotifyPropertyChanged
{
String Title { get; };
String Icon { get; };
Microsoft.Terminal.Settings.Model.Command SwitchToTabCommand;
Microsoft.UI.Xaml.Controls.TabViewItem TabViewItem { get; };
Windows.UI.Xaml.FrameworkElement Content { get; };
Windows.UI.Xaml.FocusState FocusState { get; };
UInt32 TabViewIndex;
UInt32 TabViewNumTabs;
overridable void Focus(Windows.UI.Xaml.FocusState focusState);
overridable void Shutdown();
void SetDispatch(ShortcutActionDispatch dispatch);
}
}

View File

@@ -70,6 +70,12 @@
<ClInclude Include="MinMaxCloseControl.h">
<DependentUpon>MinMaxCloseControl.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="TabBase.h">
<DependentUpon>TabBase.idl</DependentUpon>
</ClInclude>
<ClInclude Include="TerminalTab.h">
<DependentUpon>TerminalTab.idl</DependentUpon>
</ClInclude>
<ClInclude Include="TerminalPage.h">
<DependentUpon>TerminalPage.xaml</DependentUpon>
<SubType>Code</SubType>
@@ -95,9 +101,6 @@
<ClInclude Include="IconPathConverter.h">
<DependentUpon>IconPathConverter.idl</DependentUpon>
</ClInclude>
<ClInclude Include="Tab.h">
<DependentUpon>Tab.idl</DependentUpon>
</ClInclude>
<ClInclude Include="Pane.h" />
<ClInclude Include="ColorHelper.h" />
<ClInclude Include="TerminalSettings.h">
@@ -127,6 +130,12 @@
<ClCompile Include="MinMaxCloseControl.cpp">
<DependentUpon>MinMaxCloseControl.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="TabBase.cpp">
<DependentUpon>TabBase.idl</DependentUpon>
</ClCompile>
<ClCompile Include="TerminalTab.cpp">
<DependentUpon>TerminalTab.idl</DependentUpon>
</ClCompile>
<ClCompile Include="TerminalPage.cpp">
<DependentUpon>TerminalPage.xaml</DependentUpon>
<SubType>Code</SubType>
@@ -152,9 +161,6 @@
<ClCompile Include="IconPathConverter.cpp">
<DependentUpon>IconPathConverter.idl</DependentUpon>
</ClCompile>
<ClCompile Include="Tab.cpp">
<DependentUpon>Tab.idl</DependentUpon>
</ClCompile>
<ClCompile Include="Pane.cpp" />
<ClCompile Include="Pane.LayoutSizeNode.cpp" />
<ClCompile Include="ColorHelper.cpp" />
@@ -197,6 +203,8 @@
<DependentUpon>MinMaxCloseControl.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="TabBase.idl" />
<Midl Include="TerminalTab.idl" />
<Midl Include="TerminalPage.idl">
<DependentUpon>TerminalPage.xaml</DependentUpon>
<SubType>Code</SubType>
@@ -220,7 +228,6 @@
<Midl Include="EmptyStringVisibilityConverter.idl" />
<Midl Include="HasNestedCommandsVisibilityConverter.idl" />
<Midl Include="IconPathConverter.idl" />
<Midl Include="Tab.idl" />
<Midl Include="TerminalSettings.idl" />
</ItemGroup>
<!-- ========================= Misc Files ======================== -->

View File

@@ -27,6 +27,10 @@
<ClCompile Include="TerminalSettings.cpp">
<Filter>settings</Filter>
</ClCompile>
<ClCompile Include="Jumplist.cpp" />
<ClCompile Include="Tab.cpp">
<Filter>tab</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Utils.h" />
@@ -48,6 +52,10 @@
<ClInclude Include="TerminalSettings.h">
<Filter>settings</Filter>
</ClInclude>
<ClInclude Include="Jumplist.h" />
<ClInclude Include="Tab.h">
<Filter>tab</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Midl Include="AppLogic.idl">
@@ -62,14 +70,13 @@
<Midl Include="ShortcutActionDispatch.idl">
<Filter>settings</Filter>
</Midl>
<Midl Include="Tab.idl">
<Filter>tab</Filter>
</Midl>
<Midl Include="IDirectKeyListener.idl" />
<Midl Include="CommandKeyChordVisibilityConverter.idl" />
<Midl Include="TerminalSettings.idl">
<Filter>settings</Filter>
</Midl>
<Midl Include="TerminalTab.idl">
<Filter>tab</Filter>
</Midl>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
@@ -119,4 +126,4 @@
<Filter>app</Filter>
</ApplicationDefinition>
</ItemGroup>
</Project>
</Project>

View File

@@ -42,7 +42,7 @@ namespace winrt
namespace winrt::TerminalApp::implementation
{
TerminalPage::TerminalPage() :
_tabs{ winrt::single_threaded_observable_vector<TerminalApp::Tab>() },
_tabs{ winrt::single_threaded_observable_vector<TerminalApp::TabBase>() },
_mruTabActions{ winrt::single_threaded_vector<Command>() },
_startupActions{ winrt::single_threaded_vector<ActionAndArgs>() }
{
@@ -668,8 +668,10 @@ namespace winrt::TerminalApp::implementation
TermControl term{ settings, connection };
auto newTabImpl = winrt::make_self<TerminalTab>(profileGuid, term);
_MakeSwitchToTabCommand(*newTabImpl, _tabs.Size());
// Add the new tab to the list of our tabs.
auto newTabImpl = winrt::make_self<Tab>(profileGuid, term);
_tabs.Append(*newTabImpl);
_mruTabActions.Append(newTabImpl->SwitchToTabCommand());
@@ -686,7 +688,8 @@ namespace winrt::TerminalApp::implementation
auto weakTab = make_weak(newTabImpl);
// When the tab's active pane changes, we'll want to lookup a new icon
// for it, and possibly propagate the title up to the window.
// for it. The Title change will be propagated upwards through the tab's
// PropertyChanged event handler.
newTabImpl->ActivePaneChanged([weakTab, weakThis{ get_weak() }]() {
auto page{ weakThis.get() };
auto tab{ weakTab.get() };
@@ -695,24 +698,12 @@ namespace winrt::TerminalApp::implementation
{
// Possibly update the icon of the tab.
page->_UpdateTabIcon(*tab);
// Possibly update the title of the tab, window to match the newly
// focused pane.
page->_UpdateTitle(*tab);
}
});
auto tabViewItem = newTabImpl->GetTabViewItem();
auto tabViewItem = newTabImpl->TabViewItem();
_tabView.TabItems().Append(tabViewItem);
// GH#6570
// The TabView does not apply compact sizing to items added after Compact is enabled.
// By forcibly reapplying compact sizing every time we add a new tab, we'll make sure
// that it works.
// Workaround from https://github.com/microsoft/microsoft-ui-xaml/issues/2711
if (_tabView.TabWidthMode() == MUX::Controls::TabViewWidthMode::Compact)
{
_tabView.UpdateLayout();
_tabView.TabWidthMode(MUX::Controls::TabViewWidthMode::Compact);
}
_ReapplyCompactTabSize();
// Set this tab's icon to the icon from the user's profile
const auto profile = _settings.FindProfile(profileGuid);
@@ -923,12 +914,12 @@ namespace winrt::TerminalApp::implementation
// TitleChanged event.
// Arguments:
// - tab: the Tab to update the title for.
void TerminalPage::_UpdateTitle(const Tab& tab)
void TerminalPage::_UpdateTitle(const TerminalTab& tab)
{
auto newTabTitle = tab.GetActiveTitle();
auto newTabTitle = tab.Title();
if (_settings.GlobalSettings().ShowTitleInTitlebar() &&
tab.IsFocused())
tab.FocusState() != FocusState::Unfocused)
{
_titleChangeHandlers(*this, newTabTitle);
}
@@ -939,7 +930,7 @@ namespace winrt::TerminalApp::implementation
// tab's icon to that icon.
// Arguments:
// - tab: the Tab to update the title for.
void TerminalPage::_UpdateTabIcon(Tab& tab)
void TerminalPage::_UpdateTabIcon(TerminalTab& tab)
{
const auto lastFocusedProfileOpt = tab.GetFocusedProfile();
if (lastFocusedProfileOpt.has_value())
@@ -990,30 +981,32 @@ namespace winrt::TerminalApp::implementation
{
if (auto index{ _GetFocusedTabIndex() })
{
try
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
{
auto focusedTab = _GetStrongTabImpl(*index);
// TODO: GH#5047 - In the future, we should get the Profile of
// the focused pane, and use that to build a new instance of the
// settings so we can duplicate this tab/pane.
//
// Currently, if the profile doesn't exist anymore in our
// settings, we'll silently do nothing.
//
// In the future, it will be preferable to just duplicate the
// current control's settings, but we can't do that currently,
// because we won't be able to create a new instance of the
// connection without keeping an instance of the original Profile
// object around.
const auto& profileGuid = focusedTab->GetFocusedProfile();
if (profileGuid.has_value())
try
{
const auto settings{ winrt::make<TerminalSettings>(_settings, profileGuid.value(), *_bindings) };
_CreateNewTabFromSettings(profileGuid.value(), settings);
// TODO: GH#5047 - In the future, we should get the Profile of
// the focused pane, and use that to build a new instance of the
// settings so we can duplicate this tab/pane.
//
// Currently, if the profile doesn't exist anymore in our
// settings, we'll silently do nothing.
//
// In the future, it will be preferable to just duplicate the
// current control's settings, but we can't do that currently,
// because we won't be able to create a new instance of the
// connection without keeping an instance of the original Profile
// object around.
const auto& profileGuid = terminalTab->GetFocusedProfile();
if (profileGuid.has_value())
{
const auto settings{ winrt::make<TerminalSettings>(_settings, profileGuid.value(), *_bindings) };
_CreateNewTabFromSettings(profileGuid.value(), settings);
}
}
CATCH_LOG();
}
CATCH_LOG();
}
}
@@ -1040,8 +1033,8 @@ namespace winrt::TerminalApp::implementation
{
// Removing the tab from the collection should destroy its control and disconnect its connection,
// but it doesn't always do so. The UI tree may still be holding the control and preventing its destruction.
auto tab{ _GetStrongTabImpl(tabIndex) };
tab->Shutdown();
auto tab{ _tabs.GetAt(tabIndex) };
tab.Shutdown();
uint32_t mruIndex;
if (_mruTabActions.IndexOf(_tabs.GetAt(tabIndex).SwitchToTabCommand(), mruIndex))
@@ -1091,8 +1084,8 @@ namespace winrt::TerminalApp::implementation
// here. If we don't, then the TabView will technically not have a
// selected item at all, which can make things like ClosePane not
// work correctly.
auto newSelectedTab{ _GetStrongTabImpl(newSelectedIndex) };
_tabView.SelectedItem(newSelectedTab->GetTabViewItem());
auto newSelectedTab{ _tabs.GetAt(newSelectedIndex) };
_tabView.SelectedItem(newSelectedTab.TabViewItem());
}
// GH#5559 - If we were in the middle of a drag/drop, end it by clearing
@@ -1114,7 +1107,7 @@ namespace winrt::TerminalApp::implementation
// Arguments:
// - term: The newly created TermControl to connect the events for
// - hostingTab: The Tab that's hosting this TermControl instance
void TerminalPage::_RegisterTerminalEvents(TermControl term, Tab& hostingTab)
void TerminalPage::_RegisterTerminalEvents(TermControl term, TerminalTab& hostingTab)
{
// Add an event handler when the terminal's selection wants to be copied.
// When the text buffer data is retrieved, we'll copy the data into the Clipboard
@@ -1144,12 +1137,12 @@ namespace winrt::TerminalApp::implementation
}
else if (args.PropertyName() == L"Content")
{
if (tab == page->_GetFocusedTab())
if (*tab == page->_GetFocusedTab())
{
page->_tabContent.Children().Clear();
page->_tabContent.Children().Append(tab->Content());
tab->SetFocused(true);
tab->Focus(FocusState::Programmatic);
}
}
}
@@ -1160,7 +1153,7 @@ namespace winrt::TerminalApp::implementation
auto page{ weakThis.get() };
auto tab{ weakTab.get() };
if (page && tab && tab->IsFocused())
if (page && tab && (tab->FocusState() != FocusState::Unfocused))
{
page->_SetNonClientAreaColors(color);
}
@@ -1170,7 +1163,7 @@ namespace winrt::TerminalApp::implementation
auto page{ weakThis.get() };
auto tab{ weakTab.get() };
if (page && tab && tab->IsFocused())
if (page && tab && (tab->FocusState() != FocusState::Unfocused))
{
page->_ClearNonClientAreaColors();
}
@@ -1234,8 +1227,8 @@ namespace winrt::TerminalApp::implementation
{
if (_startupState == StartupState::InStartup)
{
auto tab{ _GetStrongTabImpl(tabIndex) };
_tabView.SelectedItem(tab->GetTabViewItem());
auto tab{ _tabs.GetAt(tabIndex) };
_tabView.SelectedItem(tab.TabViewItem());
_UpdatedSelectedTab(tabIndex);
}
else
@@ -1263,16 +1256,21 @@ namespace winrt::TerminalApp::implementation
// - <none>
void TerminalPage::_UnZoomIfNeeded()
{
auto activeTab = _GetFocusedTab();
if (activeTab && activeTab->IsZoomed())
if (auto focusedTab = _GetFocusedTab())
{
// Remove the content from the tab first, so Pane::UnZoom can
// re-attach the content to the tree w/in the pane
_tabContent.Children().Clear();
// In ExitZoom, we'll change the Tab's Content(), triggering the
// content changed event, which will re-attach the tab's new content
// root to the tree.
activeTab->ExitZoom();
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
{
if (activeTab->IsZoomed())
{
// Remove the content from the tab first, so Pane::UnZoom can
// re-attach the content to the tree w/in the pane
_tabContent.Children().Clear();
// In ExitZoom, we'll change the Tab's Content(), triggering the
// content changed event, which will re-attach the tab's new content
// root to the tree.
activeTab->ExitZoom();
}
}
}
}
@@ -1288,9 +1286,11 @@ namespace winrt::TerminalApp::implementation
{
if (auto index{ _GetFocusedTabIndex() })
{
auto focusedTab{ _GetStrongTabImpl(*index) };
_UnZoomIfNeeded();
focusedTab->NavigateFocus(direction);
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
{
_UnZoomIfNeeded();
terminalTab->NavigateFocus(direction);
}
}
}
@@ -1298,13 +1298,12 @@ namespace winrt::TerminalApp::implementation
{
if (auto index{ _GetFocusedTabIndex() })
{
auto focusedTab{ _GetStrongTabImpl(*index) };
return focusedTab->GetActiveTerminalControl();
}
else
{
return nullptr;
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
{
return terminalTab->GetActiveTerminalControl();
}
}
return nullptr;
}
// Method Description:
@@ -1327,11 +1326,11 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - returns a com_ptr to the currently focused tab. This might return null,
// so make sure to check the result!
winrt::com_ptr<Tab> TerminalPage::_GetFocusedTab()
winrt::TerminalApp::TabBase TerminalPage::_GetFocusedTab()
{
if (auto index{ _GetFocusedTabIndex() })
{
return _GetStrongTabImpl(*index);
return _tabs.GetAt(*index);
}
return nullptr;
}
@@ -1356,8 +1355,8 @@ namespace winrt::TerminalApp::implementation
if (auto page{ weakThis.get() })
{
auto tab{ _GetStrongTabImpl(tabIndex) };
_tabView.SelectedItem(tab->GetTabViewItem());
auto tabToFocus = page->_tabs.GetAt(tabIndex);
_tabView.SelectedItem(tabToFocus.TabViewItem());
}
}
@@ -1379,9 +1378,11 @@ namespace winrt::TerminalApp::implementation
{
if (auto index{ _GetFocusedTabIndex() })
{
auto focusedTab{ _GetStrongTabImpl(*index) };
_UnZoomIfNeeded();
focusedTab->ClosePane();
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
{
_UnZoomIfNeeded();
terminalTab->ClosePane();
}
}
}
@@ -1422,22 +1423,24 @@ namespace winrt::TerminalApp::implementation
{
if (auto index{ _GetFocusedTabIndex() })
{
auto focusedTab{ _GetStrongTabImpl(*index) };
uint32_t realRowsToScroll;
if (rowsToScroll == nullptr)
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
{
// The magic value of WHEEL_PAGESCROLL indicates that we need to scroll the entire page
realRowsToScroll = _systemRowsToScroll == WHEEL_PAGESCROLL ?
focusedTab->GetActiveTerminalControl().GetViewHeight() :
_systemRowsToScroll;
uint32_t realRowsToScroll;
if (rowsToScroll == nullptr)
{
// The magic value of WHEEL_PAGESCROLL indicates that we need to scroll the entire page
realRowsToScroll = _systemRowsToScroll == WHEEL_PAGESCROLL ?
terminalTab->GetActiveTerminalControl().GetViewHeight() :
_systemRowsToScroll;
}
else
{
// use the custom value specified in the command
realRowsToScroll = rowsToScroll.Value();
}
auto scrollDelta = _ComputeScrollDelta(scrollDirection, realRowsToScroll);
terminalTab->Scroll(scrollDelta);
}
else
{
// use the custom value specified in the command
realRowsToScroll = rowsToScroll.Value();
}
auto scrollDelta = _ComputeScrollDelta(scrollDirection, realRowsToScroll);
focusedTab->Scroll(scrollDelta);
}
}
@@ -1470,9 +1473,16 @@ namespace winrt::TerminalApp::implementation
return;
}
auto focusedTab = _GetTerminalTabImpl(_tabs.GetAt(*indexOpt));
// Do nothing if the focused tab isn't a TerminalTab
if (!focusedTab)
{
return;
}
try
{
auto focusedTab = _GetStrongTabImpl(*indexOpt);
TerminalApp::TerminalSettings controlSettings;
GUID realGuid;
bool profileFound = false;
@@ -1546,9 +1556,11 @@ namespace winrt::TerminalApp::implementation
{
if (auto index{ _GetFocusedTabIndex() })
{
auto focusedTab{ _GetStrongTabImpl(*index) };
_UnZoomIfNeeded();
focusedTab->ResizePane(direction);
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
{
_UnZoomIfNeeded();
terminalTab->ResizePane(direction);
}
}
}
@@ -1566,11 +1578,13 @@ namespace winrt::TerminalApp::implementation
return;
}
const auto control = _GetActiveControl();
const auto termHeight = control.GetViewHeight();
auto focusedTab{ _GetStrongTabImpl(*indexOpt) };
auto scrollDelta = _ComputeScrollDelta(scrollDirection, termHeight);
focusedTab->Scroll(scrollDelta);
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*indexOpt)))
{
const auto control = _GetActiveControl();
const auto termHeight = control.GetViewHeight();
auto scrollDelta = _ComputeScrollDelta(scrollDirection, termHeight);
terminalTab->Scroll(scrollDelta);
}
}
// Method Description:
@@ -1681,8 +1695,10 @@ namespace winrt::TerminalApp::implementation
{
if (auto index{ _GetFocusedTabIndex() })
{
auto focusedTab{ _GetStrongTabImpl(*index) };
return focusedTab->CalcSnappedDimension(widthOrHeight, dimension);
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
{
return terminalTab->CalcSnappedDimension(widthOrHeight, dimension);
}
}
}
return dimension;
@@ -1960,18 +1976,17 @@ namespace winrt::TerminalApp::implementation
// Unfocus all the tabs.
for (auto tab : _tabs)
{
auto tabImpl{ _GetStrongTabImpl(tab) };
tabImpl->SetFocused(false);
tab.Focus(FocusState::Unfocused);
}
if (index >= 0)
{
try
{
auto tab{ _GetStrongTabImpl(index) };
auto tab{ _tabs.GetAt(index) };
_tabContent.Children().Clear();
_tabContent.Children().Append(tab->Content());
_tabContent.Children().Append(tab.Content());
// GH#7409: If the tab switcher is open, then we _don't_ want to
// automatically focus the new tab here. The tab switcher wants
@@ -1985,12 +2000,12 @@ namespace winrt::TerminalApp::implementation
// need to worry about focus getting lost.
if (CommandPalette().Visibility() != Visibility::Visible)
{
tab->SetFocused(true);
tab.Focus(FocusState::Programmatic);
_UpdateMRUTab(index);
}
// Raise an event that our title changed
_titleChangeHandlers(*this, tab->GetActiveTitle());
_titleChangeHandlers(*this, tab.Title());
}
CATCH_LOG();
}
@@ -2025,8 +2040,10 @@ namespace winrt::TerminalApp::implementation
const auto newSize = e.NewSize();
for (auto tab : _tabs)
{
auto tabImpl{ _GetStrongTabImpl(tab) };
tabImpl->ResizeContent(newSize);
if (auto terminalTab = _GetTerminalTabImpl(tab))
{
terminalTab->ResizeContent(newSize);
}
}
}
@@ -2097,9 +2114,10 @@ namespace winrt::TerminalApp::implementation
for (auto tab : _tabs)
{
// Attempt to reload the settings of any panes with this profile
auto tabImpl{ _GetStrongTabImpl(tab) };
tabImpl->UpdateSettings(settings, profileGuid);
if (auto terminalTab = _GetTerminalTabImpl(tab))
{
terminalTab->UpdateSettings(settings, profileGuid);
}
}
}
CATCH_LOG();
@@ -2111,11 +2129,17 @@ namespace winrt::TerminalApp::implementation
// anymore, so we can't possibly update its settings.
// Update the icon of the tab for the currently focused profile in that tab.
// Only do this for TerminalTabs. Other types of tabs won't have multiple panes
// and profiles so the Title and Icon will be set once and only once on init.
for (auto tab : _tabs)
{
auto tabImpl{ _GetStrongTabImpl(tab) };
_UpdateTabIcon(*tabImpl);
_UpdateTitle(*tabImpl);
if (auto terminalTab = _GetTerminalTabImpl(tab))
{
_UpdateTabIcon(*terminalTab);
// Force the TerminalTab to re-grab its currently active control's title.
terminalTab->UpdateTitle();
}
}
auto weakThis{ get_weak() };
@@ -2255,10 +2279,9 @@ namespace winrt::TerminalApp::implementation
for (const auto& tab : _tabs)
{
auto tabImpl{ _GetStrongTabImpl(tab) };
if (tabImpl->GetTabViewItem().ContextFlyout())
if (tab.TabViewItem().ContextFlyout())
{
tabImpl->GetTabViewItem().ContextFlyout().Hide();
tab.TabViewItem().ContextFlyout().Hide();
}
}
}
@@ -2317,32 +2340,6 @@ namespace winrt::TerminalApp::implementation
_alwaysOnTopChangedHandlers(*this, nullptr);
}
// Method Description:
// - Returns a com_ptr to the implementation type of the tab at the given index
// Arguments:
// - index: an unsigned integer index to a tab in _tabs
// Return Value:
// - a com_ptr to the implementation type of the Tab
winrt::com_ptr<Tab> TerminalPage::_GetStrongTabImpl(const uint32_t index) const
{
winrt::com_ptr<Tab> tabImpl;
tabImpl.copy_from(winrt::get_self<Tab>(_tabs.GetAt(index)));
return tabImpl;
}
// Method Description:
// - Returns a com_ptr to the implementation type of the given projected Tab
// Arguments:
// - tab: the projected type of a Tab
// Return Value:
// - a com_ptr to the implementation type of the Tab
winrt::com_ptr<Tab> TerminalPage::_GetStrongTabImpl(const ::winrt::TerminalApp::Tab& tab) const
{
winrt::com_ptr<Tab> tabImpl;
tabImpl.copy_from(winrt::get_self<Tab>(tab));
return tabImpl;
}
// Method Description:
// - Sets the tab split button color when a new tab color is selected
// Arguments:
@@ -2544,7 +2541,7 @@ namespace winrt::TerminalApp::implementation
// Return focus to the active control
if (auto index{ _GetFocusedTabIndex() })
{
_GetStrongTabImpl(index.value())->SetFocused(true);
_tabs.GetAt(*index).Focus(FocusState::Programmatic);
_UpdateMRUTab(index.value());
}
}
@@ -2583,10 +2580,75 @@ namespace winrt::TerminalApp::implementation
const uint32_t size = _tabs.Size();
for (uint32_t i = 0; i < size; ++i)
{
_GetStrongTabImpl(i)->UpdateTabViewIndex(i, size);
auto tab{ _tabs.GetAt(i) };
auto tabImpl{ winrt::get_self<TabBase>(tab) };
tabImpl->UpdateTabViewIndex(i, size);
}
}
// Method Description:
// - Returns a com_ptr to the implementation type of the given tab if it's a TerminalTab.
// If the tab is not a TerminalTab, returns nullptr.
// Arguments:
// - tab: the projected type of a Tab
// Return Value:
// - If the tab is a TerminalTab, a com_ptr to the implementation type.
// If the tab is not a TerminalTab, nullptr
winrt::com_ptr<TerminalTab> TerminalPage::_GetTerminalTabImpl(const TerminalApp::TabBase& tab) const
{
if (auto terminalTab = tab.try_as<TerminalApp::TerminalTab>())
{
winrt::com_ptr<TerminalTab> tabImpl;
tabImpl.copy_from(winrt::get_self<TerminalTab>(terminalTab));
return tabImpl;
}
else
{
return nullptr;
}
}
// Method Description:
// - The TabView does not apply compact sizing to items added after Compact is enabled.
// By forcibly reapplying compact sizing every time we add a new tab, we'll make sure
// that it works.
// Workaround from https://github.com/microsoft/microsoft-ui-xaml/issues/2711
// TODO: Remove this function and its calls when ingesting the above changes.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TerminalPage::_ReapplyCompactTabSize()
{
if (_tabView.TabWidthMode() == MUX::Controls::TabViewWidthMode::Compact)
{
_tabView.UpdateLayout();
_tabView.TabWidthMode(MUX::Controls::TabViewWidthMode::Compact);
}
}
// Method Description:
// - Initializes a SwitchToTab command object for this Tab instance.
// This should be done before the tab is added to the _tabs vector so that
// controls like the CmdPal that observe the vector changes can always expect
// a SwitchToTab command to be available.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TerminalPage::_MakeSwitchToTabCommand(const TerminalApp::TabBase& tab, const uint32_t index)
{
SwitchToTabArgs args{ index };
ActionAndArgs focusTabAction{ ShortcutAction::SwitchToTab, args };
Command command;
command.Action(focusTabAction);
command.Name(tab.Title());
command.Icon(tab.Icon());
tab.SwitchToTabCommand(command);
}
// Method Description:
// - Computes the delta for scrolling the tab's viewport.
// Arguments:

View File

@@ -4,7 +4,7 @@
#pragma once
#include "TerminalPage.g.h"
#include "Tab.h"
#include "TerminalTab.h"
#include "AppKeyBindings.h"
#include "TerminalSettings.h"
@@ -96,10 +96,10 @@ namespace winrt::TerminalApp::implementation
Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr };
Windows::Foundation::Collections::IObservableVector<TerminalApp::Tab> _tabs;
Windows::Foundation::Collections::IObservableVector<TerminalApp::TabBase> _tabs;
Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Model::Command> _mruTabActions;
winrt::com_ptr<Tab> _GetStrongTabImpl(const uint32_t index) const;
winrt::com_ptr<Tab> _GetStrongTabImpl(const ::winrt::TerminalApp::Tab& tab) const;
winrt::com_ptr<TerminalTab> _GetTerminalTabImpl(const TerminalApp::TabBase& tab) const;
void _UpdateTabIndices();
bool _isInFocusMode{ false };
@@ -146,8 +146,8 @@ namespace winrt::TerminalApp::implementation
void _HookupKeyBindings(const Microsoft::Terminal::Settings::Model::KeyMapping& keymap) noexcept;
void _RegisterActionCallbacks();
void _UpdateTitle(const Tab& tab);
void _UpdateTabIcon(Tab& tab);
void _UpdateTitle(const TerminalTab& tab);
void _UpdateTabIcon(TerminalTab& tab);
void _UpdateTabView();
void _UpdateTabWidthMode();
void _UpdateCommandsForPalette();
@@ -159,7 +159,7 @@ namespace winrt::TerminalApp::implementation
void _RemoveTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem);
void _RemoveTabViewItemByIndex(uint32_t tabIndex);
void _RegisterTerminalEvents(Microsoft::Terminal::TerminalControl::TermControl term, Tab& hostingTab);
void _RegisterTerminalEvents(Microsoft::Terminal::TerminalControl::TermControl term, TerminalTab& hostingTab);
void _SelectNextTab(const bool bMoveRight);
bool _SelectTab(const uint32_t tabIndex);
@@ -167,7 +167,7 @@ namespace winrt::TerminalApp::implementation
winrt::Microsoft::Terminal::TerminalControl::TermControl _GetActiveControl();
std::optional<uint32_t> _GetFocusedTabIndex() const noexcept;
winrt::com_ptr<Tab> _GetFocusedTab();
TerminalApp::TabBase _GetFocusedTab();
winrt::fire_and_forget _SetFocusedTabIndex(const uint32_t tabIndex);
void _CloseFocusedTab();
void _CloseFocusedPane();
@@ -218,6 +218,12 @@ namespace winrt::TerminalApp::implementation
void _UnZoomIfNeeded();
void _OpenSettingsUI();
void _ReapplyCompactTabSize();
void _MakeSwitchToTabCommand(const TerminalApp::TabBase& tab, const uint32_t index);
static int _ComputeScrollDelta(ScrollDirection scrollDirection, const uint32_t rowsToScroll);
static uint32_t _ReadSystemRowsToScroll();

View File

@@ -4,8 +4,8 @@
#include "pch.h"
#include <LibraryResources.h>
#include "ColorPickupFlyout.h"
#include "Tab.h"
#include "Tab.g.cpp"
#include "TerminalTab.h"
#include "TerminalTab.g.cpp"
#include "Utils.h"
#include "ColorHelper.h"
@@ -24,7 +24,7 @@ namespace winrt
namespace winrt::TerminalApp::implementation
{
Tab::Tab(const GUID& profile, const TermControl& control)
TerminalTab::TerminalTab(const GUID& profile, const TermControl& control)
{
_rootPane = std::make_shared<Pane>(profile, control, true);
@@ -36,7 +36,6 @@ namespace winrt::TerminalApp::implementation
Content(_rootPane->GetRootElement());
_MakeTabViewItem();
_MakeSwitchToTabCommand();
_CreateContextMenu();
}
@@ -46,18 +45,18 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - <none>
void Tab::_MakeTabViewItem()
void TerminalTab::_MakeTabViewItem()
{
_tabViewItem = ::winrt::MUX::Controls::TabViewItem{};
TabViewItem(::winrt::MUX::Controls::TabViewItem{});
_tabViewItem.DoubleTapped([weakThis = get_weak()](auto&& /*s*/, auto&& /*e*/) {
TabViewItem().DoubleTapped([weakThis = get_weak()](auto&& /*s*/, auto&& /*e*/) {
if (auto tab{ weakThis.get() })
{
tab->ActivateTabRenamer();
}
});
_UpdateTitle();
UpdateTitle();
_RecalculateAndApplyTabColor();
}
@@ -72,22 +71,11 @@ namespace winrt::TerminalApp::implementation
// Return Value:
// - nullptr if no children were marked `_lastFocused`, else the TermControl
// that was last focused.
TermControl Tab::GetActiveTerminalControl() const
TermControl TerminalTab::GetActiveTerminalControl() const
{
return _activePane->GetTerminalControl();
}
// Method Description:
// - Gets the TabViewItem that represents this Tab
// Arguments:
// - <none>
// Return Value:
// - The TabViewItem that represents this Tab
winrt::MUX::Controls::TabViewItem Tab::GetTabViewItem()
{
return _tabViewItem;
}
// Method Description:
// - Called after construction of a Tab object to bind event handlers to its
// associated Pane and TermControl object
@@ -95,39 +83,29 @@ namespace winrt::TerminalApp::implementation
// - control: reference to the TermControl object to bind event to
// Return Value:
// - <none>
void Tab::Initialize(const TermControl& control)
void TerminalTab::Initialize(const TermControl& control)
{
_BindEventHandlers(control);
}
// Method Description:
// - Returns true if this is the currently focused tab. For any set of tabs,
// there should only be one tab that is marked as focused, though each tab has
// no control over the other tabs in the set.
// Arguments:
// - <none>
// Return Value:
// - true iff this tab is focused.
bool Tab::IsFocused() const noexcept
{
return _focused;
}
// Method Description:
// - Updates our focus state. If we're gaining focus, make sure to transfer
// focus to the last focused terminal control in our tree of controls.
// Arguments:
// - focused: our new focus state. If true, we should be focused. If false, we
// should be unfocused.
// - focused: our new focus state
// Return Value:
// - <none>
void Tab::SetFocused(const bool focused)
void TerminalTab::Focus(WUX::FocusState focusState)
{
_focused = focused;
_focusState = focusState;
if (_focused)
if (_focusState != FocusState::Unfocused)
{
_Focus();
auto lastFocusedControl = GetActiveTerminalControl();
if (lastFocusedControl)
{
lastFocusedControl.Focus(_focusState);
}
}
}
@@ -140,7 +118,7 @@ namespace winrt::TerminalApp::implementation
// Return Value:
// - nullopt if no children of this tab were the last control to be
// focused, else the GUID of the profile of the last control to be focused
std::optional<GUID> Tab::GetFocusedProfile() const noexcept
std::optional<GUID> TerminalTab::GetFocusedProfile() const noexcept
{
return _activePane->GetFocusedProfile();
}
@@ -152,7 +130,7 @@ namespace winrt::TerminalApp::implementation
// - control: reference to the TermControl object to bind event to
// Return Value:
// - <none>
void Tab::_BindEventHandlers(const TermControl& control) noexcept
void TerminalTab::_BindEventHandlers(const TermControl& control) noexcept
{
_AttachEventHandlersToPane(_rootPane);
_AttachEventHandlersToControl(control);
@@ -165,35 +143,18 @@ namespace winrt::TerminalApp::implementation
// - profile: The GUID of the profile these settings should apply to.
// Return Value:
// - <none>
void Tab::UpdateSettings(const TerminalSettings& settings, const GUID& profile)
void TerminalTab::UpdateSettings(const TerminalSettings& settings, const GUID& profile)
{
_rootPane->UpdateSettings(settings, profile);
}
// Method Description:
// - Focus the last focused control in our tree of panes.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Tab::_Focus()
{
_focused = true;
auto lastFocusedControl = GetActiveTerminalControl();
if (lastFocusedControl)
{
lastFocusedControl.Focus(FocusState::Programmatic);
}
}
// Method Description:
// - Set the icon on the TabViewItem for this tab.
// Arguments:
// - iconPath: The new path string to use as the IconPath for our TabViewItem
// Return Value:
// - <none>
winrt::fire_and_forget Tab::UpdateIcon(const winrt::hstring iconPath)
winrt::fire_and_forget TerminalTab::UpdateIcon(const winrt::hstring iconPath)
{
// Don't reload our icon if it hasn't changed.
if (iconPath == _lastIconPath)
@@ -205,13 +166,13 @@ namespace winrt::TerminalApp::implementation
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(_tabViewItem.Dispatcher());
co_await winrt::resume_foreground(TabViewItem().Dispatcher());
if (auto tab{ weakThis.get() })
{
// The TabViewItem Icon needs MUX while the IconSourceElement in the CommandPalette needs WUX...
IconSource(IconPathConverter::IconSourceWUX(_lastIconPath));
_tabViewItem.IconSource(IconPathConverter::IconSourceMUX(_lastIconPath));
Icon(_lastIconPath);
TabViewItem().IconSource(IconPathConverter::IconSourceMUX(_lastIconPath));
// Update SwitchToTab command's icon
SwitchToTabCommand().Icon(_lastIconPath);
@@ -225,7 +186,7 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - the title string of the last focused terminal control in our tree.
winrt::hstring Tab::GetActiveTitle() const
winrt::hstring TerminalTab::_GetActiveTitle() const
{
if (!_runtimeTabText.empty())
{
@@ -243,14 +204,14 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - <none>
winrt::fire_and_forget Tab::_UpdateTitle()
winrt::fire_and_forget TerminalTab::UpdateTitle()
{
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(_tabViewItem.Dispatcher());
co_await winrt::resume_foreground(TabViewItem().Dispatcher());
if (auto tab{ weakThis.get() })
{
// Bubble our current tab text to anyone who's listening for changes.
Title(GetActiveTitle());
Title(_GetActiveTitle());
// Update SwitchToTab command's name
SwitchToTabCommand().Name(Title());
@@ -268,7 +229,7 @@ namespace winrt::TerminalApp::implementation
// - delta: a number of lines to move the viewport relative to the current viewport.
// Return Value:
// - <none>
winrt::fire_and_forget Tab::Scroll(const int delta)
winrt::fire_and_forget TerminalTab::Scroll(const int delta)
{
auto control = GetActiveTerminalControl();
@@ -284,7 +245,7 @@ namespace winrt::TerminalApp::implementation
// - splitType: The type of split we want to create.
// Return Value:
// - True if the focused pane can be split. False otherwise.
bool Tab::CanSplitPane(SplitState splitType)
bool TerminalTab::CanSplitPane(SplitState splitType)
{
return _activePane->CanSplit(splitType);
}
@@ -298,7 +259,7 @@ namespace winrt::TerminalApp::implementation
// - control: A TermControl to use in the new pane.
// Return Value:
// - <none>
void Tab::SplitPane(SplitState splitType, const GUID& profile, TermControl& control)
void TerminalTab::SplitPane(SplitState splitType, const GUID& profile, TermControl& control)
{
auto [first, second] = _activePane->Split(splitType, profile, control);
_activePane = first;
@@ -318,7 +279,7 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - See Pane::CalcSnappedDimension
float Tab::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const
float TerminalTab::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const
{
return _rootPane->CalcSnappedDimension(widthOrHeight, dimension);
}
@@ -330,7 +291,7 @@ namespace winrt::TerminalApp::implementation
// - newSize: the amount of space that the panes have to fill now.
// Return Value:
// - <none>
void Tab::ResizeContent(const winrt::Windows::Foundation::Size& newSize)
void TerminalTab::ResizeContent(const winrt::Windows::Foundation::Size& newSize)
{
// NOTE: This _must_ be called on the root pane, so that it can propagate
// throughout the entire tree.
@@ -344,7 +305,7 @@ namespace winrt::TerminalApp::implementation
// - direction: The direction to move the separator in.
// Return Value:
// - <none>
void Tab::ResizePane(const Direction& direction)
void TerminalTab::ResizePane(const Direction& direction)
{
// NOTE: This _must_ be called on the root pane, so that it can propagate
// throughout the entire tree.
@@ -358,7 +319,7 @@ namespace winrt::TerminalApp::implementation
// - direction: The direction to move the focus in.
// Return Value:
// - <none>
void Tab::NavigateFocus(const Direction& direction)
void TerminalTab::NavigateFocus(const Direction& direction)
{
// NOTE: This _must_ be called on the root pane, so that it can propagate
// throughout the entire tree.
@@ -367,7 +328,7 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - Prepares this tab for being removed from the UI hierarchy by shutting down all active connections.
void Tab::Shutdown()
void TerminalTab::Shutdown()
{
_rootPane->Shutdown();
}
@@ -380,21 +341,21 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - <none>
void Tab::ClosePane()
void TerminalTab::ClosePane()
{
_activePane->Close();
}
void Tab::SetTabText(winrt::hstring title)
void TerminalTab::SetTabText(winrt::hstring title)
{
_runtimeTabText = title;
_UpdateTitle();
UpdateTitle();
}
void Tab::ResetTabText()
void TerminalTab::ResetTabText()
{
_runtimeTabText = L"";
_UpdateTitle();
UpdateTitle();
}
// Method Description:
@@ -404,7 +365,7 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - <none>
void Tab::ActivateTabRenamer()
void TerminalTab::ActivateTabRenamer()
{
_inRename = true;
_receivedKeyDown = false;
@@ -421,7 +382,7 @@ namespace winrt::TerminalApp::implementation
// - control: the TermControl to add events to.
// Return Value:
// - <none>
void Tab::_AttachEventHandlersToControl(const TermControl& control)
void TerminalTab::_AttachEventHandlersToControl(const TermControl& control)
{
auto weakThis{ get_weak() };
@@ -431,7 +392,7 @@ namespace winrt::TerminalApp::implementation
{
// The title of the control changed, but not necessarily the title of the tab.
// Set the tab's text to the active panes' text.
tab->_UpdateTitle();
tab->UpdateTitle();
}
});
@@ -468,7 +429,7 @@ namespace winrt::TerminalApp::implementation
// - pane: a Pane to mark as active.
// Return Value:
// - <none>
void Tab::_UpdateActivePane(std::shared_ptr<Pane> pane)
void TerminalTab::_UpdateActivePane(std::shared_ptr<Pane> pane)
{
// Clear the active state of the entire tree, and mark only the pane as active.
_rootPane->ClearActive();
@@ -476,7 +437,7 @@ namespace winrt::TerminalApp::implementation
_activePane->SetActive();
// Update our own title text to match the newly-active pane.
_UpdateTitle();
UpdateTitle();
// Raise our own ActivePaneChanged event.
_ActivePaneChangedHandlers();
@@ -491,7 +452,7 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - <none>
void Tab::_AttachEventHandlersToPane(std::shared_ptr<Pane> pane)
void TerminalTab::_AttachEventHandlersToPane(std::shared_ptr<Pane> pane)
{
auto weakThis{ get_weak() };
@@ -531,7 +492,7 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - <none>
void Tab::_CreateContextMenu()
void TerminalTab::_CreateContextMenu()
{
auto weakThis{ get_weak() };
@@ -605,57 +566,7 @@ namespace winrt::TerminalApp::implementation
newTabFlyout.Items().Append(menuSeparator);
newTabFlyout.Items().Append(_CreateCloseSubMenu());
newTabFlyout.Items().Append(closeTabMenuItem);
_tabViewItem.ContextFlyout(newTabFlyout);
}
// Method Description:
// - Creates a sub-menu containing menu items to close multiple tabs
// Arguments:
// - <none>
// Return Value:
// - the created MenuFlyoutSubItem
Controls::MenuFlyoutSubItem Tab::_CreateCloseSubMenu()
{
auto weakThis{ get_weak() };
// Close tabs after
_closeTabsAfterMenuItem.Click([weakThis](auto&&, auto&&) {
if (auto tab{ weakThis.get() })
{
tab->_CloseTabsAfter();
}
});
_closeTabsAfterMenuItem.Text(RS_(L"TabCloseAfter"));
// Close other tabs
_closeOtherTabsMenuItem.Click([weakThis](auto&&, auto&&) {
if (auto tab{ weakThis.get() })
{
tab->_CloseOtherTabs();
}
});
_closeOtherTabsMenuItem.Text(RS_(L"TabCloseOther"));
Controls::MenuFlyoutSubItem closeSubMenu;
closeSubMenu.Text(RS_(L"TabCloseSubMenu"));
closeSubMenu.Items().Append(_closeTabsAfterMenuItem);
closeSubMenu.Items().Append(_closeOtherTabsMenuItem);
return closeSubMenu;
}
// Method Description:
// - Enable the Close menu items based on tab index and total number of tabs
// Arguments:
// - <none>
// Return Value:
// - <none>
void Tab::_EnableCloseMenuItems()
{
// close other tabs is enabled only if there are other tabs
_closeOtherTabsMenuItem.IsEnabled(TabViewNumTabs() > 1);
// close tabs after is enabled only if there are other tabs on the right
_closeTabsAfterMenuItem.IsEnabled(TabViewIndex() < TabViewNumTabs() - 1);
TabViewItem().ContextFlyout(newTabFlyout);
}
// Method Description:
@@ -670,9 +581,9 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - <none>
void Tab::_UpdateTabHeader()
void TerminalTab::_UpdateTabHeader()
{
winrt::hstring tabText{ GetActiveTitle() };
winrt::hstring tabText{ Title() };
if (!_inRename)
{
@@ -690,13 +601,13 @@ namespace winrt::TerminalApp::implementation
tb.Text(tabText);
sp.Children().Append(tb);
_tabViewItem.Header(sp);
TabViewItem().Header(sp);
}
else
{
// If we're not currently in the process of renaming the tab,
// then just set the tab's text to whatever our active title is.
_tabViewItem.Header(winrt::box_value(tabText));
TabViewItem().Header(winrt::box_value(tabText));
}
}
else
@@ -713,9 +624,9 @@ namespace winrt::TerminalApp::implementation
// - tabText: This should be the text to initialize the rename text box with.
// Return Value:
// - <none>
void Tab::_ConstructTabRenameBox(const winrt::hstring& tabText)
void TerminalTab::_ConstructTabRenameBox(const winrt::hstring& tabText)
{
if (_tabViewItem.Header().try_as<Controls::TextBox>())
if (TabViewItem().Header().try_as<Controls::TextBox>())
{
return;
}
@@ -763,7 +674,7 @@ namespace winrt::TerminalApp::implementation
{
tab->_runtimeTabText = textBox.Text();
tab->_inRename = false;
tab->_UpdateTitle();
tab->UpdateTitle();
}
});
@@ -793,7 +704,7 @@ namespace winrt::TerminalApp::implementation
e.Handled(true);
textBox.Text(tab->_runtimeTabText);
tab->_inRename = false;
tab->_UpdateTitle();
tab->UpdateTitle();
break;
}
}
@@ -803,7 +714,7 @@ namespace winrt::TerminalApp::implementation
_tabRenameBoxLayoutUpdatedRevoker = tabTextBox.LayoutUpdated(winrt::auto_revoke, [this](auto&&, auto&&) {
// Curiously, the sender for this event is null, so we have to
// get the TextBox from the Tab's Header().
auto textBox{ _tabViewItem.Header().try_as<Controls::TextBox>() };
auto textBox{ TabViewItem().Header().try_as<Controls::TextBox>() };
if (textBox)
{
textBox.SelectAll();
@@ -813,7 +724,7 @@ namespace winrt::TerminalApp::implementation
_tabRenameBoxLayoutUpdatedRevoker.revoke();
});
_tabViewItem.Header(tabTextBox);
TabViewItem().Header(tabTextBox);
}
// Method Description:
@@ -822,7 +733,7 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - The tab's color, if any
std::optional<winrt::Windows::UI::Color> Tab::GetTabColor()
std::optional<winrt::Windows::UI::Color> TerminalTab::GetTabColor()
{
const auto currControlColor{ GetActiveTerminalControl().TabColor() };
std::optional<winrt::Windows::UI::Color> controlTabColor;
@@ -859,7 +770,7 @@ namespace winrt::TerminalApp::implementation
// - color: the color the user picked for their tab
// Return Value:
// - <none>
void Tab::SetRuntimeTabColor(const winrt::Windows::UI::Color& color)
void TerminalTab::SetRuntimeTabColor(const winrt::Windows::UI::Color& color)
{
_runtimeTabColor.emplace(color);
_RecalculateAndApplyTabColor();
@@ -874,11 +785,11 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - <none>
void Tab::_RecalculateAndApplyTabColor()
void TerminalTab::_RecalculateAndApplyTabColor()
{
auto weakThis{ get_weak() };
_tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis]() {
TabViewItem().Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis]() {
auto ptrTab = weakThis.get();
if (!ptrTab)
return;
@@ -906,7 +817,7 @@ namespace winrt::TerminalApp::implementation
// - color: the color the user picked for their tab
// Return Value:
// - <none>
void Tab::_ApplyTabColor(const winrt::Windows::UI::Color& color)
void TerminalTab::_ApplyTabColor(const winrt::Windows::UI::Color& color)
{
Media::SolidColorBrush selectedTabBrush{};
Media::SolidColorBrush deselectedTabBrush{};
@@ -935,15 +846,15 @@ namespace winrt::TerminalApp::implementation
// currently if a tab has a custom color, a deselected state is
// signified by using the same color with a bit ot transparency
_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), selectedTabBrush);
_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackground"), deselectedTabBrush);
_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), hoverTabBrush);
_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPressed"), selectedTabBrush);
_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForeground"), fontBrush);
_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundSelected"), fontBrush);
_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPointerOver"), fontBrush);
_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPressed"), fontBrush);
_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewButtonForegroundActiveTab"), fontBrush);
TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), selectedTabBrush);
TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackground"), deselectedTabBrush);
TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), hoverTabBrush);
TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPressed"), selectedTabBrush);
TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderForeground"), fontBrush);
TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundSelected"), fontBrush);
TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPointerOver"), fontBrush);
TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPressed"), fontBrush);
TabViewItem().Resources().Insert(winrt::box_value(L"TabViewButtonForegroundActiveTab"), fontBrush);
_RefreshVisualState();
@@ -958,7 +869,7 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - <none>
void Tab::ResetRuntimeTabColor()
void TerminalTab::ResetRuntimeTabColor()
{
_runtimeTabColor.reset();
_RecalculateAndApplyTabColor();
@@ -971,7 +882,7 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - <none>
void Tab::_ClearTabBackgroundColor()
void TerminalTab::_ClearTabBackgroundColor()
{
winrt::hstring keys[] = {
L"TabViewItemHeaderBackground",
@@ -989,9 +900,9 @@ namespace winrt::TerminalApp::implementation
for (auto keyString : keys)
{
auto key = winrt::box_value(keyString);
if (_tabViewItem.Resources().HasKey(key))
if (TabViewItem().Resources().HasKey(key))
{
_tabViewItem.Resources().Remove(key);
TabViewItem().Resources().Remove(key);
}
}
@@ -1005,9 +916,9 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - <none>
void Tab::ActivateColorPicker()
void TerminalTab::ActivateColorPicker()
{
_tabColorPickup.ShowAt(_tabViewItem);
_tabColorPickup.ShowAt(TabViewItem());
}
// Method Description:
@@ -1017,17 +928,17 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - <none>
void Tab::_RefreshVisualState()
void TerminalTab::_RefreshVisualState()
{
if (_focused)
if (_focusState != FocusState::Unfocused)
{
VisualStateManager::GoToState(_tabViewItem, L"Normal", true);
VisualStateManager::GoToState(_tabViewItem, L"Selected", true);
VisualStateManager::GoToState(TabViewItem(), L"Normal", true);
VisualStateManager::GoToState(TabViewItem(), L"Selected", true);
}
else
{
VisualStateManager::GoToState(_tabViewItem, L"Selected", true);
VisualStateManager::GoToState(_tabViewItem, L"Normal", true);
VisualStateManager::GoToState(TabViewItem(), L"Selected", true);
VisualStateManager::GoToState(TabViewItem(), L"Normal", true);
}
}
@@ -1037,7 +948,7 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - The total number of leaf panes hosted by this tab.
int Tab::GetLeafPaneCount() const noexcept
int TerminalTab::GetLeafPaneCount() const noexcept
{
return _rootPane->GetLeafPaneCount();
}
@@ -1052,12 +963,12 @@ namespace winrt::TerminalApp::implementation
// Return Value:
// - The SplitState that we should use for an `Automatic` split given
// `availableSpace`
SplitState Tab::PreCalculateAutoSplit(winrt::Windows::Foundation::Size availableSpace) const
SplitState TerminalTab::PreCalculateAutoSplit(winrt::Windows::Foundation::Size availableSpace) const
{
return _rootPane->PreCalculateAutoSplit(_activePane, availableSpace).value_or(SplitState::Vertical);
}
bool Tab::PreCalculateCanSplit(SplitState splitType, winrt::Windows::Foundation::Size availableSpace) const
bool TerminalTab::PreCalculateCanSplit(SplitState splitType, winrt::Windows::Foundation::Size availableSpace) const
{
return _rootPane->PreCalculateCanSplit(_activePane, splitType, availableSpace).value_or(false);
}
@@ -1066,13 +977,13 @@ namespace winrt::TerminalApp::implementation
// - Toggle our zoom state.
// * If we're not zoomed, then zoom the active pane, making it take the
// full size of the tab. We'll achieve this by changing our response to
// Tab::GetRootElement, so that it'll return the zoomed pane only.
// Tab::GetTabContent, so that it'll return the zoomed pane only.
// * If we're currently zoomed on a pane, un-zoom that pane.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Tab::ToggleZoom()
void TerminalTab::ToggleZoom()
{
if (_zoomedPane)
{
@@ -1083,7 +994,7 @@ namespace winrt::TerminalApp::implementation
EnterZoom();
}
}
void Tab::EnterZoom()
void TerminalTab::EnterZoom()
{
_zoomedPane = _activePane;
_rootPane->Maximize(_zoomedPane);
@@ -1091,7 +1002,7 @@ namespace winrt::TerminalApp::implementation
_UpdateTabHeader();
Content(_zoomedPane->GetRootElement());
}
void Tab::ExitZoom()
void TerminalTab::ExitZoom()
{
_rootPane->Restore(_zoomedPane);
_zoomedPane = nullptr;
@@ -1100,60 +1011,12 @@ namespace winrt::TerminalApp::implementation
Content(_rootPane->GetRootElement());
}
bool Tab::IsZoomed()
bool TerminalTab::IsZoomed()
{
return _zoomedPane != nullptr;
}
// Method Description:
// - Initializes a SwitchToTab command object for this Tab instance.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Tab::_MakeSwitchToTabCommand()
{
SwitchToTabArgs args{ _TabViewIndex };
ActionAndArgs focusTabAction{ ShortcutAction::SwitchToTab, args };
Command command;
command.Action(focusTabAction);
command.Name(Title());
command.Icon(_lastIconPath);
SwitchToTabCommand(command);
}
void Tab::_CloseTabsAfter()
{
CloseTabsAfterArgs args{ _TabViewIndex };
ActionAndArgs closeTabsAfter{ ShortcutAction::CloseTabsAfter, args };
_dispatch.DoAction(closeTabsAfter);
}
void Tab::_CloseOtherTabs()
{
CloseOtherTabsArgs args{ _TabViewIndex };
ActionAndArgs closeOtherTabs{ ShortcutAction::CloseOtherTabs, args };
_dispatch.DoAction(closeOtherTabs);
}
void Tab::UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs)
{
TabViewIndex(idx);
TabViewNumTabs(numTabs);
_EnableCloseMenuItems();
SwitchToTabCommand().Action().Args().as<SwitchToTabArgs>().TabIndex(idx);
}
void Tab::SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch)
{
_dispatch = dispatch;
}
DEFINE_EVENT(Tab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
DEFINE_EVENT(Tab, ColorSelected, _colorSelected, winrt::delegate<winrt::Windows::UI::Color>);
DEFINE_EVENT(Tab, ColorCleared, _colorCleared, winrt::delegate<>);
DEFINE_EVENT(TerminalTab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
DEFINE_EVENT(TerminalTab, ColorSelected, _colorSelected, winrt::delegate<winrt::Windows::UI::Color>);
DEFINE_EVENT(TerminalTab, ColorCleared, _colorCleared, winrt::delegate<>);
}

View File

@@ -4,7 +4,8 @@
#pragma once
#include "Pane.h"
#include "ColorPickupFlyout.h"
#include "Tab.g.h"
#include "TabBase.h"
#include "TerminalTab.g.h"
// fwdecl unittest classes
namespace TerminalAppLocalTests
@@ -14,21 +15,18 @@ namespace TerminalAppLocalTests
namespace winrt::TerminalApp::implementation
{
struct Tab : public TabT<Tab>
struct TerminalTab : TerminalTabT<TerminalTab, TabBase>
{
public:
Tab() = delete;
Tab(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
TerminalTab(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
// Called after construction to perform the necessary setup, which relies on weak_ptr
void Initialize(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
winrt::Microsoft::UI::Xaml::Controls::TabViewItem GetTabViewItem();
winrt::Microsoft::Terminal::TerminalControl::TermControl GetActiveTerminalControl() const;
std::optional<GUID> GetFocusedProfile() const noexcept;
bool IsFocused() const noexcept;
void SetFocused(const bool focused);
void Focus(winrt::Windows::UI::Xaml::FocusState focusState) override;
winrt::fire_and_forget Scroll(const int delta);
@@ -46,9 +44,9 @@ namespace winrt::TerminalApp::implementation
void NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::Direction& direction);
void UpdateSettings(const winrt::TerminalApp::TerminalSettings& settings, const GUID& profile);
winrt::hstring GetActiveTitle() const;
winrt::fire_and_forget UpdateTitle();
void Shutdown();
void Shutdown() override;
void ClosePane();
void SetTabText(winrt::hstring title);
@@ -68,28 +66,10 @@ namespace winrt::TerminalApp::implementation
int GetLeafPaneCount() const noexcept;
void UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs);
void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch);
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
DECLARE_EVENT(ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
DECLARE_EVENT(ColorSelected, _colorSelected, winrt::delegate<winrt::Windows::UI::Color>);
DECLARE_EVENT(ColorCleared, _colorCleared, winrt::delegate<>);
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Title, _PropertyChangedHandlers);
OBSERVABLE_GETSET_PROPERTY(winrt::Windows::UI::Xaml::Controls::IconSource, IconSource, _PropertyChangedHandlers, nullptr);
OBSERVABLE_GETSET_PROPERTY(winrt::Microsoft::Terminal::Settings::Model::Command, SwitchToTabCommand, _PropertyChangedHandlers, nullptr);
// The TabViewIndex is the index this Tab object resides in TerminalPage's _tabs vector.
// This is needed since Tab is going to be managing its own SwitchToTab command.
OBSERVABLE_GETSET_PROPERTY(uint32_t, TabViewIndex, _PropertyChangedHandlers, 0);
// The TabViewNumTabs is the number of Tab objects in TerminalPage's _tabs vector.
OBSERVABLE_GETSET_PROPERTY(uint32_t, TabViewNumTabs, _PropertyChangedHandlers, 0);
OBSERVABLE_GETSET_PROPERTY(winrt::Windows::UI::Xaml::UIElement, Content, _PropertyChangedHandlers, nullptr);
private:
std::shared_ptr<Pane> _rootPane{ nullptr };
std::shared_ptr<Pane> _activePane{ nullptr };
@@ -101,9 +81,7 @@ namespace winrt::TerminalApp::implementation
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeOtherTabsMenuItem{};
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeTabsAfterMenuItem{};
bool _focused{ false };
bool _receivedKeyDown{ false };
winrt::Microsoft::UI::Xaml::Controls::TabViewItem _tabViewItem{ nullptr };
winrt::hstring _runtimeTabText{};
bool _inRename{ false };
@@ -112,11 +90,8 @@ namespace winrt::TerminalApp::implementation
winrt::TerminalApp::ShortcutActionDispatch _dispatch;
void _MakeTabViewItem();
void _Focus();
void _CreateContextMenu();
winrt::Windows::UI::Xaml::Controls::MenuFlyoutSubItem _CreateCloseSubMenu();
void _EnableCloseMenuItems();
void _CreateContextMenu() override;
void _RefreshVisualState();
@@ -127,19 +102,14 @@ namespace winrt::TerminalApp::implementation
void _UpdateActivePane(std::shared_ptr<Pane> pane);
winrt::hstring _GetActiveTitle() const;
void _UpdateTabHeader();
winrt::fire_and_forget _UpdateTitle();
void _ConstructTabRenameBox(const winrt::hstring& tabText);
void _RecalculateAndApplyTabColor();
void _ApplyTabColor(const winrt::Windows::UI::Color& color);
void _ClearTabBackgroundColor();
void _MakeSwitchToTabCommand();
void _CloseTabsAfter();
void _CloseOtherTabs();
friend class ::TerminalAppLocalTests::TabTests;
};
}

View File

@@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "TabBase.idl";
namespace TerminalApp
{
[default_interface] runtimeclass TerminalTab : TabBase
{
}
}

View File

@@ -30,7 +30,8 @@
<ClInclude Include="../TitlebarControl.h" />
<ClInclude Include="../TabRowControl.h" />
<ClInclude Include="../App.h" />
<ClInclude Include="../Tab.h" />
<ClInclude Include="../TerminalTab.h" />
<ClInclude Include="../SettingsTab.h" />
</ItemGroup>
<!-- ========================= Cpp Files ======================== -->
<ItemGroup>

View File

@@ -587,21 +587,21 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// 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 chain = _renderEngine->GetSwapChain();
auto chainHandle = _renderEngine->GetSwapChainHandle();
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(Dispatcher());
if (auto control{ weakThis.get() })
{
_AttachDxgiSwapChainToXaml(chain.Get());
_AttachDxgiSwapChainToXaml(chainHandle);
}
}
void TermControl::_AttachDxgiSwapChainToXaml(IDXGISwapChain1* swapChain)
void TermControl::_AttachDxgiSwapChainToXaml(HANDLE swapChainHandle)
{
auto nativePanel = SwapChainPanel().as<ISwapChainPanelNative>();
nativePanel->SetSwapChain(swapChain);
auto nativePanel = SwapChainPanel().as<ISwapChainPanelNative2>();
nativePanel->SetSwapChainHandle(swapChainHandle);
}
bool TermControl::_InitializeTerminal()
@@ -705,7 +705,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
THROW_IF_FAILED(dxEngine->Enable());
_renderEngine = std::move(dxEngine);
_AttachDxgiSwapChainToXaml(_renderEngine->GetSwapChain().Get());
_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)

View File

@@ -108,7 +108,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void ToggleRetroEffect();
winrt::fire_and_forget RenderEngineSwapChainChanged();
void _AttachDxgiSwapChainToXaml(IDXGISwapChain1* swapChain);
void _AttachDxgiSwapChainToXaml(HANDLE swapChainHandle);
winrt::fire_and_forget _RendererEnteredErrorState();
void _RenderRetryButton_Click(IInspectable const& button, IInspectable const& args);

View File

@@ -109,62 +109,63 @@ 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, LOBYTE(HIWORD(message.lParam)), true))
{
// 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, LOBYTE(HIWORD(message.lParam)), 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, LOBYTE(HIWORD(message.lParam)), false))
{
// 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;
}
return 0;
}

View File

@@ -82,7 +82,7 @@ public:
winrt::event_token name(args const& handler) { return _##name##Handlers.add(handler); } \
void name(winrt::event_token const& token) { _##name##Handlers.remove(token); } \
\
private: \
protected: \
winrt::event<args> _##name##Handlers;
// This is a helper macro for both declaring the signature and body of an event
@@ -128,7 +128,7 @@ private:
// (like when the class is being initialized).
#define OBSERVABLE_GETSET_PROPERTY(type, name, event, ...) \
public: \
type name() { return _##name; }; \
type name() const noexcept { return _##name; }; \
void name(const type& value) \
{ \
if (_##name != value) \

View File

@@ -13,7 +13,7 @@
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
<Link>
<AdditionalDependencies>onecoreuap_apiset.lib;d3dcompiler.lib;dwmapi.lib;uxtheme.lib;shlwapi.lib;ntdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>onecoreuap_apiset.lib;d3dcompiler.lib;dwmapi.lib;uxtheme.lib;shlwapi.lib;ntdll.lib;dcomp.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>

View File

@@ -47,7 +47,6 @@ class AliasTests
TEST_METHOD_PROPERTY(L"Data:bUnicode", L"{FALSE, TRUE}")
TEST_METHOD_PROPERTY(L"Data:bSetFirst", L"{FALSE, TRUE}")
END_TEST_METHOD()
};
// Caller must free ppsz if not null.

View File

@@ -3,6 +3,8 @@
#include "precomp.h"
#include <thread>
#include "..\..\interactivity\onecore\SystemConfigurationProvider.hpp"
// some assumptions have been made on this value. only change it if you have a good reason to.
@@ -11,7 +13,6 @@
using WEX::Logging::Log;
using namespace WEX::Common;
using namespace WEX::TestExecution;
// This class is intended to test:
// FlushConsoleInputBuffer
@@ -20,7 +21,6 @@ using namespace WEX::TestExecution;
// WriteConsoleInput
// GetNumberOfConsoleInputEvents
// GetNumberOfConsoleMouseButtons
// ReadConsoleA
class InputTests
{
BEGIN_TEST_CLASS(InputTests)
@@ -54,44 +54,6 @@ class InputTests
BEGIN_TEST_METHOD(TestVtInputGeneration)
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
END_TEST_METHOD();
BEGIN_TEST_METHOD(TestCookedAliasProcessing)
TEST_METHOD_PROPERTY(L"TestTimeout", L"00:01:00")
END_TEST_METHOD()
BEGIN_TEST_METHOD(TestCookedTextEntry)
TEST_METHOD_PROPERTY(L"TestTimeout", L"00:01:00")
END_TEST_METHOD()
BEGIN_TEST_METHOD(TestCookedAlphaPermutations)
TEST_METHOD_PROPERTY(L"TestTimeout", L"00:01:00")
TEST_METHOD_PROPERTY(L"Data:inputcp", L"{437, 932}")
TEST_METHOD_PROPERTY(L"Data:outputcp", L"{437, 932}")
TEST_METHOD_PROPERTY(L"Data:inputmode", L"{487, 481}") // 487 is 0x1e7, 481 is 0x1e1 (ENABLE_LINE_INPUT on/off)
TEST_METHOD_PROPERTY(L"Data:outputmode", L"{7}")
TEST_METHOD_PROPERTY(L"Data:font", L"{Consolas, MS Gothic}")
END_TEST_METHOD()
BEGIN_TEST_METHOD(TestReadCharByChar)
TEST_METHOD_PROPERTY(L"Data:readmode", L"{cooked, raw, direct}")
//TEST_METHOD_PROPERTY(L"TestTimeout", L"00:01:00")
END_TEST_METHOD()
BEGIN_TEST_METHOD(TestReadLeadTrailString)
TEST_METHOD_PROPERTY(L"Data:readmode", L"{cooked, raw, direct}")
//TEST_METHOD_PROPERTY(L"TestTimeout", L"00:01:00")
END_TEST_METHOD()
BEGIN_TEST_METHOD(TestReadChangeCodepageInMiddle)
TEST_METHOD_PROPERTY(L"Data:readmode", L"{cooked, raw, direct}")
//TEST_METHOD_PROPERTY(L"TestTimeout", L"00:01:00")
END_TEST_METHOD()
BEGIN_TEST_METHOD(TestReadChangeCodepageBetweenBytes)
TEST_METHOD_PROPERTY(L"Data:readmode", L"{cooked, raw, direct}")
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
//TEST_METHOD_PROPERTY(L"TestTimeout", L"00:01:00")
END_TEST_METHOD()
};
void VerifyNumberOfInputRecords(const HANDLE hConsoleInput, _In_ DWORD nInputs)
@@ -754,836 +716,3 @@ void InputTests::RawReadUnpacksCoalescedInputRecords()
VERIFY_WIN32_BOOL_SUCCEEDED(GetNumberOfConsoleInputEvents(hIn, &eventCount));
VERIFY_ARE_EQUAL(eventCount, static_cast<DWORD>(0));
}
static std::vector<INPUT_RECORD> _stringToInputs(std::wstring_view wstr)
{
std::vector<INPUT_RECORD> result;
for (const auto& wch : wstr)
{
INPUT_RECORD ir = { 0 };
ir.EventType = KEY_EVENT;
ir.Event.KeyEvent.bKeyDown = TRUE;
ir.Event.KeyEvent.dwControlKeyState = 0;
ir.Event.KeyEvent.uChar.UnicodeChar = wch;
ir.Event.KeyEvent.wRepeatCount = 1;
ir.Event.KeyEvent.wVirtualKeyCode = VkKeyScanW(wch);
ir.Event.KeyEvent.wVirtualScanCode = gsl::narrow<WORD>(MapVirtualKeyW(ir.Event.KeyEvent.wVirtualKeyCode, MAPVK_VK_TO_VSC));
result.emplace_back(ir);
ir.Event.KeyEvent.bKeyDown = FALSE;
result.emplace_back(ir);
}
return result;
}
static HRESULT _sendStringToInput(HANDLE in, std::wstring_view wstr)
{
auto records = _stringToInputs(wstr);
DWORD written;
RETURN_IF_WIN32_BOOL_FALSE(WriteConsoleInputW(in, records.data(), gsl::narrow<DWORD>(records.size()), &written));
return S_OK;
}
// Routine Description:
// - Reads data from the standard input with a 5 second timeout
// Arguments:
// - in - The standard input handle
// - buf - The buffer to use. On in, this is the max size we'll read. On out, it's resized to fit.
// - async - Whether to read async, default to true. Reading async will put a 5 second timeout on the read.
// Return Value:
// - S_OK or an error from ReadConsole/threading timeout.
static HRESULT _readStringFromInput(HANDLE in, std::string& buf, bool async = true)
{
DWORD read = 0;
if (async)
{
auto tryRead = std::async(std::launch::async, [&] {
return _readStringFromInput(in, buf, false); // just re-enter ourselves on the other thread as sync.
});
if (std::future_status::ready != tryRead.wait_for(std::chrono::seconds{ 5 }))
{
// Shove something into the input to unstick it then fail.
_sendStringToInput(in, L"a\r\n");
RETURN_NTSTATUS(STATUS_TIMEOUT);
// If somehow this still isn't enough to unstick the thread, be sure to set
// the whole test timeout is 1 min in the parameters/metadata at the top.
}
else
{
return tryRead.get();
}
}
else
{
RETURN_IF_WIN32_BOOL_FALSE(ReadConsoleA(in, buf.data(), gsl::narrow<DWORD>(buf.size()), &read, nullptr));
// If we successfully read, then resize to fit the buffer.
buf.resize(read);
return S_OK;
}
}
static HRESULT _readStringFromInputDirect(HANDLE in, std::string& buf, bool async = true)
{
if (async)
{
auto tryRead = std::async(std::launch::async, [&] {
return _readStringFromInputDirect(in, buf, false); // just re-enter ourselves on the other thread as sync.
});
if (std::future_status::ready != tryRead.wait_for(std::chrono::seconds{ 5 }))
{
// Shove something into the input to unstick it then fail.
_sendStringToInput(in, L"a\r\n");
RETURN_NTSTATUS(STATUS_TIMEOUT);
// If somehow this still isn't enough to unstick the thread, be sure to set
// the whole test timeout is 1 min in the parameters/metadata at the top.
}
else
{
return tryRead.get();
}
}
else
{
const auto originalSize = buf.size();
buf.clear();
std::vector<INPUT_RECORD> ir;
DWORD read = 0;
do
{
ir.clear();
ir.resize(originalSize - buf.size());
RETURN_IF_WIN32_BOOL_FALSE(ReadConsoleInputA(in, ir.data(), gsl::narrow_cast<DWORD>(ir.size()), &read));
for (const auto& r : ir)
{
if (r.EventType == KEY_EVENT)
{
if (!r.Event.KeyEvent.bKeyDown)
{
buf.push_back(r.Event.KeyEvent.uChar.AsciiChar);
}
}
}
ir.clear();
} while (originalSize > buf.size());
return S_OK;
}
}
void InputTests::TestCookedAliasProcessing()
{
const auto in = GetStdInputHandle();
DWORD originalInMode = 0;
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleMode(in, &originalInMode));
DWORD originalCodepage = GetConsoleCP();
auto restoreInModeOnExit = wil::scope_exit([&] {
SetConsoleMode(in, originalInMode);
SetConsoleCP(originalCodepage);
});
const DWORD testInMode = ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleMode(in, testInMode));
auto modulePath = wil::GetModuleFileNameW<std::wstring>(nullptr);
std::filesystem::path path{ modulePath };
auto fileName = path.filename();
auto exeName = fileName.wstring();
VERIFY_WIN32_BOOL_SUCCEEDED(AddConsoleAliasW(L"foo", L"echo bar$Techo baz$Techo bam", exeName.data()));
std::wstring commandWritten = L"foo\r\n";
std::queue<std::string> commandExpected;
commandExpected.push("echo bar\r");
commandExpected.push("echo baz\r");
commandExpected.push("echo bam\r");
VERIFY_SUCCEEDED(_sendStringToInput(in, commandWritten));
std::string buf;
while (!commandExpected.empty())
{
buf.resize(500);
VERIFY_SUCCEEDED(_readStringFromInput(in, buf));
auto actual = buf;
auto expected = commandExpected.front();
commandExpected.pop();
VERIFY_ARE_EQUAL(expected, actual);
}
}
void InputTests::TestCookedTextEntry()
{
const auto in = GetStdInputHandle();
DWORD originalInMode = 0;
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleMode(in, &originalInMode));
DWORD originalCodepage = GetConsoleCP();
auto restoreInModeOnExit = wil::scope_exit([&] {
SetConsoleMode(in, originalInMode);
SetConsoleCP(originalCodepage);
});
const DWORD testInMode = ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleMode(in, testInMode));
std::wstring commandWritten = L"foo\r\n";
std::queue<std::string> commandExpected;
commandExpected.push("foo\r\n");
VERIFY_SUCCEEDED(_sendStringToInput(in, commandWritten));
std::string buf;
while (!commandExpected.empty())
{
buf.resize(500);
VERIFY_SUCCEEDED(_readStringFromInput(in, buf));
auto actual = buf;
auto expected = commandExpected.front();
commandExpected.pop();
VERIFY_ARE_EQUAL(expected, actual);
}
}
// Greek letters, lowercase...
const std::array<std::wstring, 4> wide = {
L"\u03b1", // alpha
L"\u03b2", // beta
// no gamma because it doesn't translate to 437
L"\u03b4", // delta
L"\u03b5" //epsilon
};
const std::array<std::string, 4> char437 = {
"\xe0",
"\xe1",
"\xeb",
"\xee"
};
const std::array<std::string, 4> char932 = {
"\x83\xbf",
"\x83\xc0",
"\x83\xc2",
"\x83\xc3"
};
const std::wstring widecrlf = L"\r\n";
const std::string crlf = "\r\n";
enum class ReadMode
{
Cooked, // ReadConsoleA with ENABLE_LINE_INPUT
Raw, // ReadConsoleA without ENABLE_LINE_INPUT
Direct // ReadConsoleInputA
};
static HRESULT _readString(HANDLE in, ReadMode mode, std::string& buf, bool async = true)
{
switch (mode)
{
case ReadMode::Cooked:
case ReadMode::Raw:
return _readStringFromInput(in, buf, async);
case ReadMode::Direct:
return _readStringFromInputDirect(in, buf, async);
default:
VERIFY_FAIL(L"Not supported");
return E_NOTIMPL;
}
}
void InputTests::TestCookedAlphaPermutations()
{
DWORD inputcp, outputcp, inputmode, outputmode;
String font;
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"inputcp", inputcp), L"Get input cp");
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"outputcp", outputcp), L"Get output cp");
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"inputmode", inputmode), L"Get input mode");
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"outputmode", outputmode), L"Get output mode");
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"font", font), L"Get font");
std::wstring wstrFont{ font };
if (wstrFont == L"MS Gothic")
{
// MS Gothic... but in full width characters and the katakana representation...
// MS GOSHIKKU romanized...
wstrFont = L"\xff2d\xff33\x0020\x30b4\x30b7\x30c3\x30af";
}
const auto in = GetStdInputHandle();
const auto out = GetStdOutputHandle();
Log::Comment(L"Backup original modes and codepages and font.");
DWORD originalInMode, originalOutMode, originalInputCP, originalOutputCP;
CONSOLE_FONT_INFOEX originalFont = { 0 };
originalFont.cbSize = sizeof(originalFont);
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleMode(in, &originalInMode));
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleMode(out, &originalOutMode));
originalInputCP = GetConsoleCP();
originalOutputCP = GetConsoleOutputCP();
VERIFY_WIN32_BOOL_SUCCEEDED(GetCurrentConsoleFontEx(out, FALSE, &originalFont));
auto restoreModesOnExit = wil::scope_exit([&] {
SetConsoleMode(in, originalInMode);
SetConsoleMode(out, originalOutMode);
SetConsoleCP(originalInputCP);
SetConsoleOutputCP(originalOutputCP);
SetCurrentConsoleFontEx(out, FALSE, &originalFont);
});
Log::Comment(L"Apply our modes and codepages and font.");
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleMode(in, inputmode));
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleMode(out, outputmode));
if (GetACP() != 932 && !Common::_isV2 && inputcp == 932)
{
Log::Comment(L"The v1 console cannot switch to Japanese unless the system ACP is 932");
Log::Comment(L"Set it in the regional control panel legacy settings and reboot first.");
VERIFY_FAIL(L"System state invalid for v1 test. Must be in Japanese (Japan) legacy locale.");
}
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleCP(inputcp));
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleOutputCP(outputcp));
auto ourFont = originalFont;
wmemcpy_s(ourFont.FaceName, ARRAYSIZE(ourFont.FaceName), wstrFont.data(), wstrFont.size());
VERIFY_WIN32_BOOL_SUCCEEDED(SetCurrentConsoleFontEx(out, FALSE, &ourFont));
const wchar_t alpha = wide[0][0];
const std::string alpha437 = char437[0];
const std::string alpha932 = char932[0];
std::string expected = inputcp == 932 ? alpha932 : alpha437;
std::wstring sendInput;
sendInput.append(&alpha, 1);
// If we're in line input, we have to send a newline and we'll get one back.
if (WI_IsFlagSet(inputmode, ENABLE_LINE_INPUT))
{
expected.append(crlf);
sendInput.append(widecrlf);
}
Log::Comment(L"send the string");
VERIFY_SUCCEEDED(_sendStringToInput(in, sendInput));
Log::Comment(L"receive the string");
std::string recvInput;
recvInput.resize(500); // excessively big
VERIFY_SUCCEEDED(_readStringFromInput(in, recvInput));
// corruption magic
// In MS Gothic, alpha is full width (2 columns)
// In Consolas, alpha is half width (1 column)
// Alpha itself is an ambiguous character, meaning the console finds the width
// by asking the font.
// Unfortunately, there's some code mixed up in the cooked read for a long time where
// the width is used as a predictor of how many bytes it will consume.
// In this specific combination of using a font where the ambiguous alpha is half width,
// the output code page doesn't support double bytes, and the input code page does...
// The result is stomped with a null as the conversion fails thinking it doesn't have enough space.
// Also, we're not maintaining this font corruption going forward. So test it for v1 only.
if (!Common::_isV2 && wstrFont == L"Consolas" && inputcp == 932 && outputcp == 437)
{
VERIFY_IS_GREATER_THAN_OR_EQUAL(recvInput.size(), 1);
VERIFY_ARE_EQUAL('\x00', recvInput[0]);
if (WI_IsFlagSet(inputmode, ENABLE_LINE_INPUT))
{
VERIFY_IS_GREATER_THAN_OR_EQUAL(recvInput.size(), 3);
VERIFY_ARE_EQUAL('\r', recvInput[1]);
VERIFY_ARE_EQUAL('\n', recvInput[2]);
}
}
// end corruption magic
else
{
VERIFY_ARE_EQUAL(expected, recvInput);
}
}
void _unifiedReadTest(std::function<void(HANDLE, ReadMode)> fn)
{
String readmode;
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"readmode", readmode), L"Get read mode");
ReadMode rm = ReadMode::Raw;
if (readmode == L"cooked")
{
rm = ReadMode::Cooked;
}
else if (readmode == L"raw")
{
rm = ReadMode::Raw;
}
else if (readmode == L"direct")
{
rm = ReadMode::Direct;
}
else
{
VERIFY_FAIL(L"Read mode not implemented on test.");
}
const auto in = GetStdInputHandle();
DWORD originalInMode = 0;
VERIFY_WIN32_BOOL_SUCCEEDED(GetConsoleMode(in, &originalInMode));
DWORD originalCodepage = GetConsoleCP();
auto restoreInModeOnExit = wil::scope_exit([&] {
SetConsoleMode(in, originalInMode);
SetConsoleCP(originalCodepage);
});
const DWORD testInMode = rm == ReadMode::Raw ? 0 : ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleMode(in, testInMode));
Log::Comment(L"Set the codepage to Japanese");
if (GetACP() != 932 && !Common::_isV2)
{
Log::Comment(L"The v1 console cannot switch to Japanese unless the system ACP is 932");
Log::Comment(L"Set it in the regional control panel legacy settings and reboot first.");
VERIFY_FAIL(L"System state invalid for v1 test. Must be in Japanese (Japan) legacy locale.");
}
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleCP(932));
Log::Comment(L"Flush out the read queue.");
VERIFY_WIN32_BOOL_SUCCEEDED(FlushConsoleInputBuffer(in));
Log::Comment(L"Write something into the read queue.");
std::wstring sendInput;
sendInput.append(wide[0]);
sendInput.append(wide[1]);
sendInput.append(wide[2]);
sendInput.append(wide[3]);
sendInput.append(L"\r\n"); // send a newline to finish the line since we're in ENABLE_LINE_INPUT mode
Log::Comment(L"send the string");
VERIFY_SUCCEEDED(_sendStringToInput(in, sendInput));
fn(in, rm);
}
std::wstring _stringToHexString(const std::string& str)
{
std::wstring ret;
for (auto& ch : str)
{
ret.append(fmt::format(L"{:#04x} ", (byte)ch));
}
return ret;
}
void _readVersusExpected(const HANDLE in, const ReadMode mode, const std::string& expected, size_t readSize)
{
// Print expected up here so if it horks, we can at least know what we asked for to debug/fix the test.
Log::Comment(fmt::format(L"Expected: {}", _stringToHexString(expected)).c_str());
std::string recvInput;
recvInput.resize(readSize);
VERIFY_SUCCEEDED(_readString(in, mode, recvInput));
Log::Comment(fmt::format(L"Actual : {}", _stringToHexString(recvInput)).c_str());
VERIFY_ARE_EQUAL(expected, recvInput);
}
// TODO tests:
// - ensure leftover bytes are lost when read off a different handle?!
void InputTests::TestReadCharByChar()
{
_unifiedReadTest([isv2 = Common::_isV2](HANDLE in, ReadMode mode) -> void {
Log::Comment(L"Read byte by byte, should leave trailing each time.");
if (!isv2)
{
std::string expectedInput;
expectedInput = char932[0][0];
if (mode != ReadMode::Direct)
{
// this is an artifact of resizing our string to the `lpNumberOfCharsRead`
// which can be longer than the buffer we gave. `ReadConsoleA` appears to
// do this either to signal there are more or as a mistake that was never
// matched up on API review.
expectedInput.append(1, '\0');
}
_readVersusExpected(in, mode, expectedInput, 1);
// TODO: CHv1 completely loses the trailing byte.
expectedInput[0] = char932[1][0];
_readVersusExpected(in, mode, expectedInput, 1);
// TODO: CHv1 completely loses the trailing byte.
expectedInput[0] = char932[2][0];
_readVersusExpected(in, mode, expectedInput, 1);
// TODO: CHv1 completely loses the trailing byte.
expectedInput[0] = char932[3][0];
_readVersusExpected(in, mode, expectedInput, 1);
// TODO: CHv1 completely loses the trailing byte.
expectedInput = crlf[0];
_readVersusExpected(in, mode, expectedInput, 1);
if (mode != ReadMode::Raw) // Raw mode will not return the \n.
{
expectedInput = crlf[1];
_readVersusExpected(in, mode, expectedInput, 1);
}
}
else
{
Log::Comment(L"Should see lead/trail alternating and then the crlf");
std::string expectedInput;
expectedInput = char932[0][0];
_readVersusExpected(in, mode, expectedInput, expectedInput.size());
expectedInput = char932[0][1];
_readVersusExpected(in, mode, expectedInput, expectedInput.size());
expectedInput = char932[1][0];
_readVersusExpected(in, mode, expectedInput, expectedInput.size());
expectedInput = char932[1][1];
_readVersusExpected(in, mode, expectedInput, expectedInput.size());
expectedInput = char932[2][0];
_readVersusExpected(in, mode, expectedInput, expectedInput.size());
expectedInput = char932[2][1];
_readVersusExpected(in, mode, expectedInput, expectedInput.size());
expectedInput = char932[3][0];
_readVersusExpected(in, mode, expectedInput, expectedInput.size());
expectedInput = char932[3][1];
_readVersusExpected(in, mode, expectedInput, expectedInput.size());
expectedInput = crlf[0];
_readVersusExpected(in, mode, expectedInput, expectedInput.size());
if (mode != ReadMode::Raw) // Raw mode doesn't return \n.
{
expectedInput = crlf[1];
_readVersusExpected(in, mode, expectedInput, expectedInput.size());
}
}
});
}
void InputTests::TestReadLeadTrailString()
{
_unifiedReadTest([isv2 = Common::_isV2](HANDLE in, ReadMode mode) -> void {
Log::Comment(L"Read byte by byte, should attach trailing to the remaining string.");
if (!isv2)
{
std::string expectedInput;
expectedInput = char932[0][0];
if (mode != ReadMode::Direct)
{
// this is an artifact of resizing our string to the `lpNumberOfCharsRead`
// which can be longer than the buffer we gave. `ReadConsoleA` appears to
// do this either to signal there are more or as a mistake that was never
// matched up on API review.
expectedInput.append(1, '\0');
}
_readVersusExpected(in, mode, expectedInput, 1);
Log::Comment(L"Read everything else");
// TODO: CHv1 completely loses the trailing byte.
expectedInput.clear();
if (mode != ReadMode::Raw)
{
// Direct mode can successfully return the trailing byte...
// but in v1... only when the read length is > 1 record total.
// Since this is the "string remaining" test... that's >1 record.
// (as opposed to the char-by-char test where Direct loses it just like
// Cooked and Raw do.)
if (mode == ReadMode::Direct)
{
expectedInput.append(1, char932[0][1]);
}
expectedInput.append(char932[1]);
expectedInput.append(char932[2]);
expectedInput.append(char932[3]);
expectedInput.append(1, crlf[0]);
expectedInput.append(1, crlf[1]);
}
else
{
// Raw mode messes up completely here and just returns the UTF-16 characters.
// oh and a null at the end for fun. and it loses the \n.
expectedInput.append(1, LOBYTE(wide[1][0]));
expectedInput.append(1, HIBYTE(wide[1][0]));
expectedInput.append(1, LOBYTE(wide[2][0]));
expectedInput.append(1, HIBYTE(wide[2][0]));
expectedInput.append(1, LOBYTE(wide[3][0]));
expectedInput.append(1, HIBYTE(wide[3][0]));
expectedInput.append(1, crlf[0]);
expectedInput.append(1, '\0');
}
// The test helper is authored such that direct mode will keep retrying
// to read until it gets every record requested because there's a high
// potential for other events (focus, mouse) to drop into the queue
// for random reasons.
// As such, we can read to excess on cooked/raw, but we have to read
// to the exact expected length for direct.
if (mode != ReadMode::Direct)
{
_readVersusExpected(in, mode, expectedInput, 100);
}
else
{
// We can't read too far for direct because we have to loop
// to get all the right key records and we'll end up in an infinite wait.
_readVersusExpected(in, mode, expectedInput, 9);
}
}
else
{
Log::Comment(L"Should see just lead byte.");
std::string expectedInput;
expectedInput = char932[0][0];
_readVersusExpected(in, mode, expectedInput, expectedInput.size());
Log::Comment(L"Read everything else. Trailing byte stitched to front of results.");
expectedInput = char932[0][1];
expectedInput.append(char932[1]);
expectedInput.append(char932[2]);
expectedInput.append(char932[3]);
expectedInput.append(1, crlf[0]);
if (mode != ReadMode::Raw) // Raw mode doesn't return \n.
{
expectedInput.append(1, crlf[1]);
}
_readVersusExpected(in, mode, expectedInput, expectedInput.size());
}
});
}
void InputTests::TestReadChangeCodepageInMiddle()
{
_unifiedReadTest([isv2 = Common::_isV2](HANDLE in, ReadMode mode) -> void {
if (!isv2)
{
Log::Comment(L"Read only part of it including leaving behind a trailing byte.");
std::string expectedInput;
expectedInput = char932[0];
// The following two only happen if you switch part way through...
expectedInput.append(char932[1].data(), 1);
// this is an artifact of resizing our string to the `lpNumberOfCharsRead`
// which can be longer than the buffer we gave. `ReadConsoleA` appears to
// do this either to signal there are more or as a mistake that was never
// matched up on API review.
if (mode != ReadMode::Direct)
{
expectedInput.append(1, '\0');
}
if (mode == ReadMode::Raw)
{
// throw on two null bytes for funsies.
expectedInput.append(1, '\0');
expectedInput.append(1, '\0');
}
_readVersusExpected(in, mode, expectedInput, 3); // two bytes of first alpha and then a lead byte of the second one.
Log::Comment(L"Set the codepage to English");
Log::Comment(L"Changing codepage should discard all partial bytes!");
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleCP(437));
Log::Comment(L"Read the rest of it and validate that it was re-encoded as English");
expectedInput.clear();
if (mode == ReadMode::Direct)
{
expectedInput.append(char437[2]);
}
expectedInput.append(char437[3]);
if (mode != ReadMode::Raw)
{
expectedInput.append(crlf);
}
else
{
// why do we get a ?... I mean why are we getting any of this weirdness.
expectedInput.append(1, '?');
}
if (mode != ReadMode::Direct)
{
_readVersusExpected(in, mode, expectedInput, 490);
}
else
{
// We can't read too far for direct because we have to loop
// to get all the right key records and we'll end up in an infinite wait.
_readVersusExpected(in, mode, expectedInput, 4);
}
}
else
{
Log::Comment(L"Read the first whole character and a lead byte of the second (3 bytes)");
std::string expectedInput;
expectedInput = char932[0];
expectedInput.append(1, char932[1][0]);
_readVersusExpected(in, mode, expectedInput, expectedInput.size());
Log::Comment(L"Set the codepage to English");
Log::Comment(L"Changing codepage should discard all partial bytes!");
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleCP(437));
Log::Comment(L"Read everything else. Trailing byte should be gone and not stitched to front of results.");
expectedInput.clear();
expectedInput.append(char437[2]);
expectedInput.append(char437[3]);
expectedInput.append(1, crlf[0]);
if (mode != ReadMode::Raw) // Raw mode doesn't return \n.
{
expectedInput.append(1, crlf[1]);
}
_readVersusExpected(in, mode, expectedInput, expectedInput.size());
}
});
}
void InputTests::TestReadChangeCodepageBetweenBytes()
{
_unifiedReadTest([isv2 = Common::_isV2](HANDLE in, ReadMode mode) -> void {
if (!isv2)
{
Log::Comment(L"Read only part of it including leaving behind a trailing byte.");
std::string expectedInput;
expectedInput = char932[0];
if (mode == ReadMode::Raw)
{
// throw on two null bytes for funsies.
expectedInput.append(1, '\0');
expectedInput.append(1, '\0');
}
_readVersusExpected(in, mode, expectedInput, 2); // two bytes of first alpha
Log::Comment(L"Set the codepage to English");
Log::Comment(L"Changing codepage should discard all partial bytes!");
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleCP(437));
Log::Comment(L"Read the rest of it and validate that it was re-encoded as English");
expectedInput.clear();
// TODO: I believe v2 shouldn't lose this character by switching codepages.
if (mode == ReadMode::Direct)
{
expectedInput.append(char437[1]);
}
expectedInput.append(char437[2]);
if (mode == ReadMode::Raw)
{
// an infix question mark? in the raw read? for no sensible reason?
// YEP.
expectedInput.append(1, '?');
}
expectedInput.append(char437[3]);
if (mode != ReadMode::Raw)
{
expectedInput.append(crlf);
}
if (mode != ReadMode::Direct)
{
_readVersusExpected(in, mode, expectedInput, 490);
}
else
{
// We can't read too far for direct because we have to loop
// to get all the right key records and we'll end up in an infinite wait.
_readVersusExpected(in, mode, expectedInput, 5);
}
}
else
{
Log::Comment(L"Read the first two whole characters (4 bytes)");
std::string expectedInput;
expectedInput = char932[0];
expectedInput.append(char932[1]);
_readVersusExpected(in, mode, expectedInput, expectedInput.size());
Log::Comment(L"Set the codepage to English");
Log::Comment(L"Changing codepage should discard all partial bytes! But there shouldn't be any partials!");
VERIFY_WIN32_BOOL_SUCCEEDED(SetConsoleCP(437));
Log::Comment(L"Read everything else.");
expectedInput.clear();
expectedInput.append(char437[2]);
expectedInput.append(char437[3]);
expectedInput.append(1, crlf[0]);
if (mode != ReadMode::Raw) // Raw mode doesn't return \n.
{
expectedInput.append(1, crlf[1]);
}
_readVersusExpected(in, mode, expectedInput, expectedInput.size());
}
});
}

View File

@@ -185,7 +185,7 @@ bool Common::TestBufferSetup()
// to the default output buffer at the same time.
_hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE /*dwShareMode*/, // needed to read cooked
0 /*dwShareMode*/,
nullptr /*lpSecurityAttributes*/,
CONSOLE_TEXTMODE_BUFFER,
nullptr /*lpReserved*/);

View File

@@ -21,7 +21,6 @@
#include <algorithm>
#include <atomic>
#include <deque>
#include <future>
#include <list>
#include <memory>
#include <map>

View File

@@ -84,6 +84,7 @@ DxEngine::DxEngine() :
_glyphCell{},
_boxDrawingEffect{},
_haveDeviceResources{ false },
_swapChainHandle{ INVALID_HANDLE_VALUE },
_swapChainDesc{ 0 },
_swapChainFrameLatencyWaitableObject{ INVALID_HANDLE_VALUE },
_recreateDeviceRequested{ false },
@@ -488,6 +489,13 @@ try
}
case SwapChainMode::ForComposition:
{
if (!_swapChainHandle)
{
RETURN_IF_FAILED(DCompositionCreateSurfaceHandle(GENERIC_ALL, nullptr, &_swapChainHandle));
}
RETURN_IF_FAILED(_dxgiFactory2.As(&_dxgiFactoryMedia));
// Use the given target size for compositions.
_swapChainDesc.Width = _displaySizePixels.width<UINT>();
_swapChainDesc.Height = _displaySizePixels.height<UINT>();
@@ -497,10 +505,11 @@ try
// It's 100% required to use scaling mode stretch for composition. There is no other choice.
_swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
RETURN_IF_FAILED(_dxgiFactory2->CreateSwapChainForComposition(_d3dDevice.Get(),
&_swapChainDesc,
nullptr,
&_dxgiSwapChain));
RETURN_IF_FAILED(_dxgiFactoryMedia->CreateSwapChainForCompositionSurfaceHandle(_d3dDevice.Get(),
_swapChainHandle.get(),
&_swapChainDesc,
nullptr,
&_dxgiSwapChain));
break;
}
default:
@@ -842,14 +851,14 @@ try
}
CATCH_LOG()
Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
HANDLE DxEngine::GetSwapChainHandle()
{
if (_dxgiSwapChain.Get() == nullptr)
if (!_swapChainHandle)
{
THROW_IF_FAILED(_CreateDeviceResources(true));
}
return _dxgiSwapChain;
return _swapChainHandle.get();
}
void DxEngine::_InvalidateRectangle(const til::rectangle& rc)

View File

@@ -63,7 +63,7 @@ namespace Microsoft::Console::Render
void SetSoftwareRendering(bool enable) noexcept;
::Microsoft::WRL::ComPtr<IDXGISwapChain1> GetSwapChain();
HANDLE GetSwapChainHandle();
// IRenderEngine Members
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
@@ -119,6 +119,8 @@ namespace Microsoft::Console::Render
void SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept;
void SetDefaultTextBackgroundOpacity(const float opacity) noexcept;
wil::unique_handle _swapChainHandle;
void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept;
protected:
@@ -212,6 +214,7 @@ namespace Microsoft::Console::Render
::Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> _d2dBrushBackground;
::Microsoft::WRL::ComPtr<IDXGIFactory2> _dxgiFactory2;
::Microsoft::WRL::ComPtr<IDXGIFactoryMedia> _dxgiFactoryMedia;
::Microsoft::WRL::ComPtr<IDXGIDevice> _dxgiDevice;
::Microsoft::WRL::ComPtr<IDXGISurface> _dxgiSurface;

View File

@@ -21,6 +21,8 @@
#include <typeinfo>
#include <stdexcept>
#include <dcomp.h>
#include <dxgi.h>
#include <dxgi1_2.h>
#include <dxgi1_3.h>

View File

@@ -0,0 +1,281 @@
// 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()
{
co_await winrt::resume_background();
auto host0 = _manager.CreateHost();
co_await winrt::resume_foreground(_swp0.Dispatcher());
host0.CreateSwapChain(_swp0);
_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.Host().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,38 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "IslandWindow.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,50 @@
#include "pch.h"
#include "HostAndProcess.h"
#include "HostAndProcess.g.cpp"
#include "../../types/inc/utils.hpp"
using namespace winrt;
using namespace winrt::Windows::Foundation;
using namespace ::Microsoft::Console;
namespace winrt::ScratchIsland::implementation
{
HostAndProcess::HostAndProcess(ScratchWinRTServer::HostClass host,
wil::unique_process_information pi /*, wil::unique_handle hSwapchain*/) :
_host{ host },
_pi{ std::move(pi) } /*,
_hOurSwapchain {std::move(hSwapchain)}*/
{
}
ScratchWinRTServer::HostClass HostAndProcess::Host() const
{
return _host;
}
void HostAndProcess::CreateSwapChain(winrt::Windows::UI::Xaml::Controls::SwapChainPanel panel)
{
// wil::unique_handle _swapChainHandle;
DCompositionCreateSurfaceHandle(GENERIC_ALL, nullptr, &_hOurSwapchain);
// wil::unique_handle otherProcess = OpenProcess(contentProcessPid);
// HANDLE otherGuysHandleId = INVALID_HANDLE_VALUE;
BOOL ret = DuplicateHandle(GetCurrentProcess(),
_hOurSwapchain.get(),
_pi.hProcess,
&_hTheirSwapchain,
0,
FALSE,
DUPLICATE_SAME_ACCESS);
// {
auto panelNative = panel.as<ISwapChainPanelNative2>();
winrt::check_hresult(panelNative->SetSwapChainHandle(_hOurSwapchain.get()));
// }
_host.ThisIsInsane((uint64_t)_hTheirSwapchain.get());
ret;
}
}

View File

@@ -0,0 +1,29 @@
#pragma once
#include <winrt/ScratchWinRTServer.h>
#include "HostAndProcess.g.h"
namespace winrt::ScratchIsland::implementation
{
struct HostAndProcess : public HostAndProcessT<HostAndProcess>
{
HostAndProcess(ScratchWinRTServer::HostClass host, wil::unique_process_information pi);
ScratchWinRTServer::HostClass Host() const;
void CreateSwapChain(Windows::UI::Xaml::Controls::SwapChainPanel panel);
private:
ScratchWinRTServer::HostClass _host{ nullptr };
wil::unique_process_information _pi;
wil::unique_handle _hOurSwapchain{ INVALID_HANDLE_VALUE };
wil::unique_handle _hTheirSwapchain{ INVALID_HANDLE_VALUE };
};
}
namespace winrt::ScratchIsland::factory_implementation
{
// struct HostAndProcess : HostAndProcessT<HostAndProcess, implementation::HostAndProcess>
// {
// };
}

View File

@@ -0,0 +1,9 @@
namespace ScratchIsland
{
[default_interface] runtimeclass HostAndProcess {
ScratchWinRTServer.HostClass Host { get; };
void CreateSwapChain(Windows.UI.Xaml.Controls.SwapChainPanel panel);
};
}

View File

@@ -0,0 +1,81 @@
#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<winrt::ScratchIsland::HostAndProcess>();
}
Collections::IObservableVector<winrt::ScratchIsland::HostAndProcess> HostManager::Hosts()
{
return _hosts;
}
static wil::unique_process_information _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
);
THROW_IF_WIN32_BOOL_FALSE(succeeded);
// 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(2500);
// 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)
return std::move(piOne);
}
winrt::ScratchIsland::HostAndProcess HostManager::CreateHost()
{
// 1. Generate a GUID.
winrt::guid g{ Utils::CreateGuid() };
// 2. Spawn a Server.exe, with the guid on the commandline
auto piContent{ std::move(_createHostClassProcess(g)) };
auto host = create_instance<winrt::ScratchWinRTServer::HostClass>(g, CLSCTX_LOCAL_SERVER);
THROW_IF_NULL_ALLOC(host);
auto hostAndProcess = winrt::make_self<HostAndProcess>(host, std::move(piContent));
_hosts.Append(*hostAndProcess);
return *hostAndProcess;
}
}

View File

@@ -0,0 +1,66 @@
#pragma once
#include "HostManager.g.h"
#include "HostAndProcess.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<winrt::ScratchIsland::HostAndProcess> Hosts();
winrt::ScratchIsland::HostAndProcess CreateHost();
private:
Windows::Foundation::Collections::IObservableVector<winrt::ScratchIsland::HostAndProcess> _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 @@
import "HostAndProcess.idl";
namespace ScratchIsland
{
[default_interface] runtimeclass HostManager // : IScratchInterface
{
HostManager();
Windows.Foundation.Collections.IObservableVector<HostAndProcess> Hosts { get; };
HostAndProcess 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,175 @@
<?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" />
<ClInclude Include="HostAndProcess.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" />
<ClCompile Include="HostAndProcess.cpp" />
</ItemGroup>
<ItemGroup>
<Midl Include="HostManager.idl" />
<Midl Include="HostAndProcess.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,92 @@
/*++
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 <dcomp.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>
#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,261 @@
#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();
}
winrt::guid HostClass::Id()
{
return _id;
}
void HostClass::Attach(Windows::UI::Xaml::Controls::SwapChainPanel panel)
{
printf("Hey dummy, we're not using Attach() anymore\n");
}
void HostClass::BeginRendering()
{
_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)
{
// NOPE DONT DO THIS
// auto nativePanel = _panel.as<ISwapChainPanelNative2>();
// nativePanel->SetSwapChainHandle(swapChainHandle);
}
void HostClass::ThisIsInsane(uint64_t swapchainHandle)
{
HANDLE foo = (HANDLE)swapchainHandle;
_hSwapchain.reset(foo);
// _hSwapchain.put((HANDLE)swapchainHandle);
// _hSwapchain = (HANDLE)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>();
dxEngine->_swapChainHandle.swap(_hSwapchain);
_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
_settings.DefaultBackground(til::color{ 255, 0, 255, 255 }); //rgba
_settings.DefaultForeground(til::color{ 0, 0, 0, 255 }); //rgba
_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);
_terminal->Write(L"Hello world from another process");
return true;
}
}

View File

@@ -0,0 +1,52 @@
#pragma once
#include "HostClass.g.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>
{
HostClass(const winrt::guid& g);
~HostClass();
void DoTheThing();
void Attach(Windows::UI::Xaml::Controls::SwapChainPanel panel);
void BeginRendering();
void RenderEngineSwapChainChanged();
void ThisIsInsane(uint64_t swapchainHandle);
private:
int _DoCount{ 0 };
winrt::guid _id;
wil::unique_handle _hSwapchain{ INVALID_HANDLE_VALUE };
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,15 @@
namespace ScratchWinRTServer
{
[default_interface] runtimeclass HostClass {
HostClass(Guid g);
Guid Id { get; };
void Attach(Windows.UI.Xaml.Controls.SwapChainPanel panel);
void BeginRendering();
void ThisIsInsane(UInt64 swapchainHandle);
};
}

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/
========================================================================