Compare commits

..

51 Commits

Author SHA1 Message Date
Dustin L. Howett
873a156e97 Migrate spelling-0.0.21 changes from main 2020-11-05 15:33:36 -06:00
Dustin L. Howett
ad13eeee50 Migrate spelling-0.0.19 changes from main 2020-11-05 15:33:36 -06:00
Mike Griese
87118bf061 Add my readme, because it's more useful 2020-11-05 15:33:36 -06:00
Mike Griese
cd818334a5 Tons of cleanup so Brian can maybe help me 2020-11-05 14:48:51 -06:00
Mike Griese
05895a64a2 This raises MORE questions! 2020-11-05 13:03:52 -06:00
Mike Griese
1fbf15836a Ho boy, there were a lot of issues merging code that was 2 months old 2020-11-05 11:33:22 -06:00
Mike Griese
d10f694778 Merge branch 'main' into dev/migrie/oop-scratch-3 2020-11-05 10:31:44 -06:00
Mike Griese
6639df9edc Allow the user to use the tab switcher with in-order tab switching (#8076)
## Summary of the Pull Request

Changes the way the `useTabSwitcher` setting works. It now accepts either a boolean or a string:
* `true`, `"mru"`: Use the tab switcher with MRU tab switching
* `"inOrder"`: Use the tab switcher, with in-order tab switching
* `false`, `"disabled"`: Don't use the tab switcher. Tabs will switch in-order.

This is following the discussion chronicled in #8025, as well as the follow-up investigation in that thread.

## References

* #7952 introduced MRU tab switching

## PR Checklist
* [x] Closes #8025 - there's also discussion of using a parameter in an action to override this setting, but that should get punted to a follow-up task
* [x] I work here
* [x] Tests added/passed - YOU BET THEY WERE
* [ ] Requires documentation to be updated

## Validation Steps Performed

I've been switching tabs all day and all night, with different settings values, and hot-reloading the setting.

I also _ran the test_ I added.
2020-11-05 14:28:16 +00:00
Alan Ninan Thomas
930e24c6b3 Configure the options available in the issue list (#8114)
Closes #7953
2020-11-04 17:18:24 -08:00
Kiminori Kaburagi
341bb4f91e Enable moving back to the root level in the command palette (#8051)
This commit adds functionality so that users can move back from sub menu
whenever they want. As a result, users no longer have to close command
palette and open it again to get all commands again.

Closes #7910
2020-11-05 00:19:52 +00:00
Carlos Zamora
c0c23291b8 Update active profiles when loading defaults (#8156)
## Summary of the Pull Request
When we get a serialization error, we "catch" it in `AppLogic` and only
`LoadDefaults()`. Since `LoadDefaults()` doesn't perform a full
validation of `CascadiaSettings`, we need to manually update our list of
active profiles (similar to how we manually resolve the default
profile).

## Validation Steps Performed
Repro steps fixed:
1. add deserialization error to settings.json (i.e. "fontWeight": "wumbo")
2. launch WT
3. verify that dropdown is populated with active profiles

Closes #8146
2020-11-04 23:57:15 +00:00
Mike Griese
d5d2b7727f Warn the user if the keyboard service is disabled (#8095)
## Summary of the Pull Request

![kb-service-disabled](https://user-images.githubusercontent.com/18356694/97578533-eb792d80-19be-11eb-9b13-b771327a72a0.png)

With this PR, the Terminal will check to make sure the "Touch, Keyboard and Handwriting Panel Service" is enabled at startup. If it isn't, then the Terminal won't be able to receive keyboard input (see #4448 and the 20 linked issues to that one).

## References

* See #4448 for more details

## PR Checklist
* [x] Closes #7886 
* [ ] Should this make #4448 not-open as well?
* [x] I work here
* [n/a] Tests added/passed
* [x] Docs: https://github.com/MicrosoftDocs/terminal/pull/168

## Validation Steps Performed

I manually set the service to "Disabled", restarted the machine, verified the dialog opens (and that I'm unable to type in the Terminal), then re-set the service to automatic and rebooted, and the dialog doesn't appear.
2020-11-04 21:44:53 +00:00
Mike Griese
c173f20244 Gank the linter harder (#8162)
Let's not just disable the `on` rules for the linter, let's just remove
it entirely. The way it's set up now, you'll get an email every time you
push to a PR, because GitHub fails to find any time to run the linter. 

* [x] I work here
* [x] Follow-up to #8152
2020-11-04 10:41:50 -08: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
31416ff20a Merge branch 'main' into dev/migrie/oop-scratch-3 2020-10-22 22:05:26 -05:00
Mike Griese
12e50ea911 Merge remote-tracking branch 'origin/master' into dev/migrie/oop-scratch-3
# Conflicts:
#	src/cascadia/WindowsTerminal/main.cpp
#	src/renderer/dx/DxRenderer.hpp
2020-10-21 11:17:06 -05:00
Mike Griese
24561399a2 This is code for how _not_ to impersonate another user 2020-08-06 08:24:01 -05:00
Mike Griese
b1eefb865c some fallout from the merge with master 2020-08-05 15:09:37 -05:00
Mike Griese
f742afdb49 Merge remote-tracking branch 'origin/master' into dev/migrie/oop-scratch-3 2020-08-05 11:03:36 -05:00
Mike Griese
f3cffafa52 change this branch to be better suited for testing with cross-elevation oop winrt 2020-08-05 11:03:22 -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
76 changed files with 2870 additions and 1304 deletions

12
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
blank_issues_enabled: true
contact_links:
- name: Microsoft Security Response Center 🔐
url: https://msrc.microsoft.com/create-report
about: Please report security vulnerabilities here.
- name: Windows Terminal Documentation issue 📄
url: https://github.com/MicrosoftDocs/terminal/issues/new
about: Report issues with the documentation for the Windows Terminal (in docs.microsoft.com/windows/terminal)
- name: Console Documentation issue 📄
url: https://github.com/MicrosoftDocs/console-docs/issues/new
about: Report issues with the documentation for the Console (in docs.microsoft.com/windows/console)

View File

@@ -1,57 +0,0 @@
---
###########################
###########################
## Linter GitHub Actions ##
###########################
###########################
name: Lint Code Base
#
# Documentation:
# https://help.github.com/en/articles/workflow-syntax-for-github-actions
#
###################################################
# The linter is noisy; we used to run it on push. #
###################################################
#
#on:
# pull_request:
# branches: [main]
###############
# Set the Job #
###############
jobs:
build:
# Name the Job
name: Lint Code Base
# Set the agent to run on
runs-on: ubuntu-latest
##################
# Load all steps #
##################
steps:
##########################
# Checkout the code base #
##########################
- name: Checkout Code
uses: actions/checkout@v2
with:
# Full git history is needed to get a proper list of changed files within `super-linter`
fetch-depth: 0
################################
# Run Linter against code base #
################################
- name: Lint Code Base
uses: github/super-linter@v3
env:
VALIDATE_ALL_CODEBASE: false
DEFAULT_BRANCH: main
MARKDOWN_CONFIG_FILE: .markdown-lint.yml
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VALIDATE_EDITORCONFIG: false
# The json linter doesn't like JSONC, which we use all over. So just disable it.
VALIDATE_JSON: false

View File

@@ -313,6 +313,24 @@ 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}") = "ScratchWinRTClient", "src\tools\ScratchWinRTClient\ScratchWinRTClient.vcxproj", "{06382349-D62A-4C7D-A7D3-9CA817EAE092}"
ProjectSection(ProjectDependencies) = postProject
{D46D9547-F085-4645-B8F7-E8CD21559AB4} = {D46D9547-F085-4645-B8F7-E8CD21559AB4}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ScratchIsland", "src\tools\ScratchIsland\ScratchIsland.vcxproj", "{23A1F736-CD19-4196-980F-84BCD50CF783}"
ProjectSection(ProjectDependencies) = postProject
{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 +2003,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 +2271,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

@@ -685,8 +685,38 @@
},
"useTabSwitcher": {
"default": true,
"description": "When set to \"true\", the \"nextTab\" and \"prevTab\" commands will use the tab switcher UI.",
"type": "boolean"
"description": "Deprecated. Please use \"tabSwitcherMode\" instead.",
"oneOf": [
{
"type": "boolean"
},
{
"enum": [
"mru",
"inOrder",
"disabled",
],
"type": "string"
}
],
"deprecated": true
},
"tabSwitcherMode": {
"default": true,
"description": "When set to \"true\" or \"mru\", the \"nextTab\" and \"prevTab\" commands will use the tab switcher UI, with most-recently-used ordering. When set to \"inOrder\", these actions will switch tabs in their current ordering. Set to \"false\" to disable the tab switcher.",
"oneOf": [
{
"type": "boolean"
},
{
"enum": [
"mru",
"inOrder",
"disabled",
],
"type": "string"
}
]
}
},
"required": [

View File

@@ -83,6 +83,8 @@ namespace SettingsModelLocalTests
TEST_METHOD(TestCopy);
TEST_METHOD(TestCloneInheritanceTree);
TEST_METHOD(TestValidDefaults);
TEST_CLASS_SETUP(ClassSetup)
{
InitializeJsonReader();
@@ -2583,4 +2585,13 @@ namespace SettingsModelLocalTests
verifyEmptyPD(emptyPDJson);
verifyEmptyPD(missingPDJson);
}
void DeserializationTests::TestValidDefaults()
{
// GH#8146: A LoadDefaults call should populate the list of active profiles
const auto settings{ CascadiaSettings::LoadDefaults() };
VERIFY_ARE_EQUAL(settings.ActiveProfiles().Size(), settings.AllProfiles().Size());
VERIFY_ARE_EQUAL(settings.AllProfiles().Size(), 2u);
}
}

View File

@@ -7,17 +7,32 @@
#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;
using namespace TerminalApp;
using namespace winrt::TerminalApp;
using namespace winrt::Microsoft::Terminal::Settings::Model;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
using namespace winrt::Windows::ApplicationModel::DataTransfer;
using namespace winrt::Windows::Foundation::Collections;
using namespace winrt::Windows::System;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::UI::Text;
namespace winrt
{
namespace MUX = Microsoft::UI::Xaml;
namespace WUX = Windows::UI::Xaml;
using IInspectable = Windows::Foundation::IInspectable;
}
namespace TerminalAppLocalTests
{
@@ -66,6 +81,8 @@ namespace TerminalAppLocalTests
TEST_METHOD(MoveFocusFromZoomedPane);
TEST_METHOD(CloseZoomedPane);
TEST_METHOD(NextMRUTab);
TEST_CLASS_SETUP(ClassSetup)
{
return true;
@@ -82,6 +99,13 @@ namespace TerminalAppLocalTests
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> _commonSetup();
};
template<typename TFunction>
void TestOnUIThread(const TFunction& function)
{
const auto result = RunOnUIThread(function);
VERIFY_SUCCEEDED(result);
}
void TabTests::EnsureTestsActivate()
{
// This test was originally used to ensure that XAML Islands was
@@ -250,8 +274,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 +477,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 +487,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 +505,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.");
@@ -513,16 +537,31 @@ namespace TerminalAppLocalTests
const std::string settingsJson0{ R"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"showTabsInTitlebar": false,
"profiles": [
{
"name" : "profile0",
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"tabTitle" : "Profile 0",
"historySize": 1
},
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"tabTitle" : "Profile 1",
"historySize": 2
},
{
"name" : "profile2",
"guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}",
"tabTitle" : "Profile 2",
"historySize": 3
},
{
"name" : "profile3",
"guid": "{6239a42c-4444-49a3-80bd-e8fdd045185c}",
"tabTitle" : "Profile 3",
"historySize": 4
}
]
})" };
@@ -562,7 +601,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 +612,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 +622,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 +639,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 +653,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 +667,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 +684,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 +698,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 +711,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,10 +722,130 @@ 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());
});
VERIFY_SUCCEEDED(result);
}
void TabTests::NextMRUTab()
{
// This is a test for GH#8025 - we want to make sure that we can do both
// in-order and MRU tab traversal, using the tab switcher and with the
// tab switcher disabled.
auto page = _commonSetup();
Log::Comment(L"Create a second tab");
TestOnUIThread([&page]() {
NewTerminalArgs newTerminalArgs{ 1 };
page->_OpenNewTab(newTerminalArgs);
});
VERIFY_ARE_EQUAL(2u, page->_tabs.Size());
Log::Comment(L"Create a third tab");
TestOnUIThread([&page]() {
NewTerminalArgs newTerminalArgs{ 2 };
page->_OpenNewTab(newTerminalArgs);
});
VERIFY_ARE_EQUAL(3u, page->_tabs.Size());
Log::Comment(L"Create a fourth tab");
TestOnUIThread([&page]() {
NewTerminalArgs newTerminalArgs{ 3 };
page->_OpenNewTab(newTerminalArgs);
});
VERIFY_ARE_EQUAL(4u, page->_tabs.Size());
TestOnUIThread([&page]() {
uint32_t focusedIndex = page->_GetFocusedTabIndex().value_or(-1);
VERIFY_ARE_EQUAL(3u, focusedIndex, L"Verify the fourth tab is the focused one");
});
Log::Comment(L"Select the second tab");
TestOnUIThread([&page]() {
page->_SelectTab(1);
});
TestOnUIThread([&page]() {
uint32_t focusedIndex = page->_GetFocusedTabIndex().value_or(-1);
VERIFY_ARE_EQUAL(1u, focusedIndex, L"Verify the second tab is the focused one");
});
Log::Comment(L"Change the tab switch order to MRU switching");
TestOnUIThread([&page]() {
page->_settings.GlobalSettings().TabSwitcherMode(TabSwitcherMode::MostRecentlyUsed);
});
Log::Comment(L"Switch to the next MRU tab, which is the fourth tab");
TestOnUIThread([&page]() {
ActionEventArgs eventArgs{};
page->_HandleNextTab(nullptr, eventArgs);
});
Log::Comment(L"Sleep to let events propagate");
Sleep(250);
TestOnUIThread([&page]() {
Log::Comment(L"Hide the command palette, to confirm the selection");
// If you don't do this, the palette will just stay open, and the
// next time we call _HandleNextTab, we'll continue traversing the
// MRU list, instead of just hoping one entry.
page->CommandPalette().Visibility(Visibility::Collapsed);
});
TestOnUIThread([&page]() {
uint32_t focusedIndex = page->_GetFocusedTabIndex().value_or(-1);
VERIFY_ARE_EQUAL(3u, focusedIndex, L"Verify the fourth tab is the focused one");
});
Log::Comment(L"Switch to the next MRU tab, which is the second tab");
TestOnUIThread([&page]() {
ActionEventArgs eventArgs{};
page->_HandleNextTab(nullptr, eventArgs);
});
Log::Comment(L"Sleep to let events propagate");
Sleep(250);
TestOnUIThread([&page]() {
Log::Comment(L"Hide the command palette, to confirm the selection");
// If you don't do this, the palette will just stay open, and the
// next time we call _HandleNextTab, we'll continue traversing the
// MRU list, instead of just hoping one entry.
page->CommandPalette().Visibility(Visibility::Collapsed);
});
TestOnUIThread([&page]() {
uint32_t focusedIndex = page->_GetFocusedTabIndex().value_or(-1);
VERIFY_ARE_EQUAL(1u, focusedIndex, L"Verify the second tab is the focused one");
});
Log::Comment(L"Change the tab switch order to in-order switching");
page->_settings.GlobalSettings().TabSwitcherMode(TabSwitcherMode::InOrder);
Log::Comment(L"Switch to the next in-order tab, which is the third tab");
TestOnUIThread([&page]() {
ActionEventArgs eventArgs{};
page->_HandleNextTab(nullptr, eventArgs);
});
TestOnUIThread([&page]() {
uint32_t focusedIndex = page->_GetFocusedTabIndex().value_or(-1);
VERIFY_ARE_EQUAL(2u, focusedIndex, L"Verify the third tab is the focused one");
});
Log::Comment(L"Change the tab switch order to not use the tab switcher (which is in-order always)");
page->_settings.GlobalSettings().TabSwitcherMode(TabSwitcherMode::Disabled);
Log::Comment(L"Switch to the next in-order tab, which is the fourth tab");
TestOnUIThread([&page]() {
ActionEventArgs eventArgs{};
page->_HandleNextTab(nullptr, eventArgs);
});
TestOnUIThread([&page]() {
uint32_t focusedIndex = page->_GetFocusedTabIndex().value_or(-1);
VERIFY_ARE_EQUAL(3u, focusedIndex, L"Verify the fourth tab is the focused one");
});
}
}

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);
}
@@ -508,12 +524,7 @@ namespace winrt::TerminalApp::implementation
const ActionEventArgs& args)
{
// Tab search is always in-order.
auto tabCommands = winrt::single_threaded_vector<Command>();
for (const auto& tab : _tabs)
{
tabCommands.Append(tab.SwitchToTabCommand());
}
CommandPalette().SetTabActions(tabCommands);
_UpdatePaletteWithInOrderTabs();
auto opt = _GetFocusedTabIndex();
uint32_t startIdx = opt.value_or(0);

View File

@@ -470,6 +470,11 @@ namespace winrt::TerminalApp::implementation
void AppLogic::_OnLoaded(const IInspectable& /*sender*/,
const RoutedEventArgs& /*eventArgs*/)
{
const auto keyboardServiceIsDisabled = !_IsKeyboardServiceEnabled();
if (keyboardServiceIsDisabled)
{
_root->ShowKeyboardServiceWarning();
}
if (FAILED(_settingsLoadedResult))
{
const winrt::hstring titleKey = USES_RESOURCE(L"InitialJsonParseErrorTitle");
@@ -482,6 +487,51 @@ namespace winrt::TerminalApp::implementation
}
}
// Method Description:
// - Helper for determining if the "Touch Keyboard and Handwriting Panel
// Service" is enabled. If it isn't, we want to be able to display a
// warning to the user, because they won't be able to type in the
// Terminal.
// Return Value:
// - true if the service is enabled, or if we fail to query the service. We
// return true in that case, to be less noisy (though, that is unexpected)
bool AppLogic::_IsKeyboardServiceEnabled()
{
if (IsUwp())
{
return true;
}
// If at any point we fail to open the service manager, the service,
// etc, then just quick return true to disable the dialog. We'd rather
// not be noisy with this dialog if we failed for some reason.
// Open the service manager. This will return 0 if it failed.
wil::unique_schandle hManager{ OpenSCManager(nullptr, nullptr, 0) };
if (LOG_LAST_ERROR_IF(!hManager.is_valid()))
{
return true;
}
// Get a handle to the keyboard service
wil::unique_schandle hService{ OpenService(hManager.get(), TabletInputServiceKey.data(), SERVICE_QUERY_STATUS) };
if (LOG_LAST_ERROR_IF(!hService.is_valid()))
{
return true;
}
// Get the current state of the service
SERVICE_STATUS status{ 0 };
if (!LOG_IF_WIN32_BOOL_FALSE(QueryServiceStatus(hService.get(), &status)))
{
return true;
}
const auto state = status.dwCurrentState;
return (state == SERVICE_RUNNING || state == SERVICE_START_PENDING);
}
// Method Description:
// - Get the size in pixels of the client area we'll need to launch this
// terminal app. This method will use the default profile's settings to do

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"
@@ -86,6 +84,8 @@ namespace winrt::TerminalApp::implementation
void _ShowLoadErrorsDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey, HRESULT settingsLoadedResult);
void _ShowLoadWarningsDialog();
bool _IsKeyboardServiceEnabled();
void _ShowKeyboardServiceDisabledDialog();
fire_and_forget _LoadErrorsDialogRoutine();
fire_and_forget _ShowLoadWarningsDialogRoutine();

View File

@@ -28,7 +28,7 @@ namespace winrt::TerminalApp::implementation
_nestedActionStack = winrt::single_threaded_vector<Command>();
_currentNestedCommands = winrt::single_threaded_vector<Command>();
_allCommands = winrt::single_threaded_vector<Command>();
_allTabActions = winrt::single_threaded_vector<Command>();
_tabActions = winrt::single_threaded_vector<Command>();
_switchToMode(CommandPaletteMode::ActionMode);
@@ -51,9 +51,9 @@ namespace winrt::TerminalApp::implementation
if (_currentMode == CommandPaletteMode::TabSwitchMode)
{
_searchBox().Visibility(Visibility::Collapsed);
_filteredActionsView().Focus(FocusState::Keyboard);
_filteredActionsView().SelectedIndex(_switcherStartIdx);
_filteredActionsView().ScrollIntoView(_filteredActionsView().SelectedItem());
_filteredActionsView().Focus(FocusState::Keyboard);
// Do this right after becoming visible so we can quickly catch scenarios where
// modifiers aren't held down (e.g. command palette invocation).
@@ -61,9 +61,9 @@ namespace winrt::TerminalApp::implementation
}
else
{
_filteredActionsView().SelectedIndex(0);
_searchBox().Focus(FocusState::Programmatic);
_updateFilteredActions();
_filteredActionsView().SelectedIndex(0);
}
TraceLoggingWrite(
@@ -428,6 +428,25 @@ namespace winrt::TerminalApp::implementation
_dispatchCommand(e.ClickedItem().try_as<Command>());
}
// Method Description:
// This event is called when the user clicks on an ChevronLeft button right
// next to the ParentCommandName (e.g. New Tab...) above the subcommands list.
// It'll go up a level when the users click the button.
// Arguments:
// - sender: the button that got clicked
// Return Value:
// - <none>
void CommandPalette::_moveBackButtonClicked(Windows::Foundation::IInspectable const& /*sender*/,
Windows::UI::Xaml::RoutedEventArgs const&)
{
_nestedActionStack.Clear();
ParentCommandName(L"");
_currentNestedCommands.Clear();
_searchBox().Focus(FocusState::Programmatic);
_updateFilteredActions();
_filteredActionsView().SelectedIndex(0);
}
// Method Description:
// - This is called when the user selects a command with subcommands. It
// will update our UI to now display the list of subcommands instead, and
@@ -472,8 +491,9 @@ namespace winrt::TerminalApp::implementation
return _allCommands;
case CommandPaletteMode::TabSearchMode:
return _tabActions;
case CommandPaletteMode::TabSwitchMode:
return _allTabActions;
return _tabActions;
case CommandPaletteMode::CommandlineMode:
return winrt::single_threaded_vector<Command>();
default:
@@ -684,9 +704,20 @@ namespace winrt::TerminalApp::implementation
_updateFilteredActions();
}
void CommandPalette::SetTabActions(Collections::IVector<Command> const& tabs)
void CommandPalette::SetTabActions(Collections::IVector<Command> const& tabs, const bool clearList)
{
_allTabActions = tabs;
_tabActions = tabs;
// The smooth remove/add animations that happen during
// UpdateFilteredActions don't work very well with changing the tab
// order, because of the sheer amount of remove/adds. So, let's just
// clear & rebuild the list when we change the set of tabs.
//
// Some callers might actually want smooth updating, like when the list
// of tabs changes.
if (clearList && _currentMode == CommandPaletteMode::TabSwitchMode)
{
_filteredActions.Clear();
}
_updateFilteredActions();
}
@@ -1066,4 +1097,5 @@ namespace winrt::TerminalApp::implementation
_updateFilteredActions();
}
}

View File

@@ -23,7 +23,7 @@ namespace winrt::TerminalApp::implementation
Windows::Foundation::Collections::IObservableVector<Microsoft::Terminal::Settings::Model::Command> FilteredActions();
void SetCommands(Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command> const& actions);
void SetTabActions(Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command> const& tabs);
void SetTabActions(Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command> const& tabs, const bool clearList);
void SetKeyBindings(Microsoft::Terminal::TerminalControl::IKeyBindings bindings);
void EnableCommandPaletteMode();
@@ -40,6 +40,7 @@ namespace winrt::TerminalApp::implementation
// Tab Switcher
void EnableTabSwitcherMode(const bool searchMode, const uint32_t startIdx);
void SetTabSwitchOrder(const Microsoft::Terminal::Settings::Model::TabSwitcherMode order);
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, NoMatchesText, _PropertyChangedHandlers);
@@ -57,7 +58,6 @@ namespace winrt::TerminalApp::implementation
Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command> _nestedActionStack{ nullptr };
winrt::TerminalApp::ShortcutActionDispatch _dispatch;
Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command> _commandsToFilter();
bool _lastFilterTextWasEmpty{ true };
@@ -81,6 +81,8 @@ namespace winrt::TerminalApp::implementation
void _listItemClicked(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Controls::ItemClickEventArgs const& e);
void _moveBackButtonClicked(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const&);
void _updateFilteredActions();
std::vector<Microsoft::Terminal::Settings::Model::Command> _collectFilteredActions();
@@ -97,7 +99,7 @@ namespace winrt::TerminalApp::implementation
Microsoft::Terminal::TerminalControl::IKeyBindings _bindings;
// Tab Switcher
Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command> _allTabActions{ nullptr };
Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command> _tabActions{ nullptr };
uint32_t _switcherStartIdx;
void _anchorKeyUpHandler();

View File

@@ -19,7 +19,7 @@ namespace TerminalApp
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Model.Command> FilteredActions { get; };
void SetCommands(Windows.Foundation.Collections.IVector<Microsoft.Terminal.Settings.Model.Command> actions);
void SetTabActions(Windows.Foundation.Collections.IVector<Microsoft.Terminal.Settings.Model.Command> tabs);
void SetTabActions(Windows.Foundation.Collections.IVector<Microsoft.Terminal.Settings.Model.Command> tabs, Boolean clearList);
void SetKeyBindings(Microsoft.Terminal.TerminalControl.IKeyBindings bindings);
void EnableCommandPaletteMode();

View File

@@ -186,16 +186,36 @@ the MIT License. See LICENSE in the project root for license information. -->
>
</TextBlock>
<TextBlock
Padding="16, 0, 16, 4"
x:Name="_parentCommandText"
FontStyle="Italic"
Visibility="{x:Bind ParentCommandName,
Mode=OneWay,
Converter={StaticResource ParentCommandVisibilityConverter}}"
Grid.Row="1"
Text="{x:Bind ParentCommandName, Mode=OneWay}">
</TextBlock>
<StackPanel Orientation="Horizontal"
Padding="16, 0, 16, 4"
Grid.Row="1"
Visibility="{x:Bind ParentCommandName,
Mode=OneWay,
Converter={StaticResource ParentCommandVisibilityConverter}}">
<Button
Background="Transparent"
x:Name="_parentCommandBackButton"
x:Uid="ParentCommandBackButton"
Click="_moveBackButtonClicked"
ClickMode="Press"
VerticalAlignment="Center">
<FontIcon
FontSize="12"
FontFamily="Segoe MDL2 Assets"
Glyph="&#xE76b;">
</FontIcon>
</Button>
<TextBlock
Padding="16, 0, 16, 4"
x:Name="_parentCommandText"
FontStyle="Italic"
Grid.Row="1"
Text="{x:Bind ParentCommandName, Mode=OneWay}"
VerticalAlignment="Center">
</TextBlock>
</StackPanel>
<TextBlock
Padding="16"

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@@ -149,6 +149,16 @@
<data name="SettingsValidateErrorTitle" xml:space="preserve">
<value>Encountered errors while loading user settings</value>
</data>
<data name="KeyboardServiceDisabledDialog.PrimaryButtonText" xml:space="preserve">
<value>OK</value>
</data>
<data name="KeyboardServiceDisabledDialog.Title" xml:space="preserve">
<value>Warning:</value>
</data>
<data name="KeyboardServiceWarningText" xml:space="preserve">
<value>The "{0}" isn't running on your machine. This can prevent the Terminal from receiving keyboard input.</value>
<comment>{0} will be replaced with the OS-localized name of the TabletInputService</comment>
</data>
<data name="Ok" xml:space="preserve">
<value>OK</value>
</data>
@@ -472,4 +482,10 @@
<data name="FailedToWriteToSettings" xml:space="preserve">
<value>We could not write to your settings file. Check the permissions on that file to ensure that the read-only flag is not set and that write access is granted.</value>
</data>
<data name="ParentCommandBackButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Back</value>
</data>
<data name="ParentCommandBackButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Back</value>
</data>
</root>

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,20 +1033,32 @@ 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))
{
_mruTabActions.RemoveAt(mruIndex);
CommandPalette().SetTabActions(_mruTabActions);
}
_tabs.RemoveAt(tabIndex);
_tabView.TabItems().RemoveAt(tabIndex);
_UpdateTabIndices();
// If the tab switcher is currently open, update it to reflect the
// current list of tabs.
const auto tabSwitchMode = _settings.GlobalSettings().TabSwitcherMode();
const bool commandPaletteIsVisible = CommandPalette().Visibility() == Visibility::Visible;
if (tabSwitchMode == TabSwitcherMode::MostRecentlyUsed && commandPaletteIsVisible)
{
CommandPalette().SetTabActions(_mruTabActions, false);
}
else if (commandPaletteIsVisible)
{
_UpdatePaletteWithInOrderTabs();
}
// To close the window here, we need to close the hosting window.
if (_tabs.Size() == 0)
{
@@ -1091,8 +1096,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 +1119,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 +1149,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 +1165,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 +1175,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();
}
@@ -1184,36 +1189,83 @@ namespace winrt::TerminalApp::implementation
// _ClearNewTabButtonColor();
}
// Method Description:
// - Updates the command palette (tab switcher) with a list of actions
// reflecting the current in-order list of tabs.
void TerminalPage::_UpdatePaletteWithInOrderTabs()
{
auto tabCommands = winrt::single_threaded_vector<Command>();
for (const auto& tab : _tabs)
{
tabCommands.Append(tab.SwitchToTabCommand());
}
CommandPalette().SetTabActions(tabCommands, true);
}
// Method Description:
// - Sets focus to the tab to the right or left the currently selected tab.
void TerminalPage::_SelectNextTab(const bool bMoveRight)
{
if (_settings.GlobalSettings().UseTabSwitcher())
{
CommandPalette().SetTabActions(_mruTabActions);
const auto tabSwitchMode = _settings.GlobalSettings().TabSwitcherMode();
const bool useInOrderTabIndex = tabSwitchMode != TabSwitcherMode::MostRecentlyUsed;
// Since ATS is always MRU, our focused tab index is always 0.
// So, going next should go to index 1, and going prev should wrap to the end.
// First, determine what the index of the newly selected tab should be.
// This changes if we're doing an in-order traversal vs a MRU traversal.
auto newTabIndex = 0;
if (useInOrderTabIndex)
{
// Determine what the next in-order tab index is
if (auto index{ _GetFocusedTabIndex() })
{
uint32_t tabCount = _tabs.Size();
// Wraparound math. By adding tabCount and then calculating
// modulo tabCount, we clamp the values to the range [0,
// tabCount) while still supporting moving leftward from 0 to
// tabCount - 1.
newTabIndex = ((tabCount + *index + (bMoveRight ? 1 : -1)) % tabCount);
}
}
else
{
// Determine what the next "most recently used" index is.
// In this case, our focused tab index (in the MRU ordering) is
// always 0. So, going next should go to index 1, and going prev
// should wrap to the end.
uint32_t tabCount = _mruTabActions.Size();
auto newTabIndex = ((tabCount + (bMoveRight ? 1 : -1)) % tabCount);
newTabIndex = ((tabCount + (bMoveRight ? 1 : -1)) % tabCount);
}
const bool useTabSwitcher = tabSwitchMode != TabSwitcherMode::Disabled;
if (useTabSwitcher)
{
if (useInOrderTabIndex)
{
// Set up the list of in-order tabs
_UpdatePaletteWithInOrderTabs();
}
else
{
// Set up the list of MRU tabs
CommandPalette().SetTabActions(_mruTabActions, true);
}
if (CommandPalette().Visibility() == Visibility::Visible)
{
// If the tab switcher is currently open, don't change its mode.
// Just select the new tab.
CommandPalette().SelectNextItem(bMoveRight);
}
else
{
// Otherwise, set up the tab switcher in the selected mode, with
// the given ordering, and make it visible.
CommandPalette().EnableTabSwitcherMode(false, newTabIndex);
CommandPalette().Visibility(Visibility::Visible);
}
}
else if (auto index{ _GetFocusedTabIndex() })
{
uint32_t tabCount = _tabs.Size();
// Wraparound math. By adding tabCount and then calculating modulo tabCount,
// we clamp the values to the range [0, tabCount) while still supporting moving
// leftward from 0 to tabCount - 1.
auto newTabIndex = ((tabCount + *index + (bMoveRight ? 1 : -1)) % tabCount);
_SelectTab(newTabIndex);
}
}
@@ -1234,8 +1286,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 +1315,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 +1345,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 +1357,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 +1385,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 +1414,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 +1437,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 +1482,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 +1532,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 +1615,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 +1637,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 +1754,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 +2035,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 +2059,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 +2099,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 +2173,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 +2188,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 +2338,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 +2399,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 +2600,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 +2639,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:
@@ -2636,11 +2757,87 @@ namespace winrt::TerminalApp::implementation
{
_mruTabActions.RemoveAt(mruIndex);
_mruTabActions.InsertAt(0, command);
CommandPalette().SetTabActions(_mruTabActions);
// If the tab switcher is currently open, AND we're using it in
// MRU mode, then update it to reflect the current list of tabs.
const auto tabSwitchMode = _settings.GlobalSettings().TabSwitcherMode();
const bool commandPaletteIsVisible = CommandPalette().Visibility() == Visibility::Visible;
if (tabSwitchMode == TabSwitcherMode::MostRecentlyUsed && commandPaletteIsVisible)
{
CommandPalette().SetTabActions(_mruTabActions, false);
}
}
}
}
// Method Description:
// - Displays a dialog stating the "Touch Keyboard and Handwriting Panel
// Service" is disabled.
void TerminalPage::ShowKeyboardServiceWarning()
{
if (auto presenter{ _dialogPresenter.get() })
{
presenter.ShowDialog(FindName(L"KeyboardServiceDisabledDialog").try_as<WUX::Controls::ContentDialog>());
}
}
// Function Description:
// - Helper function to get the OS-localized name for the "Touch Keyboard
// and Handwriting Panel Service". If we can't open up the service for any
// reason, then we'll just return the service's key, "TabletInputService".
// Return Value:
// - The OS-localized name for the TabletInputService
winrt::hstring _getTabletServiceName()
{
auto isUwp = false;
try
{
isUwp = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsUwp();
}
CATCH_LOG();
if (isUwp)
{
return winrt::hstring{ TabletInputServiceKey };
}
wil::unique_schandle hManager{ OpenSCManager(nullptr, nullptr, 0) };
if (LOG_LAST_ERROR_IF(!hManager.is_valid()))
{
return winrt::hstring{ TabletInputServiceKey };
}
DWORD cchBuffer = 0;
GetServiceDisplayName(hManager.get(), TabletInputServiceKey.data(), nullptr, &cchBuffer);
std::wstring buffer;
cchBuffer += 1; // Add space for a null
buffer.resize(cchBuffer);
if (LOG_LAST_ERROR_IF(!GetServiceDisplayName(hManager.get(),
TabletInputServiceKey.data(),
buffer.data(),
&cchBuffer)))
{
return winrt::hstring{ TabletInputServiceKey };
}
return winrt::hstring{ buffer };
}
// Method Description:
// - Return the fully-formed warning message for the
// "KeyboardServiceDisabled" dialog. This dialog is used to warn the user
// if the keyboard service is disabled, and uses the OS localization for
// the service's actual name. It's bound to the dialog in XAML.
// Return Value:
// - The warning message, including the OS-localized service name.
winrt::hstring TerminalPage::KeyboardServiceDisabledText()
{
const winrt::hstring serviceName{ _getTabletServiceName() };
const winrt::hstring text{ fmt::format(std::wstring_view(RS_(L"KeyboardServiceWarningText")), serviceName) };
return text;
}
// -------------------------------- WinRT Events ---------------------------------
// Winrt events need a method for adding a callback to the event and removing the callback.
// These macros will define them both for you.

View File

@@ -4,7 +4,7 @@
#pragma once
#include "TerminalPage.g.h"
#include "Tab.h"
#include "TerminalTab.h"
#include "AppKeyBindings.h"
#include "TerminalSettings.h"
@@ -13,7 +13,7 @@
#include "AppCommandlineArgs.h"
static constexpr uint32_t DefaultRowsToScroll{ 3 };
static constexpr std::wstring_view TabletInputServiceKey{ L"TabletInputService" };
// fwdecl unittest classes
namespace TerminalAppLocalTests
{
@@ -71,6 +71,9 @@ namespace winrt::TerminalApp::implementation
winrt::TerminalApp::IDialogPresenter DialogPresenter() const;
void DialogPresenter(winrt::TerminalApp::IDialogPresenter dialogPresenter);
void ShowKeyboardServiceWarning();
winrt::hstring KeyboardServiceDisabledText();
// -------------------------------- WinRT Events ---------------------------------
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(TitleChanged, _titleChangeHandlers, winrt::Windows::Foundation::IInspectable, winrt::hstring);
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(LastTabClosed, _lastTabClosedHandlers, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::LastTabClosedEventArgs);
@@ -96,10 +99,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,11 +149,12 @@ 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();
void _UpdatePaletteWithInOrderTabs();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, Microsoft::Terminal::Settings::Model::Command> _ExpandCommands(Windows::Foundation::Collections::IMapView<winrt::hstring, Microsoft::Terminal::Settings::Model::Command> commandsToExpand,
Windows::Foundation::Collections::IVectorView<Microsoft::Terminal::Settings::Model::Profile> profiles,
Windows::Foundation::Collections::IMapView<winrt::hstring, Microsoft::Terminal::Settings::Model::ColorScheme> schemes);
@@ -159,7 +163,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 +171,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 +222,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

@@ -26,6 +26,8 @@ namespace TerminalApp
// that there's only one application-global dialog visible at a time,
// and because of GH#5224.
IDialogPresenter DialogPresenter;
void ShowKeyboardServiceWarning();
String KeyboardServiceDisabledText { get; };
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed;

View File

@@ -83,6 +83,20 @@ the MIT License. See LICENSE in the project root for license information. -->
</TextBlock>
</ContentDialog>
<ContentDialog
x:Load="False"
x:Name="KeyboardServiceDisabledDialog"
x:Uid="KeyboardServiceDisabledDialog"
DefaultButton="Primary">
<TextBlock
Foreground="{ThemeResource ErrorTextBrush}"
IsTextSelectionEnabled="True"
TextWrapping="WrapWholeWords"
Text="{x:Bind KeyboardServiceDisabledText, Mode=OneWay}" />
</ContentDialog>
<local:CommandPalette
x:Name="CommandPalette"
Grid.Row="1"

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

@@ -323,6 +323,7 @@ winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings CascadiaSettings::
resultPtr->_ParseJsonString(DefaultJson, true);
resultPtr->LayerJson(resultPtr->_defaultSettings);
resultPtr->_ResolveDefaultProfile();
resultPtr->_UpdateActiveProfiles();
return *resultPtr;
}

View File

@@ -37,7 +37,8 @@ static constexpr std::string_view ConfirmCloseAllKey{ "confirmCloseAllTabs" };
static constexpr std::string_view SnapToGridOnResizeKey{ "snapToGridOnResize" };
static constexpr std::string_view EnableStartupTaskKey{ "startOnUserLogin" };
static constexpr std::string_view AlwaysOnTopKey{ "alwaysOnTop" };
static constexpr std::string_view UseTabSwitcherKey{ "useTabSwitcher" };
static constexpr std::string_view LegacyUseTabSwitcherModeKey{ "useTabSwitcher" };
static constexpr std::string_view TabSwitcherModeKey{ "tabSwitcherMode" };
static constexpr std::string_view DisableAnimationsKey{ "disableAnimations" };
static constexpr std::string_view DebugFeaturesKey{ "debugFeatures" };
@@ -107,7 +108,7 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
globals->_DebugFeaturesEnabled = _DebugFeaturesEnabled;
globals->_StartOnUserLogin = _StartOnUserLogin;
globals->_AlwaysOnTop = _AlwaysOnTop;
globals->_UseTabSwitcher = _UseTabSwitcher;
globals->_TabSwitcherMode = _TabSwitcherMode;
globals->_DisableAnimations = _DisableAnimations;
globals->_UnparsedDefaultProfile = _UnparsedDefaultProfile;
@@ -286,7 +287,11 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
JsonUtils::GetValueForKey(json, AlwaysOnTopKey, _AlwaysOnTop);
JsonUtils::GetValueForKey(json, UseTabSwitcherKey, _UseTabSwitcher);
// GH#8076 - when adding enum values to this key, we also changed it from
// "useTabSwitcher" to "tabSwitcherMode". Continue supporting
// "useTabSwitcher", but prefer "tabSwitcherMode"
JsonUtils::GetValueForKey(json, LegacyUseTabSwitcherModeKey, _TabSwitcherMode);
JsonUtils::GetValueForKey(json, TabSwitcherModeKey, _TabSwitcherMode);
JsonUtils::GetValueForKey(json, DisableAnimationsKey, _DisableAnimations);

View File

@@ -81,7 +81,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
GETSET_SETTING(bool, DebugFeaturesEnabled); // default value set in constructor
GETSET_SETTING(bool, StartOnUserLogin, false);
GETSET_SETTING(bool, AlwaysOnTop, false);
GETSET_SETTING(bool, UseTabSwitcher, true);
GETSET_SETTING(Model::TabSwitcherMode, TabSwitcherMode, Model::TabSwitcherMode::MostRecentlyUsed);
GETSET_SETTING(bool, DisableAnimations, false);
private:

View File

@@ -26,6 +26,13 @@ namespace Microsoft.Terminal.Settings.Model
MaximizedFocusMode,
};
enum TabSwitcherMode
{
MostRecentlyUsed,
InOrder,
Disabled,
};
[default_interface] runtimeclass GlobalAppSettings {
Guid DefaultProfile;
Boolean HasUnparsedDefaultProfile();
@@ -120,9 +127,9 @@ namespace Microsoft.Terminal.Settings.Model
void ClearAlwaysOnTop();
Boolean AlwaysOnTop;
Boolean HasUseTabSwitcher();
void ClearUseTabSwitcher();
Boolean UseTabSwitcher;
Boolean HasTabSwitcherMode();
void ClearTabSwitcherMode();
TabSwitcherMode TabSwitcherMode;
Boolean HasDisableAnimations();
void ClearDisableAnimations();

View File

@@ -342,3 +342,26 @@ JSON_ENUM_MAPPER(::winrt::Windows::System::VirtualKey)
pair_type{ "shift", ValueType::Shift },
};
};
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::TabSwitcherMode)
{
JSON_MAPPINGS(3) = {
pair_type{ "mru", ValueType::MostRecentlyUsed },
pair_type{ "inOrder", ValueType::InOrder },
pair_type{ "disabled", ValueType::Disabled },
};
auto FromJson(const Json::Value& json)
{
if (json.isBool())
{
return json.asBool() ? ValueType::MostRecentlyUsed : ValueType::Disabled;
}
return BaseEnumMapper::FromJson(json);
}
bool CanConvert(const Json::Value& json)
{
return BaseEnumMapper::CanConvert(json) || json.isBool();
}
};

View File

@@ -18,7 +18,7 @@
"showTabsInTitlebar": true,
"showTerminalTitleInTitlebar": true,
"tabWidthMode": "equal",
"useTabSwitcher": true,
"useTabSwitcher": "mru",
// Miscellaneous
"confirmCloseAllTabs": true,

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

@@ -1141,7 +1141,7 @@ size_t Alias::s_ReplaceMacros(std::wstring& str,
// - If we found a matching alias, this will be the processed data
// and lineCount is updated to the new number of lines.
// - If we didn't match and process an alias, return an empty string.
std::wstring Alias::s_MatchAndCopyAlias(const std::wstring_view sourceText,
std::wstring Alias::s_MatchAndCopyAlias(const std::wstring& sourceText,
const std::wstring& exeName,
size_t& lineCount)
{

View File

@@ -24,7 +24,7 @@ public:
const std::wstring& exeName,
DWORD& lines);
static std::wstring s_MatchAndCopyAlias(const std::wstring_view sourceText,
static std::wstring s_MatchAndCopyAlias(const std::wstring& sourceText,
const std::wstring& exeName,
size_t& lineCount);

View File

@@ -3,8 +3,6 @@
#include "precomp.h"
#include <future>
// This particular block is necessary so we can include both a UNICODE and non-UNICODE version
// of our test supporting function so we can accurately portray and measure both types of text
// and test both versions of the console API.
@@ -49,24 +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()
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, 485 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()
};
// Caller must free ppsz if not null.
@@ -163,284 +143,3 @@ void AliasTests::TestGetConsoleAlias()
bSetFirst);
}
}
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;
}
void AliasTests::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");
auto inputs = _stringToInputs(commandWritten);
DWORD written = 0;
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleInputW(in, inputs.data(), gsl::narrow<DWORD>(inputs.size()), &written));
std::string buf;
buf.resize(500);
while (!commandExpected.empty())
{
DWORD read = 0;
auto tryRead = std::async(std::launch::async, [&] {
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleA(in, buf.data(), gsl::narrow<DWORD>(buf.size()), &read, nullptr));
});
if (std::future_status::ready != tryRead.wait_for(std::chrono::seconds{ 5 }))
{
// Shove something into the input to unstick it then fail.
auto events = _stringToInputs(L"a\r\n");
WriteConsoleInputW(in, events.data(), gsl::narrow<DWORD>(events.size()), &written);
VERIFY_FAILED(HRESULT_FROM_NT(STATUS_TIMEOUT));
// If somehow this still isn't enough to unstick the thread, the whole test timeout is 1 min
// set in the parameters/metadata at the top.
return;
}
auto actual = std::string{ buf.data(), read };
auto expected = commandExpected.front();
commandExpected.pop();
VERIFY_ARE_EQUAL(expected, actual);
}
}
void AliasTests::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));
auto modulePath = wil::GetModuleFileNameW<std::wstring>(nullptr);
std::filesystem::path path{ modulePath };
auto fileName = path.filename();
auto exeName = fileName.wstring();
std::wstring commandWritten = L"foo\r\n";
std::queue<std::string> commandExpected;
commandExpected.push("foo\r\n");
auto inputs = _stringToInputs(commandWritten);
DWORD written = 0;
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleInputW(in, inputs.data(), gsl::narrow<DWORD>(inputs.size()), &written));
std::string buf;
buf.resize(500);
while (!commandExpected.empty())
{
DWORD read = 0;
auto tryRead = std::async(std::launch::async, [&] {
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleA(in, buf.data(), gsl::narrow<DWORD>(buf.size()), &read, nullptr));
});
if (std::future_status::ready != tryRead.wait_for(std::chrono::seconds{ 5 }))
{
// Shove something into the input to unstick it then fail.
auto events = _stringToInputs(L"a\r\n");
WriteConsoleInputW(in, events.data(), gsl::narrow<DWORD>(events.size()), &written);
VERIFY_FAILED(HRESULT_FROM_NT(STATUS_TIMEOUT));
// If somehow this still isn't enough to unstick the thread, the whole test timeout is 1 min
// set in the parameters/metadata at the top.
return;
}
auto actual = std::string{ buf.data(), read };
auto expected = commandExpected.front();
commandExpected.pop();
VERIFY_ARE_EQUAL(expected, actual);
}
}
// tests todo:
// - test alpha in/out in 437/932 permutations
// - test leftover leadbyte/trailbyte when buffer too small
void AliasTests::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));
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 = L'\u03b1';
const std::string alpha437 = "\xe0";
const std::string alpha932 = "\x83\xbf";
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("\r\n");
sendInput.append(L"\r\n");
}
Log::Comment(L"send the string");
auto sendInputRecords = _stringToInputs(sendInput);
DWORD written;
VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleInputW(in, sendInputRecords.data(), gsl::narrow<DWORD>(sendInputRecords.size()), &written));
Log::Comment(L"receive the string");
std::string recvInput;
recvInput.resize(500); // excessively big
DWORD read;
VERIFY_WIN32_BOOL_SUCCEEDED(ReadConsoleA(in, recvInput.data(), gsl::narrow<DWORD>(recvInput.size()), &read, nullptr));
recvInput.resize(read);
// 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.
if (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);
}
}
// TODO tests:
// - leaving behind a lead/trail byte
// - leaving behind a lead/trail byte and having more data
// -- doing it in a loop/continuously.
// - read it char by char
// - change the codepage in the middle of reading and/or between commands

View File

@@ -1002,267 +1002,198 @@ void COOKED_READ_DATA::SavePendingInput(const size_t index, const bool multiline
// - Status code that indicates success, out of memory, etc.
[[nodiscard]] NTSTATUS COOKED_READ_DATA::_handlePostCharInputLoop(const bool isUnicode, size_t& numBytes, ULONG& controlKeyState) noexcept
{
std::wstring inputLine{ _backupLimit, _bytesRead / sizeof(WCHAR) };
size_t lineCount = 1;
DWORD LineCount = 1;
if (_echoInput)
{
const auto foundPos = inputLine.find(UNICODE_CARRIAGERETURN);
// If we find a carriage return...
if (decltype(inputLine)::npos != foundPos)
// Figure out where real string ends (at carriage return or end of buffer).
PWCHAR StringPtr = _backupLimit;
size_t StringLength = _bytesRead;
bool FoundCR = false;
for (size_t i = 0; i < (_bytesRead / sizeof(WCHAR)); i++)
{
if (*StringPtr++ == UNICODE_CARRIAGERETURN)
{
StringLength = i * sizeof(WCHAR);
FoundCR = true;
break;
}
}
if (FoundCR)
{
// ...and we have command history, save the command.
if (_commandHistory)
{
const auto command = inputLine.substr(0, foundPos); // Save only the command up to the \r\n, not the \r\n itself.
// add to command line recall list if we have a history list.
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
LOG_IF_FAILED(_commandHistory->Add( command, WI_IsFlagSet(gci.Flags, CONSOLE_HISTORY_NODUP)));
LOG_IF_FAILED(_commandHistory->Add({ _backupLimit, StringLength / sizeof(wchar_t) },
WI_IsFlagSet(gci.Flags, CONSOLE_HISTORY_NODUP)));
}
// Translate the alias if we find one that matches and replace our input line.
const auto aliasedLine = Alias::s_MatchAndCopyAlias(inputLine, _exeName, lineCount);
if (!aliasedLine.empty())
{
inputLine = aliasedLine;
}
// check for alias
ProcessAliases(LineCount);
}
}
gsl::span<byte> returnData;
std::string translation;
std::optional<DWORD> translationCP;
if (!isUnicode)
bool fAddDbcsLead = false;
size_t NumBytes = 0;
// at this point, a->NumBytes contains the number of bytes in
// the UNICODE string read. UserBufferSize contains the converted
// size of the app's buffer.
if (_bytesRead > _userBufferSize || LineCount > 1)
{
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
translationCP = gci.CP;
translation = ConvertToA(*translationCP, inputLine);
returnData = gsl::span<byte>{ reinterpret_cast<byte*>(translation.data()), translation.size() };
if (LineCount > 1)
{
PWSTR Tmp;
if (!isUnicode)
{
if (_pInputBuffer->IsReadPartialByteSequenceAvailable())
{
fAddDbcsLead = true;
std::unique_ptr<IInputEvent> event = GetInputBuffer()->FetchReadPartialByteSequence(false);
const KeyEvent* const pKeyEvent = static_cast<const KeyEvent* const>(event.get());
*_userBuffer = static_cast<char>(pKeyEvent->GetCharData());
_userBuffer++;
_userBufferSize -= sizeof(wchar_t);
}
NumBytes = 0;
for (Tmp = _backupLimit;
*Tmp != UNICODE_LINEFEED && _userBufferSize / sizeof(WCHAR) > NumBytes;
Tmp++)
{
NumBytes += IsGlyphFullWidth(*Tmp) ? 2 : 1;
}
}
// clang-format off
#pragma prefast(suppress: __WARNING_BUFFER_OVERFLOW, "LineCount > 1 means there's a UNICODE_LINEFEED")
// clang-format on
for (Tmp = _backupLimit; *Tmp != UNICODE_LINEFEED; Tmp++)
{
FAIL_FAST_IF(!(Tmp < (_backupLimit + _bytesRead)));
}
numBytes = (ULONG)(Tmp - _backupLimit + 1) * sizeof(*Tmp);
}
else
{
if (!isUnicode)
{
PWSTR Tmp;
if (_pInputBuffer->IsReadPartialByteSequenceAvailable())
{
fAddDbcsLead = true;
std::unique_ptr<IInputEvent> event = GetInputBuffer()->FetchReadPartialByteSequence(false);
const KeyEvent* const pKeyEvent = static_cast<const KeyEvent* const>(event.get());
*_userBuffer = static_cast<char>(pKeyEvent->GetCharData());
_userBuffer++;
_userBufferSize -= sizeof(wchar_t);
}
NumBytes = 0;
size_t NumToWrite = _bytesRead;
for (Tmp = _backupLimit;
NumToWrite && _userBufferSize / sizeof(WCHAR) > NumBytes;
Tmp++, NumToWrite -= sizeof(WCHAR))
{
NumBytes += IsGlyphFullWidth(*Tmp) ? 2 : 1;
}
}
numBytes = _userBufferSize;
}
__analysis_assume(numBytes <= _userBufferSize);
memmove(_userBuffer, _backupLimit, numBytes);
INPUT_READ_HANDLE_DATA* const pInputReadHandleData = GetInputReadHandleData();
const std::wstring_view pending{ _backupLimit + (numBytes / sizeof(wchar_t)), (_bytesRead - numBytes) / sizeof(wchar_t) };
if (LineCount > 1)
{
pInputReadHandleData->SaveMultilinePendingInput(pending);
}
else
{
pInputReadHandleData->SavePendingInput(pending);
}
}
else
{
returnData = gsl::span<byte>{ reinterpret_cast<byte*>(inputLine.data()), inputLine.size() * sizeof(wchar_t) };
if (!isUnicode)
{
PWSTR Tmp;
if (_pInputBuffer->IsReadPartialByteSequenceAvailable())
{
fAddDbcsLead = true;
std::unique_ptr<IInputEvent> event = GetInputBuffer()->FetchReadPartialByteSequence(false);
const KeyEvent* const pKeyEvent = static_cast<const KeyEvent* const>(event.get());
*_userBuffer = static_cast<char>(pKeyEvent->GetCharData());
_userBuffer++;
_userBufferSize -= sizeof(wchar_t);
if (_userBufferSize == 0)
{
numBytes = 1;
return STATUS_SUCCESS;
}
}
NumBytes = 0;
size_t NumToWrite = _bytesRead;
for (Tmp = _backupLimit;
NumToWrite && _userBufferSize / sizeof(WCHAR) > NumBytes;
Tmp++, NumToWrite -= sizeof(WCHAR))
{
NumBytes += IsGlyphFullWidth(*Tmp) ? 2 : 1;
}
}
numBytes = _bytesRead;
if (numBytes > _userBufferSize)
{
return STATUS_BUFFER_OVERFLOW;
}
memmove(_userBuffer, _backupLimit, numBytes);
}
gsl::span<byte> userBuffer{ reinterpret_cast<byte*>(_userBuffer), _userBufferSize };
const auto maxFit = std::min(returnData.size(), userBuffer.size());
std::copy_n(returnData.begin(), maxFit, userBuffer.begin());
numBytes = maxFit;
controlKeyState = _controlKeyState;
// TODO: limit translation to only return one line at a time (for multiline)
// TODO: store any full characters we couldn't return as pending input
// TODO: store any partial characters (trailing bytes) in the inputbuffer partial sequence
// TODO: retrieve leftover partials into the front of the return data
// TODO: store the original data, translation, its codepage, and the bytes we used of it
// so subsequent calls without changing the codepage can keep walking..
// ...and calls that do change the codepage can walk WC2MB to find out how much of the WC they
// already used and retranslate it. (this is slow, but changing modes in the middle is not advised)
if (!isUnicode)
{
// if ansi, translate string.
std::unique_ptr<char[]> tempBuffer;
try
{
tempBuffer = std::make_unique<char[]>(NumBytes);
}
catch (...)
{
return STATUS_NO_MEMORY;
}
std::unique_ptr<IInputEvent> partialEvent;
numBytes = TranslateUnicodeToOem(_userBuffer,
gsl::narrow<ULONG>(numBytes / sizeof(wchar_t)),
tempBuffer.get(),
gsl::narrow<ULONG>(NumBytes),
partialEvent);
if (partialEvent.get())
{
GetInputBuffer()->StoreReadPartialByteSequence(std::move(partialEvent));
}
if (numBytes > _userBufferSize)
{
return STATUS_BUFFER_OVERFLOW;
}
memmove(_userBuffer, tempBuffer.get(), numBytes);
if (fAddDbcsLead)
{
numBytes++;
}
}
return STATUS_SUCCESS;
//
// // ---
//
// DWORD LineCount = 1;
//
// if (_echoInput)
// {
// // Figure out where real string ends (at carriage return or end of buffer).
// PWCHAR StringPtr = _backupLimit;
// size_t StringLength = _bytesRead;
// bool FoundCR = false;
// for (size_t i = 0; i < (_bytesRead / sizeof(WCHAR)); i++)
// {
// if (*StringPtr++ == UNICODE_CARRIAGERETURN)
// {
// StringLength = i * sizeof(WCHAR);
// FoundCR = true;
// break;
// }
// }
//
// if (FoundCR)
// {
// if (_commandHistory)
// {
// // add to command line recall list if we have a history list.
// CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
// LOG_IF_FAILED(_commandHistory->Add({ _backupLimit, StringLength / sizeof(wchar_t) },
// WI_IsFlagSet(gci.Flags, CONSOLE_HISTORY_NODUP)));
// }
//
// // check for alias
// ProcessAliases(LineCount);
// }
// }
//
// bool fAddDbcsLead = false;
// size_t NumBytes = 0;
// // at this point, a->NumBytes contains the number of bytes in
// // the UNICODE string read. UserBufferSize contains the converted
// // size of the app's buffer.
// if (_bytesRead > _userBufferSize || LineCount > 1)
// {
// if (LineCount > 1)
// {
// PWSTR Tmp;
// if (!isUnicode)
// {
// if (_pInputBuffer->IsReadPartialByteSequenceAvailable())
// {
// fAddDbcsLead = true;
// std::unique_ptr<IInputEvent> event = GetInputBuffer()->FetchReadPartialByteSequence(false);
// const KeyEvent* const pKeyEvent = static_cast<const KeyEvent* const>(event.get());
// *_userBuffer = static_cast<char>(pKeyEvent->GetCharData());
// _userBuffer++;
// _userBufferSize -= sizeof(wchar_t);
// }
//
// NumBytes = 0;
// for (Tmp = _backupLimit;
// *Tmp != UNICODE_LINEFEED && _userBufferSize / sizeof(WCHAR) > NumBytes;
// Tmp++)
// {
// NumBytes += IsGlyphFullWidth(*Tmp) ? 2 : 1;
// }
// }
//
// // clang-format off
//#pragma prefast(suppress: __WARNING_BUFFER_OVERFLOW, "LineCount > 1 means there's a UNICODE_LINEFEED")
// // clang-format on
// for (Tmp = _backupLimit; *Tmp != UNICODE_LINEFEED; Tmp++)
// {
// FAIL_FAST_IF(!(Tmp < (_backupLimit + _bytesRead)));
// }
//
// numBytes = (ULONG)(Tmp - _backupLimit + 1) * sizeof(*Tmp);
// }
// else
// {
// if (!isUnicode)
// {
// PWSTR Tmp;
//
// if (_pInputBuffer->IsReadPartialByteSequenceAvailable())
// {
// fAddDbcsLead = true;
// std::unique_ptr<IInputEvent> event = GetInputBuffer()->FetchReadPartialByteSequence(false);
// const KeyEvent* const pKeyEvent = static_cast<const KeyEvent* const>(event.get());
// *_userBuffer = static_cast<char>(pKeyEvent->GetCharData());
// _userBuffer++;
// _userBufferSize -= sizeof(wchar_t);
// }
// NumBytes = 0;
// size_t NumToWrite = _bytesRead;
// for (Tmp = _backupLimit;
// NumToWrite && _userBufferSize / sizeof(WCHAR) > NumBytes;
// Tmp++, NumToWrite -= sizeof(WCHAR))
// {
// NumBytes += IsGlyphFullWidth(*Tmp) ? 2 : 1;
// }
// }
// numBytes = _userBufferSize;
// }
//
// __analysis_assume(numBytes <= _userBufferSize);
// memmove(_userBuffer, _backupLimit, numBytes);
//
// INPUT_READ_HANDLE_DATA* const pInputReadHandleData = GetInputReadHandleData();
// const std::wstring_view pending{ _backupLimit + (numBytes / sizeof(wchar_t)), (_bytesRead - numBytes) / sizeof(wchar_t) };
// if (LineCount > 1)
// {
// pInputReadHandleData->SaveMultilinePendingInput(pending);
// }
// else
// {
// pInputReadHandleData->SavePendingInput(pending);
// }
// }
// else
// {
// if (!isUnicode)
// {
// PWSTR Tmp;
//
// if (_pInputBuffer->IsReadPartialByteSequenceAvailable())
// {
// fAddDbcsLead = true;
// std::unique_ptr<IInputEvent> event = GetInputBuffer()->FetchReadPartialByteSequence(false);
// const KeyEvent* const pKeyEvent = static_cast<const KeyEvent* const>(event.get());
// *_userBuffer = static_cast<char>(pKeyEvent->GetCharData());
// _userBuffer++;
// _userBufferSize -= sizeof(wchar_t);
//
// if (_userBufferSize == 0)
// {
// numBytes = 1;
// return STATUS_SUCCESS;
// }
// }
// NumBytes = 0;
// size_t NumToWrite = _bytesRead;
// for (Tmp = _backupLimit;
// NumToWrite && _userBufferSize / sizeof(WCHAR) > NumBytes;
// Tmp++, NumToWrite -= sizeof(WCHAR))
// {
// NumBytes += IsGlyphFullWidth(*Tmp) ? 2 : 1;
// }
// }
//
// numBytes = _bytesRead;
//
// if (numBytes > _userBufferSize)
// {
// return STATUS_BUFFER_OVERFLOW;
// }
//
// memmove(_userBuffer, _backupLimit, numBytes);
// }
// controlKeyState = _controlKeyState;
//
// if (!isUnicode)
// {
// // if ansi, translate string.
// std::unique_ptr<char[]> tempBuffer;
// try
// {
// tempBuffer = std::make_unique<char[]>(NumBytes);
// }
// catch (...)
// {
// return STATUS_NO_MEMORY;
// }
//
// std::unique_ptr<IInputEvent> partialEvent;
// numBytes = TranslateUnicodeToOem(_userBuffer,
// gsl::narrow<ULONG>(numBytes / sizeof(wchar_t)),
// tempBuffer.get(),
// gsl::narrow<ULONG>(NumBytes),
// partialEvent);
//
// if (partialEvent.get())
// {
// GetInputBuffer()->StoreReadPartialByteSequence(std::move(partialEvent));
// }
//
// if (numBytes > _userBufferSize)
// {
// return STATUS_BUFFER_OVERFLOW;
// }
//
// memmove(_userBuffer, tempBuffer.get(), numBytes);
// if (fAddDbcsLead)
// {
// numBytes++;
// }
// }
// return STATUS_SUCCESS;
}

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,44 @@
To checkout the solution, and the branch I'm on, do the following:
```cmd
git clone https://github.com/microsoft/terminal.git
cd terminal
git checkout dev/migrie/oop-mixed-elevation-1
git submodule update --init --recursive
```
Then, to build the scratch projects I'm working with, do the following (in `cmd`):
_You may need to open the solution in VS once first, to make sure it installs all the dependencies!_
```cmd
.\tools\razzle.cmd
nuget restore
cd src\tools\ScratchWinRTClient
bx
```
(after the first build, `bx` will just rebuild the `ScratchWinRTClient` and `ScratchWinRTServer` projects.)
That should build both `ScratchWinRTClient` and `ScratchWinRTServer`. However, the client needs the server exe to live next to it, so you'll need to do the following (from the root of the solution)
```cmd
copy /y bin\x64\Debug\ScratchWinRTServer\ScratchWinRTServer.exe bin\x64\Debug\ScratchWinRTClient\
```
whenever the server's code changes.
Then, in one terminal window, run (again, from the root of the solution)
```cmd
bin\x64\Debug\ScratchWinRTClient\ScratchWinRTClient.exe
```
That'll print out a list of "Hosts" and their GUIDs. Copy one of those GUIDs, including braces. Then, in an elevated window, run:
```cmd
bin\x64\Debug\ScratchWinRTClient\ScratchWinRTClient.exe {the guid}
```
That'll hit the code in `createExistingObjectApp`, where we try to de-elevate

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>{06382349-d62a-4c7d-a7d3-9ca817eae092}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>ScratchWinRTClient</RootNamespace>
<ProjectName>ScratchWinRTClient</ProjectName>
<TargetName>ScratchWinRTClient</TargetName>
<ConfigurationType>Application</ConfigurationType>
<OpenConsoleUniversalApp>false</OpenConsoleUniversalApp>
<ApplicationType>Windows Store</ApplicationType>
<TargetPlatformIdentifier>Windows</TargetPlatformIdentifier>
</PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<PropertyGroup>
<GenerateManifest>true</GenerateManifest>
<EmbedManifest>true</EmbedManifest>
</PropertyGroup>
<!-- Source Files -->
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="HostManager.h">
<DependentUpon>HostManager.idl</DependentUpon>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="HostManager.cpp">
<DependentUpon>HostManager.idl</DependentUpon>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Midl Include="HostManager.idl" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<!-- Dependencies -->
<ItemGroup>
<ProjectReference Include="..\ScratchWinRTServer\ScratchWinRTServer.vcxproj">
<Project>{d46d9547-f085-4645-b8f7-e8cd21559ab4}</Project>
</ProjectReference>
<ProjectReference Include="$(SolutionDir)src\types\lib\types.vcxproj" />
</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" />
<!-- These have to come after post.props because the Cpp common targets will inexplicably overwrite them. -->
<ItemDefinitionGroup>
<ClCompile>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<!-- <Import Project="$(OpenConsoleDir)\build\rules\GenerateSxsManifestsFromWinmds.targets" /> -->
</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,376 @@
#include "pch.h"
#include <conio.h>
#include "HostManager.h"
#include <winrt/ScratchWinRTServer.h>
#include "../../types/inc/utils.hpp"
using namespace winrt;
using namespace winrt::Windows::Foundation;
using namespace ::Microsoft::Console;
void createExistingObjectApp(int /*argc*/, char** argv)
{
winrt::guid guidFromCmdline{};
std::string guidString{ argv[1] };
auto canConvert = guidString.length() == 38 && guidString.front() == '{' && guidString.back() == '}';
if (canConvert)
{
std::wstring wideGuidStr{ til::u8u16(guidString) };
printf("\x1b[90mCLIENT: Found GUID:%ls\x1b[m\n", wideGuidStr.c_str());
GUID result{};
THROW_IF_FAILED(IIDFromString(wideGuidStr.c_str(), &result));
guidFromCmdline = result;
}
if (guidFromCmdline == winrt::guid{})
{
printf("client did not recieve GUID, early returning.");
return; // -1;
}
winrt::ScratchWinRTServer::HostClass host{ nullptr };
{
HANDLE hProcessToken;
auto processTokenCleanup = wil::scope_exit([&] { CloseHandle(hProcessToken); });
// Open a handle to the access token for the
// calling process that is the currently login access token
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hProcessToken))
{
printf("OpenProcessToken()-Getting the handle to access token failed, error %u\n", GetLastError());
}
else
{
printf("OpenProcessToken()-Got the handle to access token!\n");
}
{
TOKEN_ELEVATION tokenElevation{ 0 };
DWORD neededTokenInformationLength = 0;
if (GetTokenInformation(hProcessToken,
TOKEN_INFORMATION_CLASS::TokenElevation,
&tokenElevation,
sizeof(tokenElevation),
&neededTokenInformationLength))
{
printf("GetTokenInformation(TokenElevation) succeeded\n");
printf("Token is elevated? - %s\n", (tokenElevation.TokenIsElevated != 0 ? "true" : "false"));
}
else
{
auto gle = GetLastError();
printf("GetTokenInformation(TokenElevation) failed: %d\n", gle);
}
}
TOKEN_LINKED_TOKEN tokenLinkedToken = {};
DWORD neededTokenInformationLength = 0;
if (!GetTokenInformation(hProcessToken,
TokenLinkedToken,
&tokenLinkedToken,
sizeof(tokenLinkedToken),
&neededTokenInformationLength))
{
auto gle = GetLastError();
printf("GetTokenInformation(TokenLinkedToken) failed: %d\n", gle);
return;
}
else
{
printf("Got the linked token for this process\n");
}
auto tokenLinkedTokenCleanup = wil::scope_exit([&] { CloseHandle(tokenLinkedToken.LinkedToken); });
// THIS IS THE DAMNDEST THING
//
// IF YOU DO THIS, THE PROCESS WILL JUST STRAIGHT UP DIE ON THE create_instance CALL
if (SetThreadToken(nullptr, tokenLinkedToken.LinkedToken))
{
printf("SetThreadToken() succeeded\n");
}
else
{
printf("SetThreadToken failed %x\n", GetLastError());
}
// // Lets the calling process impersonate the security context of a
// // logged-on user. UNFORTUNATELY, this did not work for me.
// // * ImpersonateLoggedOnUser(tokenLinkedToken.LinkedToken) does the same
// // thing as SetThreadToken(LinkedToken), it crashes when trying to
// // create_instance.
// // * ImpersonateLoggedOnUser(hProcessToken) does seemingly nothing at
// // all - we get an "Error: Class not registered"
//
// auto revert = wil::scope_exit([&]() {
// // Terminates the impersonation of a client.
// if (RevertToSelf())
// {
// printf("Impersonation was terminated.\n");
// }
// else
// {
// auto gle = GetLastError();
// printf("RevertToSelf failed, %d\n", gle);
// }
// });
// // if (ImpersonateLoggedOnUser(hProcessToken))
// if (ImpersonateLoggedOnUser(tokenLinkedToken.LinkedToken))
// {
// printf("ImpersonateLoggedOnUser() is OK.\n");
// }
// else
// {
// printf("ImpersonateLoggedOnUser() failed, error %u.\n", GetLastError());
// exit(-1);
// }
printf("Calling create_instance...\n");
host = create_instance<winrt::ScratchWinRTServer::HostClass>(guidFromCmdline,
// CLSCTX_LOCAL_SERVER);
CLSCTX_LOCAL_SERVER | CLSCTX_ENABLE_CLOAKING);
printf("Done\n");
}
if (!host)
{
printf("Could not get the existing HostClass\n");
return;
}
else
{
printf("Got the existing HostClass\n");
}
// The DoCount could be anything, depending on which of the hosts we're creating.
printf("DoCount: %d (Expected: ?)\n",
host.DoCount());
}
void createHostClassProcess(const winrt::guid& g)
{
auto guidStr{ Utils::GuidToString(g) };
std::wstring commandline{ fmt::format(L"ScratchWinRTServer.exe {}", guidStr) };
STARTUPINFO siOne{ 0 };
siOne.cb = sizeof(STARTUPINFOW);
wil::unique_process_information piOne;
auto succeeded = CreateProcessW(
nullptr,
commandline.data(),
nullptr, // lpProcessAttributes
nullptr, // lpThreadAttributes
false, // bInheritHandles
CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags
nullptr, // lpEnvironment
nullptr, // startingDirectory
&siOne, // lpStartupInfo
&piOne // lpProcessInformation
);
if (!succeeded)
{
printf("Failed to create first host process\n");
return;
}
// Ooof this is dumb, but we need a sleep here to make the server starts.
// That's _sub par_. Maybe we could use the host's stdout to have them emit
// a byte when they're set up?
Sleep(1000);
}
void scratchApp()
{
printf("scratchApp\n");
// 1. Generate a GUID.
// 2. Spawn a Server.exe, with the guid on the commandline
// 3. Make an instance of that GUID, as a HostClass
// 4. Call HostClass::DoTheThing, and get the count with HostClass::DoCount [1]
// 5. Make another instance of HostClass, and get the count with HostClass::DoCount. It should be the same. [1, 1]
// 6. On the second HostClass, call DoTheThing. Verify that both instances have the same DoCount. [2. 2]
// 7. Create a second Server.exe, and create a Third HostClass, using that GUID.
// 8. Call DoTheThing on the third, and verify the counts of all three instances. [2, 2, 1]
// 9. QUESTION: Does releasing the first instance leave the first object alive, since the second instance still points at it?
// 1. Generate a GUID.
winrt::guid firstGuid{ Utils::CreateGuid() };
// 2. Spawn a Server.exe, with the guid on the commandline
createHostClassProcess(firstGuid);
// 3. Make an instance of that GUID, as a HostClass
printf("Trying to directly create a HostClass...\n");
auto firstHost = create_instance<winrt::ScratchWinRTServer::HostClass>(firstGuid, CLSCTX_LOCAL_SERVER);
if (!firstHost)
{
printf("Could not get the first HostClass\n");
return;
}
printf("DoCount: %d (Expected: 0)\n",
firstHost.DoCount());
// 4. Call HostClass::DoTheThing, and get the count with HostClass::DoCount [1]
firstHost.DoTheThing();
printf("DoCount: %d (Expected: 1)\n",
firstHost.DoCount());
// 5. Make another instance of HostClass, and get the count with HostClass::DoCount. It should be the same. [1, 1]
auto secondHost = create_instance<winrt::ScratchWinRTServer::HostClass>(firstGuid, CLSCTX_LOCAL_SERVER);
if (!secondHost)
{
printf("Could not get the second HostClass\n");
return;
}
printf("DoCount: [%d, %d] (Expected: [1, 1])\n",
firstHost.DoCount(),
secondHost.DoCount());
// 6. On the second HostClass, call DoTheThing. Verify that both instances have the same DoCount. [2. 2]
secondHost.DoTheThing();
printf("DoCount: [%d, %d] (Expected: [2, 2])\n",
firstHost.DoCount(),
secondHost.DoCount());
// 7. Create a second Server.exe, and create a Third HostClass, using that GUID.
winrt::guid secondGuid{ Utils::CreateGuid() };
createHostClassProcess(secondGuid);
auto thirdHost = create_instance<winrt::ScratchWinRTServer::HostClass>(secondGuid, CLSCTX_LOCAL_SERVER);
if (!thirdHost)
{
printf("Could not get the third HostClass\n");
return;
}
printf("DoCount: [%d, %d, %d] (Expected: [2, 2, 0])\n",
firstHost.DoCount(),
secondHost.DoCount(),
thirdHost.DoCount());
// 8. Call DoTheThing on the third, and verify the counts of all three instances. [2, 2, 1]
thirdHost.DoTheThing();
printf("DoCount: [%d, %d, %d] (Expected: [2, 2, 1])\n",
firstHost.DoCount(),
secondHost.DoCount(),
thirdHost.DoCount());
}
static void printHosts(const ScratchWinRTClient::HostManager& manager)
{
int index = 0;
for (const auto& h : manager.Hosts())
{
auto guidStr{ Utils::GuidToString(h.Id()) };
printf("Host[%d]: DoCount=%d %ls\n", index, h.DoCount(), guidStr.c_str());
index++;
}
if (index == 0)
{
printf("<No hosts>\n");
}
}
void managerApp()
{
ScratchWinRTClient::HostManager manager;
printHosts(manager);
printf("Create host 0:\n");
auto host0 = manager.CreateHost();
printHosts(manager);
printf("Create host 1:\n");
auto host1 = manager.CreateHost();
host1.DoTheThing();
printHosts(manager);
printf("Create host 2:\n");
auto host2 = manager.CreateHost();
host2.DoTheThing();
host2.DoTheThing();
printHosts(manager);
printf("Create host 3:\n");
auto host3 = manager.CreateHost();
host3.DoTheThing();
host3.DoTheThing();
host3.DoTheThing();
printHosts(manager);
printf("increment host 0:\n");
host0.DoTheThing();
host0.DoTheThing();
host0.DoTheThing();
host0.DoTheThing();
printHosts(manager);
bool exitRequested = false;
while (!exitRequested)
{
printf("-----------------------------\n");
printf("input a command (l, i, c, q): ");
const auto ch = _getch();
printf("\n");
if (ch == 'l')
{
printHosts(manager);
}
else if (ch == 'i')
{
printf("input a host to increment: ");
const auto ch2 = _getch();
if (ch2 >= '0' && ch2 <= '9')
{
uint32_t index = ((int)(ch2)) - ((int)('0'));
if (index < manager.Hosts().Size())
{
manager.Hosts().GetAt(index).DoTheThing();
printHosts(manager);
}
}
}
else if (ch == 'c')
{
printf("Creating a new host\n");
manager.CreateHost();
printHosts(manager);
}
else if (ch == 'q')
{
exitRequested = true;
}
}
}
int main(int argc, char** argv)
{
auto hOut = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD dwMode = 0;
GetConsoleMode(hOut, &dwMode);
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(hOut, dwMode);
init_apartment();
try
{
// If a GUID was passed on the commandline, then try to instead make an instance of that class.
if (argc > 1)
{
createExistingObjectApp(argc, argv);
}
else
{
managerApp();
}
}
catch (hresult_error const& e)
{
printf("Error: %ls\n", e.message().c_str());
}
catch (...)
{
printf("Some other exception happened\n");
LOG_CAUGHT_EXCEPTION();
}
printf("Exiting client");
}

View File

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

View File

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

View File

@@ -0,0 +1,28 @@
#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"
// 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 <Unknwn.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Media.h>
#include <windows.ui.xaml.media.dxinterop.h>
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#include "til.h"

View File

@@ -0,0 +1,51 @@
#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 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 }
{
printf("HostClass()\n");
}
HostClass::~HostClass()
{
printf("~HostClass()\n");
std::unique_lock<std::mutex> lk(m);
dtored = true;
cv.notify_one();
}
int HostClass::DoCount()
{
return _DoCount;
}
void HostClass::DoTheThing()
{
_DoCount++;
}
winrt::guid HostClass::Id()
{
return _id;
}
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include "HostClass.g.h"
namespace winrt::ScratchWinRTServer::implementation
{
struct HostClass : HostClassT<HostClass>
{
HostClass(const winrt::guid& g);
~HostClass();
void DoTheThing();
int DoCount();
winrt::guid Id();
private:
int _DoCount{ 0 };
winrt::guid _id;
};
}
namespace winrt::ScratchWinRTServer::factory_implementation
{
struct HostClass : HostClassT<HostClass, implementation::HostClass>
{
};
}

View File

@@ -0,0 +1,11 @@
namespace ScratchWinRTServer
{
[default_interface] runtimeclass HostClass {
HostClass(Guid g);
void DoTheThing();
Int32 DoCount { get; };
Guid Id { get; };
};
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,24 @@
<?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>
</assembly>

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>{d46d9547-f085-4645-b8f7-e8cd21559ab4}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>ScratchWinRTServer</RootNamespace>
<ProjectName>ScratchWinRTServer</ProjectName>
<TargetName>ScratchWinRTServer</TargetName>
<ConfigurationType>Application</ConfigurationType>
<OpenConsoleUniversalApp>false</OpenConsoleUniversalApp>
<ApplicationType>Windows Store</ApplicationType>
<TargetPlatformIdentifier>Windows</TargetPlatformIdentifier>
</PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<PropertyGroup>
<GenerateManifest>true</GenerateManifest>
<EmbedManifest>true</EmbedManifest>
</PropertyGroup>
<!-- Source Files -->
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="ScratchClass.h" />
<ClInclude Include="HostClass.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="ScratchClass.cpp">
<DependentUpon>ScratchClass.idl</DependentUpon>
</ClCompile>
<ClCompile Include="HostClass.cpp">
<DependentUpon>HostClass.idl</DependentUpon>
</ClCompile>
<ClCompile Include="main.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Midl Include="IScratchInterface.idl" />
<Midl Include="ScratchClass.idl" />
<Midl Include="HostClass.idl" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<!-- Dependencies -->
<ItemGroup>
<ProjectReference Include="$(SolutionDir)src\types\lib\types.vcxproj" />
</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" />
<!-- These have to come after post.props because the Cpp common targets will inexplicably overwrite them. -->
<ItemDefinitionGroup>
<ClCompile>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<!-- <Import Project="$(OpenConsoleDir)\build\rules\GenerateSxsManifestsFromWinmds.targets" /> -->
</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,107 @@
#include "pch.h"
#include <winrt/ScratchWinRTServer.h>
#include "ScratchClass.h"
#include "HostClass.h"
using namespace winrt;
// using namespace winrt::Windows::Foundation;
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;
};
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] };
// Obviously this isn't right but it's good enough
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,
// CLSCTX_LOCAL_SERVER | CLSCTX_ENABLE_AAA,
// CLSCTX_LOCAL_SERVER | CLSCTX_ACTIVATE_AAA_AS_IU,
// CLSCTX_LOCAL_SERVER | CLSCTX_FROM_DEFAULT_CONTEXT,
// CLSCTX_LOCAL_SERVER | CLSCTX_ENABLE_CLOAKING,
REGCLS_MULTIPLEUSE,
&registrationHostClass));
printf("%d\n", registrationHostClass);
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [] { return dtored; });
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,43 @@
#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"
// 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 <Unknwn.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Media.h>
#include <windows.ui.xaml.media.dxinterop.h>
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#include "til.h"
#include <dxgi.h>
#include <dxgi1_2.h>
#include <dxgi1_3.h>
#include <d3d11.h>
#include <d2d1.h>
#include <d2d1_1.h>
#include <d2d1_2.h>
#include <d2d1_3.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <dwrite_1.h>
#include <dwrite_2.h>
#include <dwrite_3.h>

View File

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

View File

@@ -2,52 +2,9 @@
// Licensed under the MIT license.
#include <windows.h>
#include <stdio.h>
// This wmain exists for help in writing scratch programs while debugging.
int __cdecl wmain(int /*argc*/, WCHAR* /*argv[]*/)
{
unsigned char buf[256];
DWORD len;
int i;
auto in = GetStdHandle(STD_INPUT_HANDLE);
auto out = GetStdHandle(STD_OUTPUT_HANDLE);
const wchar_t alpha = L'\u03b1';
DWORD mode;
GetConsoleMode(in, &mode);
//mode &= ~ENABLE_LINE_INPUT;
SetConsoleMode(in, mode);
SetConsoleCP(932);
SetConsoleOutputCP(437);
printf("Input CP %d\n", GetConsoleCP());
printf("Output CP %d\n", GetConsoleOutputCP());
GetConsoleMode(in, &mode);
printf("Input Mode %02x\n", mode);
GetConsoleMode(out, &mode);
printf("Output Mode %02x\n", mode);
CONSOLE_FONT_INFOEX font = { 0 };
font.cbSize = sizeof(font);
BOOL retval = GetCurrentConsoleFontEx(out, FALSE, &font);
DWORD err = GetLastError();
retval;
err;
//mode &= ~ENABLE_LINE_INPUT;
//SetConsoleMode(in, mode);
while (true)
{
ReadFile(in, buf, sizeof(buf), &len, NULL);
for (i = 0; i < (int)len; i++)
printf("%02x ", buf[i]);
printf("\n");
}
return 0;
}