Compare commits
62 Commits
v0.1.1361.
...
dev/duhowe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00db8704b6 | ||
|
|
71e19cd825 | ||
|
|
dadd74c3c6 | ||
|
|
1c16b2c06b | ||
|
|
c3e32eb1ca | ||
|
|
e52170e2cf | ||
|
|
3d7160d731 | ||
|
|
d24d647c0d | ||
|
|
e2b5fecd48 | ||
|
|
097f7d32a6 | ||
|
|
2f88c46350 | ||
|
|
8baba4b46c | ||
|
|
cfc72cee5d | ||
|
|
9ad2544033 | ||
|
|
5f938a0465 | ||
|
|
4c47631bf4 | ||
|
|
cc30475955 | ||
|
|
80f107965d | ||
|
|
42e87ed3e3 | ||
|
|
40b557a4b6 | ||
|
|
0f62ec81d8 | ||
|
|
ce0eaab9ac | ||
|
|
1c50968333 | ||
|
|
efd69990c6 | ||
|
|
2fdcb679ab | ||
|
|
1191a59681 | ||
|
|
8dab297bd1 | ||
|
|
3f95d58805 | ||
|
|
2d4eca7f4f | ||
|
|
547cba968c | ||
|
|
798912c2f4 | ||
|
|
06a5583c86 | ||
|
|
82e75ce3e2 | ||
|
|
5ec7c0325e | ||
|
|
bbbd3e0323 | ||
|
|
1d9cdb3d31 | ||
|
|
8c3af2d066 | ||
|
|
e9a3d16286 | ||
|
|
83b139596f | ||
|
|
6a79025027 | ||
|
|
20359d40e4 | ||
|
|
6c7dfd2ce4 | ||
|
|
080843f826 | ||
|
|
db637021fd | ||
|
|
8da6737d64 | ||
|
|
fd2fb07bcf | ||
|
|
68d0c23246 | ||
|
|
29e380824f | ||
|
|
acabbe0459 | ||
|
|
461c8b53fa | ||
|
|
dd9bc6ee45 | ||
|
|
0060614173 | ||
|
|
37ea2dce48 | ||
|
|
9f4ad6d1ce | ||
|
|
a0ebd2ed1b | ||
|
|
67f68ebf62 | ||
|
|
41a6d8ed3a | ||
|
|
7533b31cbd | ||
|
|
73ad742c12 | ||
|
|
fd98145af2 | ||
|
|
2b41fad198 | ||
|
|
251505b96b |
13
.editorconfig
Normal file
@@ -0,0 +1,13 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[{*.cpp,*.c,*.hpp,*.h,*.cs}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
1
.gitignore
vendored
@@ -18,7 +18,6 @@
|
||||
x64/
|
||||
x86/
|
||||
ARM64/
|
||||
build/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
|
||||
@@ -29,8 +29,6 @@
|
||||
"Microsoft.VisualStudio.Component.VC.v141.ARM64",
|
||||
"Microsoft.VisualStudio.ComponentGroup.UWP.VC",
|
||||
"Microsoft.VisualStudio.ComponentGroup.UWP.VC.v141",
|
||||
"Microsoft.VisualStudio.Component.UWP.VC.ARM64",
|
||||
"Microsoft.VisualStudio.Component.VC.v141.ATL",
|
||||
"Microsoft.VisualStudio.Component.VC.v141.ATL.ARM64"
|
||||
"Microsoft.VisualStudio.Component.UWP.VC.ARM64"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Host.Tests.Unit", "src\host
|
||||
{06EC74CB-9A12-429C-B551-8562EC954747} = {06EC74CB-9A12-429C-B551-8562EC954747}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TextBuffer.UnitTests", "src\buffer\out\ut_textbuffer\TextBuffer.UnitTests.vcxproj", "{531C23E7-4B76-4C08-8BBD-04164CB628C9}"
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TextBuffer.Unit.Tests", "src\buffer\out\ut_textbuffer\TextBuffer.Unit.Tests.vcxproj", "{531C23E7-4B76-4C08-8BBD-04164CB628C9}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{0CF235BD-2DA0-407E-90EE-C467E8BBC714} = {0CF235BD-2DA0-407E-90EE-C467E8BBC714}
|
||||
EndProjectSection
|
||||
@@ -228,6 +228,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Buffer", "Buffer", "{1E4A06
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{89CDCC5C-9F53-4054-97A4-639D99F169CD}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Types.Unit.Tests", "src\types\ut_types\Types.Unit.Tests.vcxproj", "{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
AuditMode|ARM64 = AuditMode|ARM64
|
||||
@@ -1099,6 +1101,24 @@ Global
|
||||
{EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Release|x64.Build.0 = Release|x64
|
||||
{EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Release|x86.ActiveCfg = Release|Win32
|
||||
{EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Release|x86.Build.0 = Release|Win32
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|x64.ActiveCfg = AuditMode|x64
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|x64.Build.0 = AuditMode|x64
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|x86.ActiveCfg = AuditMode|Win32
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|x86.Build.0 = AuditMode|Win32
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|x64.Build.0 = Debug|x64
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|x86.Build.0 = Debug|Win32
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|x64.ActiveCfg = Release|x64
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|x64.Build.0 = Release|x64
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|x86.ActiveCfg = Release|Win32
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -1157,6 +1177,7 @@ Global
|
||||
{F1995847-4AE5-479A-BBAF-382E51A63532} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
|
||||
{05500DEF-2294-41E3-AF9A-24E580B82836} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
|
||||
{1E4A062E-293B-4817-B20D-BF16B979E350} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
|
||||
{34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271}
|
||||
|
||||
24
README.md
@@ -63,7 +63,7 @@ Otherwise, you'll need to wait until Mid-June for an official preview build to d
|
||||
|
||||
## I built and ran the new Terminal, but I just get a blank window app!
|
||||
|
||||
Make sure your are building for your computer's architecture. If your box has a 64-bit Windows change your Solution Platform to x64.
|
||||
Make sure you are building for your computer's architecture. If your box has a 64-bit Windows change your Solution Platform to x64.
|
||||
To check your OS architecture go to Settings -> System -> About (or Win+X -> System) and under `Device specifications` check for the `System type`
|
||||
|
||||
## I built and ran the new Terminal, but it looks just like the old console! What gives?
|
||||
@@ -82,14 +82,10 @@ Secondly, try pressing <kbd>Ctrl</kbd> + <kbd>T</kbd>. The tabs are hidden when
|
||||
## Prerequisites
|
||||
|
||||
* You must be running Windows 1903 (build >= 10.0.18362.0) or above in order to run Windows Terminal
|
||||
- **As of May 2019** this build is only available through Windows Insider Program. You may register and configure Insider Program through your device's system settings.
|
||||
* You must have the [1903 SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk) (build 10.0.18362.0) installed
|
||||
* You must have at least [VS 2017](https://visualstudio.microsoft.com/downloads/) installed
|
||||
* You must install the following Workloads via the VS Installer:
|
||||
* You must have at least [VS 2017](https://visualstudio.microsoft.com/downloads/) installed.
|
||||
* You must install the following Workloads via the VS Installer. If you're running VS 2019, opening the solution will [prompt you to install missing components automatically](https://devblogs.microsoft.com/setup/configure-visual-studio-across-your-organization-with-vsconfig/).
|
||||
- Desktop Development with C++
|
||||
- If you're running VS2019, you'll also need to install the following Individual Components:
|
||||
- MSVC v141 - VS 2017 C++ (x86 and x64) build tools
|
||||
- C++ ATL for v141 build tools (x86 and x64)
|
||||
- Universal Windows Platform Development
|
||||
- Also install the following Individual Component:
|
||||
- C++ (v141) Universal Windows Platform Tools
|
||||
@@ -112,7 +108,7 @@ We ask that **before you start work on a feature that you would like to contribu
|
||||
|
||||
## Documentation
|
||||
|
||||
All documentation is located in the `./docs` folder. If you would like to contribute to the documentation, please submit a pull request.
|
||||
All documentation is located in the `./doc` folder. If you would like to contribute to the documentation, please submit a pull request.
|
||||
|
||||
## Communicating with the Team
|
||||
|
||||
@@ -140,7 +136,17 @@ This repository uses [git submodules](https://git-scm.com/book/en/v2/Git-Tools-S
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
OpenConsole.sln may be built from within Visual Studio or from the command-line using MSBuild. To build from the command line:
|
||||
OpenConsole.sln may be built from within Visual Studio or from the command-line using MSBuild. To build from the command line, find your shell below.
|
||||
|
||||
### PowerShell
|
||||
|
||||
```powershell
|
||||
Import-Module .\tools\OpenConsole.psm1
|
||||
Set-MsBuildDevEnvironment
|
||||
Invoke-OpenConsoleBuild
|
||||
```
|
||||
|
||||
### CMD
|
||||
|
||||
```shell
|
||||
.\tools\razzle.cmd
|
||||
|
||||
@@ -19,6 +19,10 @@ pr:
|
||||
name: 0.0.$(Date:yyMM).$(Date:dd)$(Rev:rr)
|
||||
|
||||
jobs:
|
||||
- template: ./templates/build-console-audit-job.yml
|
||||
parameters:
|
||||
platform: x64
|
||||
|
||||
- template: ./templates/build-console-ci.yml
|
||||
parameters:
|
||||
platform: x64
|
||||
@@ -26,3 +30,7 @@ jobs:
|
||||
- template: ./templates/build-console-ci.yml
|
||||
parameters:
|
||||
platform: x86
|
||||
|
||||
- template: ./templates/build-console-ci.yml
|
||||
parameters:
|
||||
platform: ARM64
|
||||
|
||||
@@ -16,6 +16,10 @@ variables:
|
||||
name: 'Terminal_$(date:yyMM).$(date:dd)$(rev:rrr)'
|
||||
|
||||
jobs:
|
||||
- template: ./templates/build-console-audit-job.yml
|
||||
parameters:
|
||||
platform: x64
|
||||
|
||||
- template: ./templates/build-console-int.yml
|
||||
parameters:
|
||||
platform: x64
|
||||
|
||||
53
build/pipelines/templates/build-console-audit-job.yml
Normal file
@@ -0,0 +1,53 @@
|
||||
parameters:
|
||||
platform: ''
|
||||
additionalBuildArguments: ''
|
||||
|
||||
jobs:
|
||||
- job: Build${{ parameters.platform }}AuditMode
|
||||
displayName: Static Analysis Build ${{ parameters.platform }}
|
||||
variables:
|
||||
BuildConfiguration: AuditMode
|
||||
BuildPlatform: ${{ parameters.platform }}
|
||||
pool: { vmImage: vs2017-win2016 }
|
||||
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: true
|
||||
clean: true
|
||||
|
||||
- task: NuGetToolInstaller@0
|
||||
displayName: Ensure NuGet 4.8.1
|
||||
inputs:
|
||||
versionSpec: 4.8.1
|
||||
|
||||
# In the Microsoft Azure DevOps tenant, NuGetCommand is ambiguous.
|
||||
# This should be `task: NuGetCommand@2`
|
||||
- task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2
|
||||
displayName: Restore NuGet packages
|
||||
inputs:
|
||||
command: restore
|
||||
feedsToUse: config
|
||||
configPath: NuGet.config
|
||||
restoreSolution: OpenConsole.sln
|
||||
restoreDirectory: '$(Build.SourcesDirectory)\packages'
|
||||
|
||||
- task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2
|
||||
displayName: 'NuGet restore packages for CI'
|
||||
inputs:
|
||||
command: restore
|
||||
restoreSolution: build/.nuget/packages.config
|
||||
feedsToUse: config
|
||||
externalFeedCredentials: 'TAEF NuGet Feed'
|
||||
nugetConfigPath: build/config/NuGet.config
|
||||
restoreDirectory: '$(Build.SourcesDirectory)/packages'
|
||||
|
||||
- task: VSBuild@1
|
||||
displayName: 'Build solution **\OpenConsole.sln'
|
||||
inputs:
|
||||
solution: '**\OpenConsole.sln'
|
||||
vsVersion: 15.0
|
||||
platform: '$(BuildPlatform)'
|
||||
configuration: '$(BuildConfiguration)'
|
||||
msbuildArgs: ${{ parameters.additionalBuildArguments }}
|
||||
clean: true
|
||||
maximumCpuCount: true
|
||||
@@ -4,8 +4,8 @@ parameters:
|
||||
additionalBuildArguments: ''
|
||||
|
||||
jobs:
|
||||
- job: Build${{ parameters.platform }}
|
||||
displayName: Build ${{ parameters.platform }}
|
||||
- job: Build${{ parameters.platform }}${{ parameters.configuration }}
|
||||
displayName: Build ${{ parameters.platform }} ${{ parameters.configuration }}
|
||||
variables:
|
||||
BuildConfiguration: ${{ parameters.configuration }}
|
||||
BuildPlatform: ${{ parameters.platform }}
|
||||
|
||||
@@ -4,8 +4,8 @@ parameters:
|
||||
additionalBuildArguments: ''
|
||||
|
||||
jobs:
|
||||
- job: Build${{ parameters.platform }}
|
||||
displayName: Build ${{ parameters.platform }}
|
||||
- job: Build${{ parameters.platform }}${{ parameters.configuration }}
|
||||
displayName: Build ${{ parameters.platform }} ${{ parameters.configuration }}
|
||||
variables:
|
||||
BuildConfiguration: ${{ parameters.configuration }}
|
||||
BuildPlatform: ${{ parameters.platform }}
|
||||
|
||||
@@ -6,16 +6,18 @@ jobs:
|
||||
displayName: Sign and Deploy for ${{ parameters.configuration }}
|
||||
|
||||
dependsOn:
|
||||
- Buildx64
|
||||
- Buildx86
|
||||
- Buildarm64
|
||||
- Buildx64AuditMode
|
||||
- Buildx64Release
|
||||
- Buildx86Release
|
||||
- Buildarm64Release
|
||||
condition: |
|
||||
and
|
||||
(
|
||||
in(dependencies.Buildx64.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
|
||||
in(dependencies.Buildx86.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
|
||||
in(dependencies.Buildarm64.result, 'Succeeded', 'SucceededWithIssues', 'Skipped')
|
||||
)
|
||||
in(dependencies.Buildx64AuditMode.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
|
||||
in(dependencies.Buildx64Release.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
|
||||
in(dependencies.Buildx86Release.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
|
||||
in(dependencies.Buildarm64Release.result, 'Succeeded', 'SucceededWithIssues', 'Skipped')
|
||||
)
|
||||
|
||||
variables:
|
||||
BuildConfiguration: ${{ parameters.configuration }}
|
||||
|
||||
@@ -586,6 +586,8 @@ typedef struct _CONSOLE_STATE_INFO {
|
||||
COLORREF DefaultForeground;
|
||||
COLORREF DefaultBackground;
|
||||
BOOL TerminalScrolling;
|
||||
|
||||
LPWSTR VersionString;
|
||||
/* END V2 CONSOLE_STATE_INFO */
|
||||
|
||||
} CONSOLE_STATE_INFO, *PCONSOLE_STATE_INFO;
|
||||
|
||||
89
doc/bot.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Issue/PR Management Bot Information
|
||||
|
||||
## Overview
|
||||
|
||||
The goal here is to help us automate, manage, and narrow down what we actually need to focus on in this repository.
|
||||
We'll be using tags, primarily, to help us understand what needs attention, what is sitting around and turning stale, etc.
|
||||
|
||||
### Quick-Guidance to Core Contributors
|
||||
1. Look at `Needs-Attention` as top priority
|
||||
1. Look at `Needs-Triage` during triage meetings to get a handle on what's new and sort it out
|
||||
1. Look at `Needs-Tag-Fix` when you have a few minutes to fix up things tagged impoperly
|
||||
1. Manually add `Needs-Author-Feedback` when there's something we need the author to follow up on and want attention if they return it or an auto-close for inactivity if it goes stale.
|
||||
|
||||
### Tagging/Process Details
|
||||
1. When new issues arrive, or when issues are not properly tagged... we'll mark them as `Needs-Triage` automatically.
|
||||
- The core contributor team will then come through and mark them up as appropriate. The goal is to have a tag that fits the `Product`, `Area`, and `Issue` category.
|
||||
- The `Needs-Triage` tag will be removed manually by the core contributor team during a triage meeting. (Exception, triage may also be done offline by senior team members during high-volume times.)
|
||||
- An issue may or may not be assigned to a contributor during triage. It is not necessary to assign someone to complete it.
|
||||
- We're not focusing on Projects yet.
|
||||
1. When core contributors need to ask something of the author, they will manually assign the `Needs-Author-Feedback` tag.
|
||||
- This tag will automatically drop off when the author comes back around and applies activity to the thread.
|
||||
- When this tag drops off, the bot will apply the `Needs-Attention` tag to get the core contribution team's attention again. If an author cares enough to be active, we will attempt to prioritize engaging with that author.
|
||||
- If the author doesn't come back around in a while, this will become a `No-Recent-Activity` tag.
|
||||
- If there's activity on an issue, the `No-Recent-Activity` tag will automatically drop.
|
||||
- If the `No-Recent-Activity` stays, the issue will be closed as stale.
|
||||
1. PRs will automatically get a `Needs-Author-Feedback` tag when reviewers wait on the author
|
||||
- This follows a similar decay strategy to issues.
|
||||
- If the author responds, the `Needs-Author-Feedback` tag will drop.
|
||||
- If there is no activity in a while, the `No-Recent-Activity` tag will appear.
|
||||
- If the `No-Recent-Activity` tag exists for a while, the PR will be closed as stale.
|
||||
1. Issues manually marked as `Resolution-Duplicate` will be closed shortly after activity stops
|
||||
1. Pull requests manually marked as `AutoMerge` will permit the bot to complete the PR and do cleanup when certain conditions are met. See details below.
|
||||
|
||||
## Rules
|
||||
|
||||
### Issue Management
|
||||
|
||||
#### Mark as Triage Needed
|
||||
- When an issue doesn't meet triage criteria, applies `Needs-Triage` tag. Right now, this is just when it's opened.
|
||||
|
||||
#### Author Has Responded
|
||||
- When an issue with `Needs-Author-Feedback` gets an author response, drops that tag in favor of `Needs-Attention` to flag core contributors to drop by.
|
||||
|
||||
#### Remove Activity Tag
|
||||
- When an issue with `No-Recent-Activity` has activity, drops this tag
|
||||
|
||||
#### Close Stale
|
||||
- Every hour, checks if there's an issue with `Needs-Author-Feedback` and `No-Recent-Activity` for 3 days. Closes as stale.
|
||||
|
||||
#### Tag as No Activity
|
||||
- Every hour, checks if there's been no activity in 4 days on an issue that `Needs-Author-Feedback`. If it's been 4 days, mark `No-Recent-Activity` as well.
|
||||
|
||||
#### Close Duplicate Issues
|
||||
- Every hour, checks if there's been a day since the last activity on an issue with tag `Resolution-Duplicate` and closes it if inactive.
|
||||
|
||||
#### Enforce tag system
|
||||
- When an issue is opened or labels are changed in any way, we will check if the tagging matches the system. If not, it will get `Needs-Tag-Fix`. The system is to have an `Area-`, `Issue-`, and `Product-` tag for all open things, and also a `Resolution-` for closed ones.
|
||||
- When the tags from appropriate categories are applied, it will auto-remove the `Needs-Tag-Fix` tag.
|
||||
|
||||
### PR Management
|
||||
|
||||
#### Codeflow Link *(Disabled)*
|
||||
- Bumps a PR with a link to the Microsoft CodeFlow tool for reviewing PRs
|
||||
|
||||
#### Marks PR as Awaiting Author Feedback
|
||||
- When a reviewer marks the PR as changes requested, the `Needs-Author-Feedback` tag will be applied
|
||||
|
||||
#### Removes Awaiting Author Feedback
|
||||
- When the PR author updates the pull request, comments on it, or responds to a review, the `Needs-Author-Feedback` tag is removed.
|
||||
|
||||
#### Removes No Recent Activity
|
||||
- When anyone touches the pull request, the `No-Recent-Activity` tag is removed.
|
||||
|
||||
#### Markup stale pull requests
|
||||
- Every hour, if a pull request `Needs-Author-Feedback` and hasn't been touched in 7 days, it will get the `No-Recent-Activity` tag.
|
||||
|
||||
#### Close stale pull requests
|
||||
- Every hour, if a pull request has `No-Recent-Activity` and hasn't been touched in a further 7 days, it will be closed.
|
||||
|
||||
#### Auto-Merge pull requests
|
||||
- When a pull request has the `AutoMerge` label...
|
||||
- If it has been at least 480 minutes and all the statuses pass, merge it in.
|
||||
- Will use Squash merge stratgy
|
||||
- Will attempt to delete branch after merge, if possible
|
||||
- Will automatically remove the `AutoMerge` label if changes are pushed by someone *without* Write Access.
|
||||
- More information on bot-logic that can be controlled with comments is [here](https://github.com/OfficeDev/office-ui-fabric-react/wiki/Advanced-auto-merge)
|
||||
|
||||
## Admin Panel
|
||||
[Here](https://fabric-cp.azurewebsites.net/bot/)
|
||||
@@ -1,5 +1,5 @@
|
||||
// EchoCon.cpp : Entry point for the EchoCon Pseudo-Consle sample application.
|
||||
// Copyright © 2018, Microsoft
|
||||
// EchoCon.cpp : Entry point for the EchoCon Pseudo-Console sample application.
|
||||
// Copyright © 2018, Microsoft
|
||||
|
||||
#include "stdafx.h"
|
||||
#include <Windows.h>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace GUIConsole.ConPTY.Native
|
||||
{
|
||||
/// <summary>
|
||||
/// PInvoke signatures for Win32's Console API.
|
||||
/// </summary>
|
||||
static class ConsoleApi
|
||||
{
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
internal static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate callback, bool add);
|
||||
internal delegate bool ConsoleEventDelegate(CtrlTypes ctrlType);
|
||||
|
||||
internal enum CtrlTypes : uint
|
||||
{
|
||||
CTRL_C_EVENT = 0,
|
||||
CTRL_BREAK_EVENT,
|
||||
CTRL_CLOSE_EVENT,
|
||||
CTRL_LOGOFF_EVENT = 5,
|
||||
CTRL_SHUTDOWN_EVENT
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace GUIConsole.ConPTY.Native
|
||||
{
|
||||
/// <summary>
|
||||
/// PInvoke signatures for Win32's Process API.
|
||||
/// </summary>
|
||||
static class ProcessApi
|
||||
{
|
||||
internal const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
internal struct STARTUPINFOEX
|
||||
{
|
||||
public STARTUPINFO StartupInfo;
|
||||
public IntPtr lpAttributeList;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
internal struct STARTUPINFO
|
||||
{
|
||||
public int cb;
|
||||
public string lpReserved;
|
||||
public string lpDesktop;
|
||||
public string lpTitle;
|
||||
public int dwX;
|
||||
public int dwY;
|
||||
public int dwXSize;
|
||||
public int dwYSize;
|
||||
public int dwXCountChars;
|
||||
public int dwYCountChars;
|
||||
public int dwFillAttribute;
|
||||
public int dwFlags;
|
||||
public short wShowWindow;
|
||||
public short cbReserved2;
|
||||
public IntPtr lpReserved2;
|
||||
public IntPtr hStdInput;
|
||||
public IntPtr hStdOutput;
|
||||
public IntPtr hStdError;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct PROCESS_INFORMATION
|
||||
{
|
||||
public IntPtr hProcess;
|
||||
public IntPtr hThread;
|
||||
public int dwProcessId;
|
||||
public int dwThreadId;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct SECURITY_ATTRIBUTES
|
||||
{
|
||||
public int nLength;
|
||||
public IntPtr lpSecurityDescriptor;
|
||||
public int bInheritHandle;
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool InitializeProcThreadAttributeList(
|
||||
IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool UpdateProcThreadAttribute(
|
||||
IntPtr lpAttributeList, uint dwFlags, IntPtr attribute, IntPtr lpValue,
|
||||
IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool CreateProcess(
|
||||
string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
|
||||
ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags,
|
||||
IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFOEX lpStartupInfo,
|
||||
out PROCESS_INFORMATION lpProcessInformation);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool DeleteProcThreadAttributeList(IntPtr lpAttributeList);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
internal static extern bool CloseHandle(IntPtr hObject);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace GUIConsole.ConPTY.Native
|
||||
{
|
||||
/// <summary>
|
||||
/// PInvoke signatures for Win32's PseudoConsole API.
|
||||
/// </summary>
|
||||
static class PseudoConsoleApi
|
||||
{
|
||||
internal const uint PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = 0x00020016;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct COORD
|
||||
{
|
||||
public short X;
|
||||
public short Y;
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
internal static extern int CreatePseudoConsole(COORD size, SafeFileHandle hInput, SafeFileHandle hOutput, uint dwFlags, out IntPtr phPC);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
internal static extern int ClosePseudoConsole(IntPtr hPC);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
internal static extern bool CreatePipe(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, IntPtr lpPipeAttributes, int nSize);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using static GUIConsole.ConPTY.Native.ProcessApi;
|
||||
|
||||
namespace GUIConsole.ConPTY.Processes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an instance of a process.
|
||||
/// </summary>
|
||||
internal sealed class Process : IDisposable
|
||||
{
|
||||
public Process(STARTUPINFOEX startupInfo, PROCESS_INFORMATION processInfo)
|
||||
{
|
||||
StartupInfo = startupInfo;
|
||||
ProcessInfo = processInfo;
|
||||
}
|
||||
|
||||
public STARTUPINFOEX StartupInfo { get; }
|
||||
public PROCESS_INFORMATION ProcessInfo { get; }
|
||||
|
||||
#region IDisposable Support
|
||||
|
||||
private bool disposedValue = false; // To detect redundant calls
|
||||
|
||||
void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// dispose managed state (managed objects).
|
||||
}
|
||||
|
||||
// dispose unmanaged state
|
||||
|
||||
// Free the attribute list
|
||||
if (StartupInfo.lpAttributeList != IntPtr.Zero)
|
||||
{
|
||||
DeleteProcThreadAttributeList(StartupInfo.lpAttributeList);
|
||||
Marshal.FreeHGlobal(StartupInfo.lpAttributeList);
|
||||
}
|
||||
|
||||
// Close process and thread handles
|
||||
if (ProcessInfo.hProcess != IntPtr.Zero)
|
||||
{
|
||||
CloseHandle(ProcessInfo.hProcess);
|
||||
}
|
||||
if (ProcessInfo.hThread != IntPtr.Zero)
|
||||
{
|
||||
CloseHandle(ProcessInfo.hThread);
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
~Process()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using static GUIConsole.ConPTY.Native.ProcessApi;
|
||||
|
||||
namespace GUIConsole.ConPTY.Processes
|
||||
{
|
||||
/// <summary>
|
||||
/// Support for starting and configuring processes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Possible to replace with managed code? The key is being able to provide the PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE attribute
|
||||
/// </remarks>
|
||||
static class ProcessFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Start and configure a process. The return value represents the process and should be disposed.
|
||||
/// </summary>
|
||||
internal static Process Start(string command, IntPtr attributes, IntPtr hPC)
|
||||
{
|
||||
var startupInfo = ConfigureProcessThread(hPC, attributes);
|
||||
var processInfo = RunProcess(ref startupInfo, command);
|
||||
return new Process(startupInfo, processInfo);
|
||||
}
|
||||
|
||||
private static STARTUPINFOEX ConfigureProcessThread(IntPtr hPC, IntPtr attributes)
|
||||
{
|
||||
// this method implements the behavior described in https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session#preparing-for-creation-of-the-child-process
|
||||
|
||||
var lpSize = IntPtr.Zero;
|
||||
var success = InitializeProcThreadAttributeList(
|
||||
lpAttributeList: IntPtr.Zero,
|
||||
dwAttributeCount: 1,
|
||||
dwFlags: 0,
|
||||
lpSize: ref lpSize
|
||||
);
|
||||
if (success || lpSize == IntPtr.Zero) // we're not expecting `success` here, we just want to get the calculated lpSize
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not calculate the number of bytes for the attribute list.");
|
||||
}
|
||||
|
||||
var startupInfo = new STARTUPINFOEX();
|
||||
startupInfo.StartupInfo.cb = Marshal.SizeOf<STARTUPINFOEX>();
|
||||
startupInfo.lpAttributeList = Marshal.AllocHGlobal(lpSize);
|
||||
|
||||
success = InitializeProcThreadAttributeList(
|
||||
lpAttributeList: startupInfo.lpAttributeList,
|
||||
dwAttributeCount: 1,
|
||||
dwFlags: 0,
|
||||
lpSize: ref lpSize
|
||||
);
|
||||
if (!success)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not set up attribute list.");
|
||||
}
|
||||
|
||||
success = UpdateProcThreadAttribute(
|
||||
lpAttributeList: startupInfo.lpAttributeList,
|
||||
dwFlags: 0,
|
||||
attribute: attributes,
|
||||
lpValue: hPC,
|
||||
cbSize: (IntPtr)IntPtr.Size,
|
||||
lpPreviousValue: IntPtr.Zero,
|
||||
lpReturnSize: IntPtr.Zero
|
||||
);
|
||||
if (!success)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not set pseudoconsole thread attribute.");
|
||||
}
|
||||
|
||||
return startupInfo;
|
||||
}
|
||||
|
||||
private static PROCESS_INFORMATION RunProcess(ref STARTUPINFOEX sInfoEx, string commandLine)
|
||||
{
|
||||
int securityAttributeSize = Marshal.SizeOf<SECURITY_ATTRIBUTES>();
|
||||
var pSec = new SECURITY_ATTRIBUTES { nLength = securityAttributeSize };
|
||||
var tSec = new SECURITY_ATTRIBUTES { nLength = securityAttributeSize };
|
||||
var success = CreateProcess(
|
||||
lpApplicationName: null,
|
||||
lpCommandLine: commandLine,
|
||||
lpProcessAttributes: ref pSec,
|
||||
lpThreadAttributes: ref tSec,
|
||||
bInheritHandles: false,
|
||||
dwCreationFlags: EXTENDED_STARTUPINFO_PRESENT,
|
||||
lpEnvironment: IntPtr.Zero,
|
||||
lpCurrentDirectory: null,
|
||||
lpStartupInfo: ref sInfoEx,
|
||||
lpProcessInformation: out PROCESS_INFORMATION pInfo
|
||||
);
|
||||
if (!success)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(), "Could not create process.");
|
||||
}
|
||||
|
||||
return pInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
40
samples/ConPTY/GUIConsole/GUIConsole.ConPTY/PseudoConsole.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using static GUIConsole.ConPTY.Native.PseudoConsoleApi;
|
||||
|
||||
namespace GUIConsole.ConPTY
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility functions around the new Pseudo Console APIs.
|
||||
/// </summary>
|
||||
internal sealed class PseudoConsole : IDisposable
|
||||
{
|
||||
public static readonly IntPtr PseudoConsoleThreadAttribute = (IntPtr)PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE;
|
||||
|
||||
public IntPtr Handle { get; }
|
||||
|
||||
private PseudoConsole(IntPtr handle)
|
||||
{
|
||||
this.Handle = handle;
|
||||
}
|
||||
|
||||
internal static PseudoConsole Create(SafeFileHandle inputReadSide, SafeFileHandle outputWriteSide, int width, int height)
|
||||
{
|
||||
var createResult = CreatePseudoConsole(
|
||||
new COORD { X = (short)width, Y = (short)height },
|
||||
inputReadSide, outputWriteSide,
|
||||
0, out IntPtr hPC);
|
||||
if(createResult != 0)
|
||||
{
|
||||
throw new Win32Exception(createResult, "Could not create pseudo console.");
|
||||
}
|
||||
return new PseudoConsole(hPC);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ClosePseudoConsole(Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using static GUIConsole.ConPTY.Native.PseudoConsoleApi;
|
||||
|
||||
namespace GUIConsole.ConPTY
|
||||
{
|
||||
/// <summary>
|
||||
/// A pipe used to talk to the pseudoconsole, as described in:
|
||||
/// https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We'll have two instances of this class, one for input and one for output.
|
||||
/// </remarks>
|
||||
internal sealed class PseudoConsolePipe : IDisposable
|
||||
{
|
||||
public readonly SafeFileHandle ReadSide;
|
||||
public readonly SafeFileHandle WriteSide;
|
||||
|
||||
public PseudoConsolePipe()
|
||||
{
|
||||
if (!CreatePipe(out ReadSide, out WriteSide, IntPtr.Zero, 0))
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(), "failed to create pipe");
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
ReadSide?.Dispose();
|
||||
WriteSide?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
113
samples/ConPTY/GUIConsole/GUIConsole.ConPTY/Terminal.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using GUIConsole.ConPTY.Processes;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using static GUIConsole.ConPTY.Native.ConsoleApi;
|
||||
|
||||
namespace GUIConsole.ConPTY
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for managing communication with the underlying console, and communicating with its pseudoconsole.
|
||||
/// </summary>
|
||||
public sealed class Terminal
|
||||
{
|
||||
private const string ExitCommand = "exit\r";
|
||||
private const string CtrlC_Command = "\x3";
|
||||
private SafeFileHandle _consoleInputPipeWriteHandle;
|
||||
private StreamWriter _consoleInputWriter;
|
||||
|
||||
/// <summary>
|
||||
/// A stream of VT-100-enabled output from the console.
|
||||
/// </summary>
|
||||
public FileStream ConsoleOutStream { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fired once the console has been hooked up and is ready to receive input.
|
||||
/// </summary>
|
||||
public event EventHandler OutputReady;
|
||||
|
||||
public Terminal()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start the psuedoconsole and run the process as shown in
|
||||
/// https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session#creating-the-pseudoconsole
|
||||
/// </summary>
|
||||
/// <param name="command">the command to run, e.g. cmd.exe</param>
|
||||
/// <param name="consoleHeight">The height (in characters) to start the pseudoconsole with. Defaults to 80.</param>
|
||||
/// <param name="consoleWidth">The width (in characters) to start the pseudoconsole with. Defaults to 30.</param>
|
||||
public void Start(string command, int consoleWidth = 80, int consoleHeight = 30)
|
||||
{
|
||||
using (var inputPipe = new PseudoConsolePipe())
|
||||
using (var outputPipe = new PseudoConsolePipe())
|
||||
using (var pseudoConsole = PseudoConsole.Create(inputPipe.ReadSide, outputPipe.WriteSide, consoleWidth, consoleHeight))
|
||||
using (var process = ProcessFactory.Start(command, PseudoConsole.PseudoConsoleThreadAttribute, pseudoConsole.Handle))
|
||||
{
|
||||
// copy all pseudoconsole output to a FileStream and expose it to the rest of the app
|
||||
ConsoleOutStream = new FileStream(outputPipe.ReadSide, FileAccess.Read);
|
||||
OutputReady.Invoke(this, EventArgs.Empty);
|
||||
|
||||
// Store input pipe handle, and a writer for later reuse
|
||||
_consoleInputPipeWriteHandle = inputPipe.WriteSide;
|
||||
_consoleInputWriter = new StreamWriter(new FileStream(_consoleInputPipeWriteHandle, FileAccess.Write))
|
||||
{
|
||||
AutoFlush = true
|
||||
};
|
||||
|
||||
// free resources in case the console is ungracefully closed (e.g. by the 'x' in the window titlebar)
|
||||
OnClose(() => DisposeResources(process, pseudoConsole, outputPipe, inputPipe, _consoleInputWriter));
|
||||
|
||||
WaitForExit(process).WaitOne(Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the given string to the anonymous pipe that writes to the active pseudoconsole.
|
||||
/// </summary>
|
||||
/// <param name="input">A string of characters to write to the console. Supports VT-100 codes.</param>
|
||||
public void WriteToPseudoConsole(string input)
|
||||
{
|
||||
if (_consoleInputWriter == null)
|
||||
{
|
||||
throw new InvalidOperationException("There is no writer attached to a pseudoconsole. Have you called Start on this instance yet?");
|
||||
}
|
||||
_consoleInputWriter.Write(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an AutoResetEvent that signals when the process exits
|
||||
/// </summary>
|
||||
private static AutoResetEvent WaitForExit(Process process) =>
|
||||
new AutoResetEvent(false)
|
||||
{
|
||||
SafeWaitHandle = new SafeWaitHandle(process.ProcessInfo.hProcess, ownsHandle: false)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Set a callback for when the terminal is closed (e.g. via the "X" window decoration button).
|
||||
/// Intended for resource cleanup logic.
|
||||
/// </summary>
|
||||
private static void OnClose(Action handler)
|
||||
{
|
||||
SetConsoleCtrlHandler(eventType =>
|
||||
{
|
||||
if (eventType == CtrlTypes.CTRL_CLOSE_EVENT)
|
||||
{
|
||||
handler();
|
||||
}
|
||||
return false;
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void DisposeResources(params IDisposable[] disposables)
|
||||
{
|
||||
foreach (var disposable in disposables)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
6
samples/ConPTY/GUIConsole/GUIConsole.WPF/App.config
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
|
||||
</startup>
|
||||
</configuration>
|
||||
8
samples/ConPTY/GUIConsole/GUIConsole.WPF/App.xaml
Normal file
@@ -0,0 +1,8 @@
|
||||
<Application x:Class="GUIConsole.Wpf.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
15
samples/ConPTY/GUIConsole/GUIConsole.WPF/App.xaml.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace GUIConsole.Wpf
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
public App()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
105
samples/ConPTY/GUIConsole/GUIConsole.WPF/GUIConsole.WPF.csproj
Normal file
@@ -0,0 +1,105 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{FD2109FE-F78A-4E31-8317-11D1B66B69AF}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>GUIConsole.Wpf</RootNamespace>
|
||||
<AssemblyName>GUIConsole.Wpf</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Deterministic>true</Deterministic>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xaml">
|
||||
<RequiredTargetFramework>4.0</RequiredTargetFramework>
|
||||
</Reference>
|
||||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="App.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
<Page Include="MainWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Compile Include="App.xaml.cs">
|
||||
<DependentUpon>App.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="MainWindow.xaml.cs">
|
||||
<DependentUpon>MainWindow.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
</Compile>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<None Include="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\GUIConsole.ConPTY\GUIConsole.ConPTY.csproj">
|
||||
<Project>{96634c74-0c52-4381-9477-97e1d58fe5b5}</Project>
|
||||
<Name>GUIConsole.ConPTY</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
84
samples/ConPTY/GUIConsole/GUIConsole.WPF/MainWindow.xaml
Normal file
@@ -0,0 +1,84 @@
|
||||
<Window x:Class="GUIConsole.Wpf.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
|
||||
Title="MainWindow" Height="450" Width="800"
|
||||
Background="#C7000000"
|
||||
AllowsTransparency="True"
|
||||
WindowStyle="None"
|
||||
MouseDown="Window_MouseDown"
|
||||
BorderThickness="1"
|
||||
BorderBrush="LightSlateGray"
|
||||
|
||||
KeyDown="Window_KeyDown"
|
||||
Loaded="Window_Loaded">
|
||||
<Window.Resources>
|
||||
<Style x:Key="TitleBarButtonStyle" TargetType="{x:Type Button}">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="FontFamily" Value="Segoe MDL2 Assets"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="Height" Value="32"/>
|
||||
<Setter Property="Width" Value="46"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CloseButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource TitleBarButtonStyle}">
|
||||
<Setter Property="Content" Value=""/>
|
||||
<!--Remove the default Button template's Triggers, otherwise they'll override our trigger below.-->
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border Background="{TemplateBinding Background}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Button.Background" Value="Red"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Name="TitleBarTitle" Grid.Column="0" VerticalAlignment="Center" Padding="10 0" Foreground="White">
|
||||
GUIConsole
|
||||
</TextBlock>
|
||||
<StackPanel x:Name="TitleBarButtons" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Top">
|
||||
<Button x:Name="MinimizeButton"
|
||||
Click="MinimizeButton_Click"
|
||||
Content=""
|
||||
Style="{StaticResource TitleBarButtonStyle}"/>
|
||||
<Button x:Name="MaximizeRestoreButton"
|
||||
Click="MaximizeRestoreButton_Click"
|
||||
Content=""
|
||||
FontSize="12"
|
||||
Style="{StaticResource TitleBarButtonStyle}"/>
|
||||
<Button x:Name="CloseButton"
|
||||
Click="CloseButton_Click"
|
||||
FontSize="12"
|
||||
Style="{StaticResource CloseButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<ScrollViewer x:Name="TerminalHistoryViewer" Grid.Row="1" ScrollChanged="ScrollViewer_ScrollChanged">
|
||||
<TextBlock x:Name="TerminalHistoryBlock" FontFamily="Consolas" TextWrapping="Wrap" Foreground="White"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Window>
|
||||
121
samples/ConPTY/GUIConsole/GUIConsole.WPF/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using GUIConsole.ConPTY;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace GUIConsole.Wpf
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private Terminal _terminal;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Start up the console, and point it to cmd.exe.
|
||||
_terminal = new Terminal();
|
||||
Task.Run(() => _terminal.Start("powershell.exe"));
|
||||
_terminal.OutputReady += Terminal_OutputReady;
|
||||
}
|
||||
|
||||
private void Terminal_OutputReady(object sender, EventArgs e)
|
||||
{
|
||||
// Start a long-lived thread for the "read console" task, so that we don't use a standard thread pool thread.
|
||||
Task.Factory.StartNew(() => CopyConsoleToWindow(), TaskCreationOptions.LongRunning);
|
||||
|
||||
Dispatcher.Invoke(() => { TitleBarTitle.Text = "GUIConsole - powershell.exe"; });
|
||||
}
|
||||
|
||||
private void CopyConsoleToWindow()
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(_terminal.ConsoleOutStream))
|
||||
{
|
||||
// Read the console's output 1 character at a time
|
||||
int bytesRead;
|
||||
char[] buf = new char[1];
|
||||
while ((bytesRead = reader.ReadBlock(buf, 0, 1)) != 0)
|
||||
{
|
||||
// This is where you'd parse and tokenize the incoming VT100 text, most likely.
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
// ...and then you'd do something to render it.
|
||||
// For now, just emit raw VT100 to the primary TextBlock.
|
||||
TerminalHistoryBlock.Text += new string(buf.Take(bytesRead).ToArray());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (!e.Handled)
|
||||
{
|
||||
// This is where you'd take the pressed key, and convert it to a
|
||||
// VT100 code before sending it along. For now, we'll just send _something_.
|
||||
_terminal.WriteToPseudoConsole(e.Key.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private bool _autoScroll = true;
|
||||
private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
// User scrolled...
|
||||
if (e.ExtentHeightChange == 0)
|
||||
{
|
||||
//...down to the bottom. Re-engage autoscrolling.
|
||||
if (TerminalHistoryViewer.VerticalOffset == TerminalHistoryViewer.ScrollableHeight)
|
||||
{
|
||||
_autoScroll = true;
|
||||
}
|
||||
//...elsewhere. Disengage autoscrolling.
|
||||
else
|
||||
{
|
||||
_autoScroll = false;
|
||||
}
|
||||
|
||||
// Autoscrolling is enabled, and content caused scrolling:
|
||||
if (_autoScroll && e.ExtentHeightChange != 0)
|
||||
{
|
||||
TerminalHistoryViewer.ScrollToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ChangedButton == MouseButton.Left) { DragMove(); }
|
||||
}
|
||||
|
||||
private void MaximizeRestoreButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (WindowState == WindowState.Normal)
|
||||
{
|
||||
WindowState = WindowState.Maximized;
|
||||
MaximizeRestoreButton.Content = "\uE923";
|
||||
}
|
||||
else if (WindowState == WindowState.Maximized)
|
||||
{
|
||||
WindowState = WindowState.Normal;
|
||||
MaximizeRestoreButton.Content = "\uE922";
|
||||
}
|
||||
}
|
||||
|
||||
private void MinimizeButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WindowState = WindowState.Minimized;
|
||||
}
|
||||
|
||||
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("GUIConsole.Wpf")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("GUIConsole.Wpf")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2018")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
//In order to begin building localizable applications, set
|
||||
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
|
||||
//inside a <PropertyGroup>. For example, if you are using US english
|
||||
//in your source files, set the <UICulture> to en-US. Then uncomment
|
||||
//the NeutralResourceLanguage attribute below. Update the "en-US" in
|
||||
//the line below to match the UICulture setting in the project file.
|
||||
|
||||
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
|
||||
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
63
samples/ConPTY/GUIConsole/GUIConsole.WPF/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,63 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace GUIConsole.Wpf.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GUIConsole.Wpf.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
26
samples/ConPTY/GUIConsole/GUIConsole.WPF/Properties/Settings.Designer.cs
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace GUIConsole.Wpf.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
</SettingsFile>
|
||||
61
samples/ConPTY/GUIConsole/GUIConsole.sln
Normal file
@@ -0,0 +1,61 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26124.0
|
||||
MinimumVisualStudioVersion = 15.0.26124.0
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GUIConsole.WPF", "GUIConsole.WPF\GUIConsole.WPF.csproj", "{FD2109FE-F78A-4E31-8317-11D1B66B69AF}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GUIConsole.ConPTY", "GUIConsole.ConPTY\GUIConsole.ConPTY.csproj", "{96634C74-0C52-4381-9477-97E1D58FE5B5}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|ARM = Debug|ARM
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|ARM = Release|ARM
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|x64.Build.0 = Release|Any CPU
|
||||
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{FD2109FE-F78A-4E31-8317-11D1B66B69AF}.Release|x86.Build.0 = Release|Any CPU
|
||||
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|x64.Build.0 = Release|Any CPU
|
||||
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{96634C74-0C52-4381-9477-97E1D58FE5B5}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {0066B3A2-194D-471B-A56D-E25BB5AC0EB4}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
9
samples/ConPTY/GUIConsole/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# GUIConsole
|
||||
|
||||
This is an example of what the skeleton of a custom WPF console might look like.
|
||||
|
||||
The `GUIConsole.WPF` project is a WPF application targeting .NET 4.6.1. It creates a single WPF `Window` that acts as the console, and keeps the underlying console visible.
|
||||
|
||||
The `GUIConsole.ConPTY` project is a .NET Standard 2.0 library that handles the creation of the console, and enables pseudoconsole behavior. `Terminal.cs` contains the publicly visible pieces that the WPF application will interact with. `Terminal.cs` exposes two things that allow reading from, and writing to, the console:
|
||||
* `ConsoleOutStream`, a `FileStream` hooked up to the pseudoconsole's output pipe. This will output VT100.
|
||||
* `WriteToPseudoConsole(string input)`, a method that will take the given string and write it to the pseudoconsole via its input pipe. This accepts VT100.
|
||||
@@ -1,4 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RuleSet Name="Console Rules" Description="These rules enforce static analysis on console code." ToolsVersion="15.0">
|
||||
|
||||
<Include Path="cppcorecheckrules.ruleset" Action="Default" />
|
||||
|
||||
<Rules AnalyzerId="Microsoft.Analyzers.NativeCodeAnalysis" RuleNamespace="Microsoft.Rules.Native">
|
||||
<Rule Id="C6001" Action="Error" />
|
||||
<Rule Id="C6011" Action="Error" />
|
||||
</Rules>
|
||||
|
||||
</RuleSet>
|
||||
|
||||
@@ -25,7 +25,7 @@ Author:
|
||||
|
||||
class InvalidCharInfoConversionException : public std::exception
|
||||
{
|
||||
const char* what() noexcept
|
||||
const char* what() const noexcept
|
||||
{
|
||||
return "Cannot convert to CHAR_INFO without explicit TextAttribute";
|
||||
}
|
||||
|
||||
@@ -238,8 +238,8 @@ void TextAttribute::SetDefaultBackground() noexcept
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns true if this attribute indicates it's foreground is the "default"
|
||||
// foreground. It's _rgbForeground will contain the actual value of the
|
||||
// - Returns true if this attribute indicates its foreground is the "default"
|
||||
// foreground. Its _rgbForeground will contain the actual value of the
|
||||
// default foreground. If the default colors are ever changed, this method
|
||||
// should be used to identify attributes with the default fg value, and
|
||||
// update them accordingly.
|
||||
@@ -253,8 +253,8 @@ bool TextAttribute::ForegroundIsDefault() const noexcept
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns true if this attribute indicates it's background is the "default"
|
||||
// background. It's _rgbBackground will contain the actual value of the
|
||||
// - Returns true if this attribute indicates its background is the "default"
|
||||
// background. Its _rgbBackground will contain the actual value of the
|
||||
// default background. If the default colors are ever changed, this method
|
||||
// should be used to identify attributes with the default bg value, and
|
||||
// update them accordingly.
|
||||
|
||||
@@ -32,7 +32,7 @@ void UnicodeStorage::StoreGlyph(const key_type key, const mapped_type& glyph)
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - erases key and it's associated data from the storage
|
||||
// - erases key and its associated data from the storage
|
||||
// Arguments:
|
||||
// - key - the key to remove
|
||||
void UnicodeStorage::Erase(const key_type key) noexcept
|
||||
|
||||
@@ -21,7 +21,6 @@ Abstract:
|
||||
#include "LibraryIncludes.h"
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: ALL_CPPCORECHECK_WARNINGS)
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
|
||||
#endif
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
<ProjectGuid>{531C23E7-4B76-4C08-8BBD-04164CB628C9}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>TextBufferUnitTests</RootNamespace>
|
||||
<ProjectName>TextBuffer.UnitTests</ProjectName>
|
||||
<TargetName>TextBuffer.UnitTests</TargetName>
|
||||
<ProjectName>TextBuffer.Unit.Tests</ProjectName>
|
||||
<TargetName>TextBuffer.Unit.Tests</TargetName>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
@@ -99,6 +99,14 @@
|
||||
<Content Include="Images\Wide310x150Logo.scale-150.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-200.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-400.png" />
|
||||
<Content Include="ProfileIcons\{0caa0dad-35be-5f56-a8ff-afceeeaa6101}.scale-100.png" />
|
||||
<Content Include="ProfileIcons\{0caa0dad-35be-5f56-a8ff-afceeeaa6101}.scale-200.png" />
|
||||
<Content Include="ProfileIcons\{574e775e-4f2a-5b96-ac1e-a2962a402336}.scale-100.png" />
|
||||
<Content Include="ProfileIcons\{574e775e-4f2a-5b96-ac1e-a2962a402336}.scale-200.png" />
|
||||
<Content Include="ProfileIcons\{61c54bbd-c2c6-5271-96e7-009a87ff44bf}.scale-100.png" />
|
||||
<Content Include="ProfileIcons\{61c54bbd-c2c6-5271-96e7-009a87ff44bf}.scale-200.png" />
|
||||
<Content Include="ProfileIcons\{9acb9455-ca41-5af7-950f-6bca1bc9722f}.scale-100.png" />
|
||||
<Content Include="ProfileIcons\{9acb9455-ca41-5af7-950f-6bca1bc9722f}.scale-200.png" />
|
||||
<PRIResource Include="Resources\en-US\Resources.resw" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 243 B |
|
After Width: | Height: | Size: 641 B |
|
After Width: | Height: | Size: 737 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 616 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 755 B |
|
After Width: | Height: | Size: 1.6 KiB |
@@ -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.
|
||||
-->
|
||||
@@ -123,6 +123,21 @@
|
||||
<data name="AppName" xml:space="preserve">
|
||||
<value>Windows Terminal (Preview)</value>
|
||||
</data>
|
||||
<data name="InitialJsonParseErrorText" xml:space="preserve">
|
||||
<value>Settings could not be loaded from file - temporarily using the default settings. Check for syntax errors, including trailing commas.</value>
|
||||
</data>
|
||||
<data name="InitialJsonParseErrorTitle" xml:space="preserve">
|
||||
<value>Failed to load settings</value>
|
||||
</data>
|
||||
<data name="Ok" xml:space="preserve">
|
||||
<value>Ok</value>
|
||||
</data>
|
||||
<data name="ReloadJsonParseErrorText" xml:space="preserve">
|
||||
<value>Settings could not be reloaded from file. Check for syntax errors, including trailing commas.</value>
|
||||
</data>
|
||||
<data name="ReloadJsonParseErrorTitle" xml:space="preserve">
|
||||
<value>Failed to reload settings</value>
|
||||
</data>
|
||||
<data name="AppDescriptionDev" xml:space="preserve">
|
||||
<value>The Windows Terminal, but Unofficial</value>
|
||||
</data>
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#include "XamlApplication.h"
|
||||
|
||||
#include "XamlApplication.g.cpp"
|
||||
|
||||
namespace xaml = ::winrt::Windows::UI::Xaml;
|
||||
|
||||
extern "C" {
|
||||
|
||||
@@ -6,9 +6,13 @@
|
||||
#include <shellapi.h>
|
||||
#include <filesystem>
|
||||
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
|
||||
#include <winrt/Windows.ApplicationModel.Resources.h>
|
||||
|
||||
#include "App.g.cpp"
|
||||
|
||||
using namespace winrt::Windows::ApplicationModel::DataTransfer;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Text;
|
||||
using namespace winrt::Windows::UI::Core;
|
||||
using namespace winrt::Windows::System;
|
||||
using namespace winrt::Microsoft::Terminal;
|
||||
@@ -42,7 +46,9 @@ namespace winrt::TerminalApp::implementation
|
||||
base_type(parentProvider),
|
||||
_settings{ },
|
||||
_tabs{ },
|
||||
_loadedInitialSettings{ false }
|
||||
_loadedInitialSettings{ false },
|
||||
_settingsLoadedResult{ S_OK },
|
||||
_dialogLock{}
|
||||
{
|
||||
// For your own sanity, it's better to do setup outside the ctor.
|
||||
// If you do any setup in the ctor that ends up throwing an exception,
|
||||
@@ -159,6 +165,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
IInspectable g = res.Lookup(key);
|
||||
winrt::Windows::UI::Xaml::Style style = g.try_as<winrt::Windows::UI::Xaml::Style>();
|
||||
_root.Style(style);
|
||||
_tabRow.Style(style);
|
||||
}
|
||||
|
||||
@@ -166,6 +173,68 @@ namespace winrt::TerminalApp::implementation
|
||||
_ApplyTheme(_settings->GlobalSettings().GetRequestedTheme());
|
||||
|
||||
_OpenNewTab(std::nullopt);
|
||||
|
||||
_root.Loaded({ this, &App::_OnLoaded });
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Show a ContentDialog with a single "Ok" button to dismiss. Looks up the
|
||||
// the title and text from our Resources using the provided keys.
|
||||
// - Only one dialog can be visible at a time. If another dialog is visible
|
||||
// when this is called, nothing happens.
|
||||
// Arguments:
|
||||
// - titleKey: The key to use to lookup the title text from our resources.
|
||||
// - contentKey: The key to use to lookup the content text from our resources.
|
||||
fire_and_forget App::_ShowOkDialog(const winrt::hstring& titleKey,
|
||||
const winrt::hstring& contentKey)
|
||||
{
|
||||
// DON'T release this lock in a wil::scope_exit. The scope_exit will get
|
||||
// called when we await, which is not what we want.
|
||||
std::unique_lock lock{ _dialogLock, std::try_to_lock };
|
||||
if (!lock)
|
||||
{
|
||||
// Another dialog is visible.
|
||||
return;
|
||||
}
|
||||
|
||||
auto resourceLoader = Windows::ApplicationModel::Resources::ResourceLoader::GetForCurrentView();
|
||||
auto title = resourceLoader.GetString(titleKey);
|
||||
auto message = resourceLoader.GetString(contentKey);
|
||||
auto buttonText = resourceLoader.GetString(L"Ok");
|
||||
|
||||
Controls::ContentDialog dialog;
|
||||
dialog.Title(winrt::box_value(title));
|
||||
dialog.Content(winrt::box_value(message));
|
||||
dialog.CloseButtonText(buttonText);
|
||||
|
||||
// IMPORTANT: Add the dialog to the _root UIElement before you show it,
|
||||
// so it knows how to attach to the XAML content.
|
||||
_root.Children().Append(dialog);
|
||||
|
||||
// Display the dialog.
|
||||
Controls::ContentDialogResult result = co_await dialog.ShowAsync(Controls::ContentDialogPlacement::Popup);
|
||||
|
||||
// After the dialog is dismissed, the dialog lock (held by `lock`) will
|
||||
// be released so another can be shown.
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Triggered when the application is fiished loading. If we failed to load
|
||||
// the settings, then this will display the error dialog. This is done
|
||||
// here instead of when loading the settings, because we need our UI to be
|
||||
// visible to display the dialog, and when we're loading the settings,
|
||||
// the UI might not be visible yet.
|
||||
// Arguments:
|
||||
// - <unused>
|
||||
void App::_OnLoaded(const IInspectable& /*sender*/,
|
||||
const RoutedEventArgs& /*eventArgs*/)
|
||||
{
|
||||
if (FAILED(_settingsLoadedResult))
|
||||
{
|
||||
const winrt::hstring titleKey = L"InitialJsonParseErrorTitle";
|
||||
const winrt::hstring textKey = L"InitialJsonParseErrorText";
|
||||
_ShowOkDialog(titleKey, textKey);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -215,11 +284,27 @@ namespace winrt::TerminalApp::implementation
|
||||
void App::_CreateNewTabFlyout()
|
||||
{
|
||||
auto newTabFlyout = Controls::MenuFlyout{};
|
||||
auto keyBindings = _settings->GetKeybindings();
|
||||
|
||||
const GUID defaultProfileGuid = _settings->GlobalSettings().GetDefaultProfile();
|
||||
for (int profileIndex = 0; profileIndex < _settings->GetProfiles().size(); profileIndex++)
|
||||
{
|
||||
const auto& profile = _settings->GetProfiles()[profileIndex];
|
||||
auto profileMenuItem = Controls::MenuFlyoutItem{};
|
||||
|
||||
// add the keyboard shortcuts for the first 9 profiles
|
||||
if (profileIndex < 9)
|
||||
{
|
||||
// enum value for ShortcutAction::NewTabProfileX; 0==NewTabProfile0
|
||||
auto profileKeyChord = keyBindings.GetKeyBinding(static_cast<ShortcutAction>(profileIndex + static_cast<int>(ShortcutAction::NewTabProfile0)));
|
||||
|
||||
// make sure we find one to display
|
||||
if (profileKeyChord)
|
||||
{
|
||||
_SetAcceleratorForMenuItem(profileMenuItem, profileKeyChord);
|
||||
}
|
||||
}
|
||||
|
||||
auto profileName = profile.GetName();
|
||||
winrt::hstring hName{ profileName };
|
||||
profileMenuItem.Text(hName);
|
||||
@@ -231,6 +316,12 @@ namespace winrt::TerminalApp::implementation
|
||||
profileMenuItem.Icon(_GetIconFromProfile(profile));
|
||||
}
|
||||
|
||||
if (profile.GetGuid() == defaultProfileGuid)
|
||||
{
|
||||
// Contrast the default profile with others in font weight.
|
||||
profileMenuItem.FontWeight(FontWeights::Bold());
|
||||
}
|
||||
|
||||
profileMenuItem.Click([this, profileIndex](auto&&, auto&&){
|
||||
this->_OpenNewTab({ profileIndex });
|
||||
});
|
||||
@@ -254,6 +345,12 @@ namespace winrt::TerminalApp::implementation
|
||||
settingsItem.Click({ this, &App::_SettingsButtonOnClick });
|
||||
newTabFlyout.Items().Append(settingsItem);
|
||||
|
||||
auto settingsKeyChord = keyBindings.GetKeyBinding(ShortcutAction::OpenSettings);
|
||||
if (settingsKeyChord)
|
||||
{
|
||||
_SetAcceleratorForMenuItem(settingsItem, settingsKeyChord);
|
||||
}
|
||||
|
||||
// Create the feedback button.
|
||||
auto feedbackFlyout = Controls::MenuFlyoutItem{};
|
||||
feedbackFlyout.Text(L"Feedback");
|
||||
@@ -333,6 +430,38 @@ namespace winrt::TerminalApp::implementation
|
||||
bindings.OpenSettings([this]() { _OpenSettings(); });
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempt to load the settings. If we fail for any reason, returns an error.
|
||||
// Arguments:
|
||||
// - saveOnLoad: If true, after loading the settings, we should re-write
|
||||
// them to the file, to make sure the schema is updated. See
|
||||
// `CascadiaSettings::LoadAll` for details.
|
||||
// Return Value:
|
||||
// - S_OK if we successfully parsed the settings, otherwise an appropriate HRESULT.
|
||||
[[nodiscard]]
|
||||
HRESULT App::_TryLoadSettings(const bool saveOnLoad) noexcept
|
||||
{
|
||||
HRESULT hr = E_FAIL;
|
||||
|
||||
try
|
||||
{
|
||||
auto newSettings = CascadiaSettings::LoadAll(saveOnLoad);
|
||||
_settings = std::move(newSettings);
|
||||
hr = S_OK;
|
||||
}
|
||||
catch (const winrt::hresult_error& e)
|
||||
{
|
||||
hr = e.code();
|
||||
LOG_HR(hr);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
hr = wil::ResultFromCaughtException();
|
||||
LOG_HR(hr);
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Initialized our settings. See CascadiaSettings for more details.
|
||||
// Additionally hooks up our callbacks for keybinding events to the
|
||||
@@ -342,7 +471,21 @@ namespace winrt::TerminalApp::implementation
|
||||
// happening during startup, it'll need to happen on a background thread.
|
||||
void App::LoadSettings()
|
||||
{
|
||||
_settings = CascadiaSettings::LoadAll();
|
||||
// Attempt to load the settings.
|
||||
// If it fails,
|
||||
// - use Default settings,
|
||||
// - don't persist them (LoadAll won't save them in this case).
|
||||
// - _settingsLoadedResult will be set to an error, indicating that
|
||||
// we should display the loading error.
|
||||
// * We can't display the error now, because we might not have a
|
||||
// UI yet. We'll display the error in _OnLoaded.
|
||||
_settingsLoadedResult = _TryLoadSettings(true);
|
||||
|
||||
if (FAILED(_settingsLoadedResult))
|
||||
{
|
||||
_settings = std::make_unique<CascadiaSettings>();
|
||||
_settings->CreateDefaults();
|
||||
}
|
||||
|
||||
_HookupKeyBindings(_settings->GetKeybindings());
|
||||
|
||||
@@ -399,19 +542,36 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
// Method Description:
|
||||
// - Reloads the settings from the profile.json.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void App::_ReloadSettings()
|
||||
{
|
||||
_settings = CascadiaSettings::LoadAll();
|
||||
// Attempt to load our settings.
|
||||
// If it fails,
|
||||
// - don't change the settings (and don't actually apply the new settings)
|
||||
// - don't persist them.
|
||||
// - display a loading error
|
||||
_settingsLoadedResult = _TryLoadSettings(false);
|
||||
|
||||
if (FAILED(_settingsLoadedResult))
|
||||
{
|
||||
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() {
|
||||
const winrt::hstring titleKey = L"ReloadJsonParseErrorTitle";
|
||||
const winrt::hstring textKey = L"ReloadJsonParseErrorText";
|
||||
_ShowOkDialog(titleKey, textKey);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Here, we successfully reloaded the settings, and created a new
|
||||
// TerminalSettings object.
|
||||
|
||||
// Re-wire the keybindings to their handlers, as we'll have created a
|
||||
// new AppKeyBindings object.
|
||||
_HookupKeyBindings(_settings->GetKeybindings());
|
||||
|
||||
auto profiles = _settings->GetProfiles();
|
||||
// Refresh UI elements
|
||||
|
||||
auto profiles = _settings->GetProfiles();
|
||||
for (auto &profile : profiles)
|
||||
{
|
||||
const GUID profileGuid = profile.GetGuid();
|
||||
@@ -690,7 +850,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// Negative values of `delta` will move the view up by one page, and positive values
|
||||
// will move the viewport down by one page.
|
||||
// Arguments:
|
||||
// - delta: The direction to move the view relative to the current viewport(it
|
||||
// - delta: The direction to move the view relative to the current viewport(it
|
||||
// is clamped between -1 and 1)
|
||||
void App::_ScrollPage(int delta)
|
||||
{
|
||||
@@ -853,18 +1013,26 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
uint32_t tabIndexFromControl = 0;
|
||||
_tabView.Items().IndexOf(tabViewItem, tabIndexFromControl);
|
||||
|
||||
if (tabIndexFromControl == _GetFocusedTabIndex())
|
||||
{
|
||||
_tabView.SelectedIndex((tabIndexFromControl > 0) ? tabIndexFromControl - 1 : 1);
|
||||
}
|
||||
auto focusedTabIndex = _GetFocusedTabIndex();
|
||||
|
||||
// Removing the tab from the collection will destroy its control and disconnect its connection.
|
||||
_tabs.erase(_tabs.begin() + tabIndexFromControl);
|
||||
_tabView.Items().RemoveAt(tabIndexFromControl);
|
||||
|
||||
// ensure tabs and focus is sync
|
||||
_tabView.SelectedIndex(tabIndexFromControl > 0 ? tabIndexFromControl - 1 : 0);
|
||||
if (tabIndexFromControl == focusedTabIndex)
|
||||
{
|
||||
if (focusedTabIndex >= _tabs.size())
|
||||
{
|
||||
focusedTabIndex = _tabs.size() - 1;
|
||||
}
|
||||
|
||||
if (focusedTabIndex < 0)
|
||||
{
|
||||
focusedTabIndex = 0;
|
||||
}
|
||||
|
||||
_SelectTab(focusedTabIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -899,6 +1067,40 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Method Description:
|
||||
// - Takes a MenuFlyoutItem and a corresponding KeyChord value and creates the accelerator for UI display.
|
||||
// Takes into account a special case for an error condition for a comma
|
||||
// Arguments:
|
||||
// - MenuFlyoutItem that will be displayed, and a KeyChord to map an accelerator
|
||||
void App::_SetAcceleratorForMenuItem(Windows::UI::Xaml::Controls::MenuFlyoutItem& menuItem, const winrt::Microsoft::Terminal::Settings::KeyChord& keyChord)
|
||||
{
|
||||
// work around https://github.com/microsoft/microsoft-ui-xaml/issues/708 in case of VK_OEM_COMMA
|
||||
if (keyChord.Vkey() != VK_OEM_COMMA)
|
||||
{
|
||||
// use the XAML shortcut to give us the automatic capabilities
|
||||
auto menuShortcut = Windows::UI::Xaml::Input::KeyboardAccelerator{};
|
||||
|
||||
// TODO: Modify this when https://github.com/microsoft/terminal/issues/877 is resolved
|
||||
menuShortcut.Key(static_cast<Windows::System::VirtualKey>(keyChord.Vkey()));
|
||||
|
||||
// inspect the modifiers from the KeyChord and set the flags int he XAML value
|
||||
auto modifiers = AppKeyBindings::ConvertVKModifiers(keyChord.Modifiers());
|
||||
|
||||
// add the modifiers to the shortcut
|
||||
menuShortcut.Modifiers(modifiers);
|
||||
|
||||
// add to the menu
|
||||
menuItem.KeyboardAccelerators().Append(menuShortcut);
|
||||
}
|
||||
else // we've got a comma, so need to just use the alternate method
|
||||
{
|
||||
// extract the modifier and key to a nice format
|
||||
auto overrideString = AppKeyBindings::FormatOverrideShortcutText(keyChord.Modifiers());
|
||||
menuItem.KeyboardAcceleratorTextOverride(overrideString + L" ,");
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------- 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.
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#include "App.g.h"
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
#include <wil/filesystem.h>
|
||||
|
||||
#include <winrt/Microsoft.Terminal.TerminalControl.h>
|
||||
|
||||
#include <winrt/Microsoft.UI.Xaml.Controls.h>
|
||||
@@ -64,14 +62,22 @@ namespace winrt::TerminalApp::implementation
|
||||
std::vector<std::shared_ptr<Tab>> _tabs;
|
||||
|
||||
std::unique_ptr<::TerminalApp::CascadiaSettings> _settings;
|
||||
std::unique_ptr<TerminalApp::AppKeyBindings> _keyBindings;
|
||||
|
||||
HRESULT _settingsLoadedResult;
|
||||
|
||||
bool _loadedInitialSettings;
|
||||
std::shared_mutex _dialogLock;
|
||||
|
||||
wil::unique_folder_change_reader_nothrow _reader;
|
||||
|
||||
void _Create();
|
||||
void _CreateNewTabFlyout();
|
||||
|
||||
fire_and_forget _ShowOkDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey);
|
||||
|
||||
[[nodiscard]]
|
||||
HRESULT _TryLoadSettings(const bool saveOnLoad) noexcept;
|
||||
void _LoadSettings();
|
||||
void _OpenSettings();
|
||||
|
||||
@@ -101,6 +107,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// MSFT:20641986: Add keybindings for New Window
|
||||
void _ScrollPage(int delta);
|
||||
|
||||
void _OnLoaded(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
|
||||
void _OnTabSelectionChanged(const IInspectable& sender, const Windows::UI::Xaml::Controls::SelectionChangedEventArgs& eventArgs);
|
||||
void _OnTabClosing(const IInspectable& sender, const Microsoft::UI::Xaml::Controls::TabViewTabClosingEventArgs& eventArgs);
|
||||
void _OnTabItemsChanged(const IInspectable& sender, const Windows::Foundation::Collections::IVectorChangedEventArgs& eventArgs);
|
||||
@@ -111,6 +118,7 @@ namespace winrt::TerminalApp::implementation
|
||||
void _ApplyTheme(const Windows::UI::Xaml::ElementTheme& newTheme);
|
||||
|
||||
static Windows::UI::Xaml::Controls::IconElement _GetIconFromProfile(const ::TerminalApp::Profile& profile);
|
||||
static void _SetAcceleratorForMenuItem(Windows::UI::Xaml::Controls::MenuFlyoutItem& menuItem, const winrt::Microsoft::Terminal::Settings::KeyChord& keyChord);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
|
||||
the MIT License. See LICENSE in the project root for license information. -->
|
||||
<MSMarkup:XamlApplication
|
||||
x:Class="TerminalApp.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
@@ -16,7 +18,6 @@
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<!-- Include the MUX Controls resources -->
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls"/>
|
||||
|
||||
<ResourceDictionary>
|
||||
|
||||
<!-- We're going to apply this style to the root Grid acting
|
||||
@@ -29,6 +30,20 @@
|
||||
<Setter Property="Background" Value="{ThemeResource ApplicationPageBackgroundThemeBrush}" />
|
||||
</Style>
|
||||
|
||||
<!-- Manually theme the CloseButton of a ContentDialog. We
|
||||
need to do this, because for whatever reason, if we show a
|
||||
ContentDialog when the app theme is opposite the system
|
||||
theme, the buttons will appear transparent. This only
|
||||
applies to the Close button of the dialog, since we're only
|
||||
using the Close button of the dialog. If we ever add other
|
||||
dialogs with more buttons, we'll probably want to make sure
|
||||
the buttons are styled differently. -->
|
||||
<Style TargetType="ContentDialog">
|
||||
<!-- the value `AccentButtonStyle` is taken straight
|
||||
from the ContentDialog source -->
|
||||
<Setter Target="CloseButtonStyle" Value="{StaticResource AccentButtonStyle}" />
|
||||
</Style>
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<!-- Define resources for Dark mode here -->
|
||||
@@ -39,8 +54,11 @@
|
||||
</ResourceDictionary>
|
||||
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
|
||||
|
||||
</MSMarkup:XamlApplication.Resources>
|
||||
</MSMarkup:XamlApplication>
|
||||
|
||||
@@ -1,158 +1,411 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "AppKeyBindings.h"
|
||||
|
||||
using namespace winrt::Microsoft::Terminal;
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
void AppKeyBindings::SetKeyBinding(TerminalApp::ShortcutAction const& action,
|
||||
Settings::KeyChord const& chord)
|
||||
{
|
||||
_keyShortcuts[chord] = action;
|
||||
}
|
||||
|
||||
bool AppKeyBindings::TryKeyChord(Settings::KeyChord const& kc)
|
||||
{
|
||||
const auto keyIter = _keyShortcuts.find(kc);
|
||||
if (keyIter != _keyShortcuts.end())
|
||||
{
|
||||
const auto action = keyIter->second;
|
||||
return _DoAction(action);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppKeyBindings::_DoAction(ShortcutAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case ShortcutAction::CopyText:
|
||||
_CopyTextHandlers();
|
||||
return true;
|
||||
case ShortcutAction::PasteText:
|
||||
_PasteTextHandlers();
|
||||
return true;
|
||||
case ShortcutAction::NewTab:
|
||||
_NewTabHandlers();
|
||||
return true;
|
||||
case ShortcutAction::OpenSettings:
|
||||
_OpenSettingsHandlers();
|
||||
return true;
|
||||
|
||||
case ShortcutAction::NewTabProfile0:
|
||||
_NewTabWithProfileHandlers(0);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile1:
|
||||
_NewTabWithProfileHandlers(1);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile2:
|
||||
_NewTabWithProfileHandlers(2);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile3:
|
||||
_NewTabWithProfileHandlers(3);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile4:
|
||||
_NewTabWithProfileHandlers(4);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile5:
|
||||
_NewTabWithProfileHandlers(5);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile6:
|
||||
_NewTabWithProfileHandlers(6);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile7:
|
||||
_NewTabWithProfileHandlers(7);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile8:
|
||||
_NewTabWithProfileHandlers(8);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile9:
|
||||
_NewTabWithProfileHandlers(9);
|
||||
return true;
|
||||
|
||||
case ShortcutAction::NewWindow:
|
||||
_NewWindowHandlers();
|
||||
return true;
|
||||
case ShortcutAction::CloseWindow:
|
||||
_CloseWindowHandlers();
|
||||
return true;
|
||||
case ShortcutAction::CloseTab:
|
||||
_CloseTabHandlers();
|
||||
return true;
|
||||
|
||||
case ShortcutAction::ScrollUp:
|
||||
_ScrollUpHandlers();
|
||||
return true;
|
||||
case ShortcutAction::ScrollDown:
|
||||
_ScrollDownHandlers();
|
||||
return true;
|
||||
case ShortcutAction::ScrollUpPage:
|
||||
_ScrollUpPageHandlers();
|
||||
return true;
|
||||
case ShortcutAction::ScrollDownPage:
|
||||
_ScrollDownPageHandlers();
|
||||
return true;
|
||||
case ShortcutAction::NextTab:
|
||||
_NextTabHandlers();
|
||||
return true;
|
||||
case ShortcutAction::PrevTab:
|
||||
_PrevTabHandlers();
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab0:
|
||||
_SwitchToTabHandlers(0);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab1:
|
||||
_SwitchToTabHandlers(1);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab2:
|
||||
_SwitchToTabHandlers(2);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab3:
|
||||
_SwitchToTabHandlers(3);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab4:
|
||||
_SwitchToTabHandlers(4);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab5:
|
||||
_SwitchToTabHandlers(5);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab6:
|
||||
_SwitchToTabHandlers(6);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab7:
|
||||
_SwitchToTabHandlers(7);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab8:
|
||||
_SwitchToTabHandlers(8);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab9:
|
||||
_SwitchToTabHandlers(9);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// -------------------------------- Events ---------------------------------
|
||||
DEFINE_EVENT(AppKeyBindings, CopyText, _CopyTextHandlers, TerminalApp::CopyTextEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, PasteText, _PasteTextHandlers, TerminalApp::PasteTextEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, NewTab, _NewTabHandlers, TerminalApp::NewTabEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, NewTabWithProfile, _NewTabWithProfileHandlers, TerminalApp::NewTabWithProfileEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, NewWindow, _NewWindowHandlers, TerminalApp::NewWindowEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, CloseWindow, _CloseWindowHandlers, TerminalApp::CloseWindowEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, CloseTab, _CloseTabHandlers, TerminalApp::CloseTabEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, SwitchToTab, _SwitchToTabHandlers, TerminalApp::SwitchToTabEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, NextTab, _NextTabHandlers, TerminalApp::NextTabEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, PrevTab, _PrevTabHandlers, TerminalApp::PrevTabEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, IncreaseFontSize, _IncreaseFontSizeHandlers, TerminalApp::IncreaseFontSizeEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, DecreaseFontSize, _DecreaseFontSizeHandlers, TerminalApp::DecreaseFontSizeEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, ScrollUp, _ScrollUpHandlers, TerminalApp::ScrollUpEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, ScrollDown, _ScrollDownHandlers, TerminalApp::ScrollDownEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, ScrollUpPage, _ScrollUpPageHandlers, TerminalApp::ScrollUpPageEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, ScrollDownPage, _ScrollDownPageHandlers, TerminalApp::ScrollDownPageEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, OpenSettings, _OpenSettingsHandlers, TerminalApp::OpenSettingsEventArgs);
|
||||
|
||||
|
||||
}
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "AppKeyBindings.h"
|
||||
#include "KeyChordSerialization.h"
|
||||
|
||||
#include "AppKeyBindings.g.cpp"
|
||||
|
||||
using namespace winrt::Microsoft::Terminal;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace winrt::Windows::Data::Json;
|
||||
|
||||
static constexpr std::wstring_view KEYS_KEY{ L"keys" };
|
||||
static constexpr std::wstring_view COMMAND_KEY{ L"command" };
|
||||
|
||||
static constexpr std::wstring_view COPYTEXT_KEY{ L"copy" };
|
||||
static constexpr std::wstring_view PASTETEXT_KEY{ L"paste" };
|
||||
static constexpr std::wstring_view NEWTAB_KEY{ L"newTab" };
|
||||
static constexpr std::wstring_view NEWTABWITHPROFILE0_KEY{ L"newTabProfile0" };
|
||||
static constexpr std::wstring_view NEWTABWITHPROFILE1_KEY{ L"newTabProfile1" };
|
||||
static constexpr std::wstring_view NEWTABWITHPROFILE2_KEY{ L"newTabProfile2" };
|
||||
static constexpr std::wstring_view NEWTABWITHPROFILE3_KEY{ L"newTabProfile3" };
|
||||
static constexpr std::wstring_view NEWTABWITHPROFILE4_KEY{ L"newTabProfile4" };
|
||||
static constexpr std::wstring_view NEWTABWITHPROFILE5_KEY{ L"newTabProfile5" };
|
||||
static constexpr std::wstring_view NEWTABWITHPROFILE6_KEY{ L"newTabProfile6" };
|
||||
static constexpr std::wstring_view NEWTABWITHPROFILE7_KEY{ L"newTabProfile7" };
|
||||
static constexpr std::wstring_view NEWTABWITHPROFILE8_KEY{ L"newTabProfile8" };
|
||||
static constexpr std::wstring_view NEWWINDOW_KEY{ L"newWindow" };
|
||||
static constexpr std::wstring_view CLOSEWINDOW_KEY{ L"closeWindow" };
|
||||
static constexpr std::wstring_view CLOSETAB_KEY{ L"closeTab" };
|
||||
static constexpr std::wstring_view SWITCHTOTAB_KEY{ L"switchToTab" };
|
||||
static constexpr std::wstring_view NEXTTAB_KEY{ L"nextTab" };
|
||||
static constexpr std::wstring_view PREVTAB_KEY{ L"prevTab" };
|
||||
static constexpr std::wstring_view INCREASEFONTSIZE_KEY{ L"increaseFontSize" };
|
||||
static constexpr std::wstring_view DECREASEFONTSIZE_KEY{ L"decreaseFontSize" };
|
||||
static constexpr std::wstring_view SCROLLUP_KEY{ L"scrollUp" };
|
||||
static constexpr std::wstring_view SCROLLDOWN_KEY{ L"scrollDown" };
|
||||
static constexpr std::wstring_view SCROLLUPPAGE_KEY{ L"scrollUpPage" };
|
||||
static constexpr std::wstring_view SCROLLDOWNPAGE_KEY{ L"scrollDownPage" };
|
||||
static constexpr std::wstring_view SWITCHTOTAB0_KEY{ L"switchToTab0" };
|
||||
static constexpr std::wstring_view SWITCHTOTAB1_KEY{ L"switchToTab1" };
|
||||
static constexpr std::wstring_view SWITCHTOTAB2_KEY{ L"switchToTab2" };
|
||||
static constexpr std::wstring_view SWITCHTOTAB3_KEY{ L"switchToTab3" };
|
||||
static constexpr std::wstring_view SWITCHTOTAB4_KEY{ L"switchToTab4" };
|
||||
static constexpr std::wstring_view SWITCHTOTAB5_KEY{ L"switchToTab5" };
|
||||
static constexpr std::wstring_view SWITCHTOTAB6_KEY{ L"switchToTab6" };
|
||||
static constexpr std::wstring_view SWITCHTOTAB7_KEY{ L"switchToTab7" };
|
||||
static constexpr std::wstring_view SWITCHTOTAB8_KEY{ L"switchToTab8" };
|
||||
static constexpr std::wstring_view OPENSETTINGS_KEY{ L"openSettings" };
|
||||
|
||||
// Specifically use a map here over an unordered_map. We want to be able to
|
||||
// iterate over these entries in-order when we're serializing the keybindings.
|
||||
static const std::map<std::wstring_view, ShortcutAction> commandNames {
|
||||
{ COPYTEXT_KEY, ShortcutAction::CopyText },
|
||||
{ PASTETEXT_KEY, ShortcutAction::PasteText },
|
||||
{ NEWTAB_KEY, ShortcutAction::NewTab },
|
||||
{ NEWTABWITHPROFILE0_KEY, ShortcutAction::NewTabProfile0 },
|
||||
{ NEWTABWITHPROFILE1_KEY, ShortcutAction::NewTabProfile1 },
|
||||
{ NEWTABWITHPROFILE2_KEY, ShortcutAction::NewTabProfile2 },
|
||||
{ NEWTABWITHPROFILE3_KEY, ShortcutAction::NewTabProfile3 },
|
||||
{ NEWTABWITHPROFILE4_KEY, ShortcutAction::NewTabProfile4 },
|
||||
{ NEWTABWITHPROFILE5_KEY, ShortcutAction::NewTabProfile5 },
|
||||
{ NEWTABWITHPROFILE6_KEY, ShortcutAction::NewTabProfile6 },
|
||||
{ NEWTABWITHPROFILE7_KEY, ShortcutAction::NewTabProfile7 },
|
||||
{ NEWTABWITHPROFILE8_KEY, ShortcutAction::NewTabProfile8 },
|
||||
{ NEWWINDOW_KEY, ShortcutAction::NewWindow },
|
||||
{ CLOSEWINDOW_KEY, ShortcutAction::CloseWindow },
|
||||
{ CLOSETAB_KEY, ShortcutAction::CloseTab },
|
||||
{ NEXTTAB_KEY, ShortcutAction::NextTab },
|
||||
{ PREVTAB_KEY, ShortcutAction::PrevTab },
|
||||
{ INCREASEFONTSIZE_KEY, ShortcutAction::IncreaseFontSize },
|
||||
{ DECREASEFONTSIZE_KEY, ShortcutAction::DecreaseFontSize },
|
||||
{ SCROLLUP_KEY, ShortcutAction::ScrollUp },
|
||||
{ SCROLLDOWN_KEY, ShortcutAction::ScrollDown },
|
||||
{ SCROLLUPPAGE_KEY, ShortcutAction::ScrollUpPage },
|
||||
{ SCROLLDOWNPAGE_KEY, ShortcutAction::ScrollDownPage },
|
||||
{ SWITCHTOTAB0_KEY, ShortcutAction::SwitchToTab0 },
|
||||
{ SWITCHTOTAB1_KEY, ShortcutAction::SwitchToTab1 },
|
||||
{ SWITCHTOTAB2_KEY, ShortcutAction::SwitchToTab2 },
|
||||
{ SWITCHTOTAB3_KEY, ShortcutAction::SwitchToTab3 },
|
||||
{ SWITCHTOTAB4_KEY, ShortcutAction::SwitchToTab4 },
|
||||
{ SWITCHTOTAB5_KEY, ShortcutAction::SwitchToTab5 },
|
||||
{ SWITCHTOTAB6_KEY, ShortcutAction::SwitchToTab6 },
|
||||
{ SWITCHTOTAB7_KEY, ShortcutAction::SwitchToTab7 },
|
||||
{ SWITCHTOTAB8_KEY, ShortcutAction::SwitchToTab8 },
|
||||
{ OPENSETTINGS_KEY, ShortcutAction::OpenSettings },
|
||||
};
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
void AppKeyBindings::SetKeyBinding(const TerminalApp::ShortcutAction& action,
|
||||
const Settings::KeyChord& chord)
|
||||
{
|
||||
_keyShortcuts[chord] = action;
|
||||
}
|
||||
|
||||
Microsoft::Terminal::Settings::KeyChord AppKeyBindings::GetKeyBinding(TerminalApp::ShortcutAction const& action)
|
||||
{
|
||||
for (auto& kv : _keyShortcuts)
|
||||
{
|
||||
if (kv.second == action) return kv.first;
|
||||
}
|
||||
return { nullptr };
|
||||
}
|
||||
|
||||
bool AppKeyBindings::TryKeyChord(const Settings::KeyChord& kc)
|
||||
{
|
||||
const auto keyIter = _keyShortcuts.find(kc);
|
||||
if (keyIter != _keyShortcuts.end())
|
||||
{
|
||||
const auto action = keyIter->second;
|
||||
return _DoAction(action);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AppKeyBindings::_DoAction(ShortcutAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case ShortcutAction::CopyText:
|
||||
_CopyTextHandlers();
|
||||
return true;
|
||||
case ShortcutAction::PasteText:
|
||||
_PasteTextHandlers();
|
||||
return true;
|
||||
case ShortcutAction::NewTab:
|
||||
_NewTabHandlers();
|
||||
return true;
|
||||
case ShortcutAction::OpenSettings:
|
||||
_OpenSettingsHandlers();
|
||||
return true;
|
||||
|
||||
case ShortcutAction::NewTabProfile0:
|
||||
_NewTabWithProfileHandlers(0);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile1:
|
||||
_NewTabWithProfileHandlers(1);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile2:
|
||||
_NewTabWithProfileHandlers(2);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile3:
|
||||
_NewTabWithProfileHandlers(3);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile4:
|
||||
_NewTabWithProfileHandlers(4);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile5:
|
||||
_NewTabWithProfileHandlers(5);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile6:
|
||||
_NewTabWithProfileHandlers(6);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile7:
|
||||
_NewTabWithProfileHandlers(7);
|
||||
return true;
|
||||
case ShortcutAction::NewTabProfile8:
|
||||
_NewTabWithProfileHandlers(8);
|
||||
return true;
|
||||
|
||||
case ShortcutAction::NewWindow:
|
||||
_NewWindowHandlers();
|
||||
return true;
|
||||
case ShortcutAction::CloseWindow:
|
||||
_CloseWindowHandlers();
|
||||
return true;
|
||||
case ShortcutAction::CloseTab:
|
||||
_CloseTabHandlers();
|
||||
return true;
|
||||
|
||||
case ShortcutAction::ScrollUp:
|
||||
_ScrollUpHandlers();
|
||||
return true;
|
||||
case ShortcutAction::ScrollDown:
|
||||
_ScrollDownHandlers();
|
||||
return true;
|
||||
case ShortcutAction::ScrollUpPage:
|
||||
_ScrollUpPageHandlers();
|
||||
return true;
|
||||
case ShortcutAction::ScrollDownPage:
|
||||
_ScrollDownPageHandlers();
|
||||
return true;
|
||||
|
||||
case ShortcutAction::NextTab:
|
||||
_NextTabHandlers();
|
||||
return true;
|
||||
case ShortcutAction::PrevTab:
|
||||
_PrevTabHandlers();
|
||||
return true;
|
||||
|
||||
case ShortcutAction::SwitchToTab0:
|
||||
_SwitchToTabHandlers(0);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab1:
|
||||
_SwitchToTabHandlers(1);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab2:
|
||||
_SwitchToTabHandlers(2);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab3:
|
||||
_SwitchToTabHandlers(3);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab4:
|
||||
_SwitchToTabHandlers(4);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab5:
|
||||
_SwitchToTabHandlers(5);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab6:
|
||||
_SwitchToTabHandlers(6);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab7:
|
||||
_SwitchToTabHandlers(7);
|
||||
return true;
|
||||
case ShortcutAction::SwitchToTab8:
|
||||
_SwitchToTabHandlers(8);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// -------------------------------- Events ---------------------------------
|
||||
DEFINE_EVENT(AppKeyBindings, CopyText, _CopyTextHandlers, TerminalApp::CopyTextEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, PasteText, _PasteTextHandlers, TerminalApp::PasteTextEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, NewTab, _NewTabHandlers, TerminalApp::NewTabEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, NewTabWithProfile, _NewTabWithProfileHandlers, TerminalApp::NewTabWithProfileEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, NewWindow, _NewWindowHandlers, TerminalApp::NewWindowEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, CloseWindow, _CloseWindowHandlers, TerminalApp::CloseWindowEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, CloseTab, _CloseTabHandlers, TerminalApp::CloseTabEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, SwitchToTab, _SwitchToTabHandlers, TerminalApp::SwitchToTabEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, NextTab, _NextTabHandlers, TerminalApp::NextTabEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, PrevTab, _PrevTabHandlers, TerminalApp::PrevTabEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, IncreaseFontSize, _IncreaseFontSizeHandlers, TerminalApp::IncreaseFontSizeEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, DecreaseFontSize, _DecreaseFontSizeHandlers, TerminalApp::DecreaseFontSizeEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, ScrollUp, _ScrollUpHandlers, TerminalApp::ScrollUpEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, ScrollDown, _ScrollDownHandlers, TerminalApp::ScrollDownEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, ScrollUpPage, _ScrollUpPageHandlers, TerminalApp::ScrollUpPageEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, ScrollDownPage, _ScrollDownPageHandlers, TerminalApp::ScrollDownPageEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, OpenSettings, _OpenSettingsHandlers, TerminalApp::OpenSettingsEventArgs);
|
||||
|
||||
// Method Description:
|
||||
// - Deserialize an AppKeyBindings from the key mappings that are in the
|
||||
// array `json`. The json array should contain an array of objects with
|
||||
// both a `command` string and a `keys` array, where `command` is one of
|
||||
// the names listed in `commandNames`, and `keys` is an array of
|
||||
// keypresses. Currently, the array should contain a single string, which
|
||||
// can be deserialized into a KeyChord.
|
||||
// Arguments:
|
||||
// - json: and array of JsonObject's to deserialize into our _keyShortcuts mapping.
|
||||
// Return Value:
|
||||
// - the newly constructed AppKeyBindings object.
|
||||
TerminalApp::AppKeyBindings AppKeyBindings::FromJson(const JsonArray& json)
|
||||
{
|
||||
TerminalApp::AppKeyBindings newBindings{};
|
||||
|
||||
for (const auto& value : json)
|
||||
{
|
||||
if (value.ValueType() == JsonValueType::Object)
|
||||
{
|
||||
JsonObject obj = value.GetObjectW();
|
||||
if (obj.HasKey(COMMAND_KEY) && obj.HasKey(KEYS_KEY))
|
||||
{
|
||||
const auto commandString = obj.GetNamedString(COMMAND_KEY);
|
||||
const auto keys = obj.GetNamedArray(KEYS_KEY);
|
||||
if (keys.Size() != 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const auto keyChordString = keys.GetAt(0).GetString();
|
||||
ShortcutAction action;
|
||||
|
||||
// Try matching the command to one we have
|
||||
auto found = commandNames.find(commandString);
|
||||
if (found != commandNames.end())
|
||||
{
|
||||
action = found->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try parsing the chord
|
||||
try
|
||||
{
|
||||
auto chord = KeyChordSerialization::FromString(keyChordString);
|
||||
newBindings.SetKeyBinding(action, chord);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return newBindings;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Small helper to insert a given KeyChord, ShortcutAction pair into the
|
||||
// given json array
|
||||
// Arguments:
|
||||
// - bindingsArray: The JsonArray to insert the object into.
|
||||
// - chord: The KeyChord to serailize and place in the json array
|
||||
// - actionName: the name of the ShortcutAction to use with this KeyChord
|
||||
static void _AddShortcutToJsonArray(const JsonArray& bindingsArray,
|
||||
const Settings::KeyChord& chord,
|
||||
const std::wstring_view& actionName)
|
||||
{
|
||||
const auto keyString = KeyChordSerialization::ToString(chord);
|
||||
if (keyString == L"")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
winrt::Windows::Data::Json::JsonObject jsonObject;
|
||||
winrt::Windows::Data::Json::JsonArray keysArray;
|
||||
keysArray.Append(JsonValue::CreateStringValue(keyString));
|
||||
jsonObject.Insert(KEYS_KEY, keysArray);
|
||||
jsonObject.Insert(COMMAND_KEY, JsonValue::CreateStringValue(actionName));
|
||||
|
||||
bindingsArray.Append(jsonObject);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Serialize this AppKeyBindings to a json array of objects. Each object
|
||||
// in the array represents a single keybinding, mapping a KeyChord to a
|
||||
// ShortcutAction.
|
||||
// Return Value:
|
||||
// - a JsonArray which is an equivalent serialization of this object.
|
||||
Windows::Data::Json::JsonArray AppKeyBindings::ToJson()
|
||||
{
|
||||
winrt::Windows::Data::Json::JsonArray bindingsArray;
|
||||
|
||||
// Iterate over all the possible actions in the names list, and see if
|
||||
// it has a binding.
|
||||
for (auto& actionName : commandNames)
|
||||
{
|
||||
const auto searchedForName = actionName.first;
|
||||
const auto searchedForAction = actionName.second;
|
||||
for (const auto& kv : _keyShortcuts)
|
||||
{
|
||||
const auto chord = kv.first;
|
||||
const auto command = kv.second;
|
||||
if (command == searchedForAction)
|
||||
{
|
||||
_AddShortcutToJsonArray(bindingsArray, chord, searchedForName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bindingsArray;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Takes the KeyModifier flags from Terminal and maps them to the WinRT types which are used by XAML
|
||||
// Return Value:
|
||||
// - a Windows::System::VirtualKeyModifiers object with the flags of which modifiers used.
|
||||
Windows::System::VirtualKeyModifiers AppKeyBindings::ConvertVKModifiers(Settings::KeyModifiers modifiers)
|
||||
{
|
||||
Windows::System::VirtualKeyModifiers keyModifiers = Windows::System::VirtualKeyModifiers::None;
|
||||
|
||||
if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Ctrl))
|
||||
{
|
||||
keyModifiers |= Windows::System::VirtualKeyModifiers::Control;
|
||||
}
|
||||
if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Shift))
|
||||
{
|
||||
keyModifiers |= Windows::System::VirtualKeyModifiers::Shift;
|
||||
}
|
||||
if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Alt))
|
||||
{
|
||||
// note: Menu is the Alt VK_MENU
|
||||
keyModifiers |= Windows::System::VirtualKeyModifiers::Menu;
|
||||
}
|
||||
|
||||
return keyModifiers;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Handles the special case of providing a text override for the UI shortcut due to VK_OEM_COMMA issue.
|
||||
// Looks at the flags from the KeyChord modifiers and provides a concatenated string value of all
|
||||
// in the same order that XAML would put them as well.
|
||||
// Return Value:
|
||||
// - a WinRT hstring representation of the key modifiers for the shortcut
|
||||
//NOTE: This needs to be localized with https://github.com/microsoft/terminal/issues/794 if XAML framework issue not resolved before then
|
||||
winrt::hstring AppKeyBindings::FormatOverrideShortcutText(Settings::KeyModifiers modifiers)
|
||||
{
|
||||
std::wstring buffer{ L"" };
|
||||
|
||||
if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Ctrl))
|
||||
{
|
||||
buffer += L"Ctrl+";
|
||||
}
|
||||
if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Shift))
|
||||
{
|
||||
buffer += L"Shift+";
|
||||
}
|
||||
if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Alt))
|
||||
{
|
||||
buffer += L"Alt+";
|
||||
}
|
||||
|
||||
return winrt::hstring{ buffer };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,14 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
AppKeyBindings() = default;
|
||||
|
||||
static TerminalApp::AppKeyBindings FromJson(Windows::Data::Json::JsonArray const& json);
|
||||
Windows::Data::Json::JsonArray ToJson();
|
||||
static Windows::System::VirtualKeyModifiers ConvertVKModifiers(winrt::Microsoft::Terminal::Settings::KeyModifiers modifiers);
|
||||
static winrt::hstring FormatOverrideShortcutText(winrt::Microsoft::Terminal::Settings::KeyModifiers modifiers);
|
||||
|
||||
bool TryKeyChord(winrt::Microsoft::Terminal::Settings::KeyChord const& kc);
|
||||
void SetKeyBinding(TerminalApp::ShortcutAction const& action, winrt::Microsoft::Terminal::Settings::KeyChord const& chord);
|
||||
Microsoft::Terminal::Settings::KeyChord GetKeyBinding(TerminalApp::ShortcutAction const& action);
|
||||
|
||||
DECLARE_EVENT(CopyText, _CopyTextHandlers, TerminalApp::CopyTextEventArgs);
|
||||
DECLARE_EVENT(PasteText, _PasteTextHandlers, TerminalApp::PasteTextEventArgs);
|
||||
|
||||
@@ -17,7 +17,6 @@ namespace TerminalApp
|
||||
NewTabProfile6,
|
||||
NewTabProfile7,
|
||||
NewTabProfile8,
|
||||
NewTabProfile9,
|
||||
NewWindow,
|
||||
CloseWindow,
|
||||
CloseTab,
|
||||
@@ -32,7 +31,6 @@ namespace TerminalApp
|
||||
SwitchToTab6,
|
||||
SwitchToTab7,
|
||||
SwitchToTab8,
|
||||
SwitchToTab9,
|
||||
IncreaseFontSize,
|
||||
DecreaseFontSize,
|
||||
ScrollUp,
|
||||
@@ -65,7 +63,11 @@ namespace TerminalApp
|
||||
{
|
||||
AppKeyBindings();
|
||||
|
||||
Windows.Data.Json.JsonArray ToJson();
|
||||
static AppKeyBindings FromJson(Windows.Data.Json.JsonArray json);
|
||||
|
||||
void SetKeyBinding(ShortcutAction action, Microsoft.Terminal.Settings.KeyChord chord);
|
||||
Microsoft.Terminal.Settings.KeyChord GetKeyBinding(ShortcutAction action);
|
||||
|
||||
event CopyTextEventArgs CopyText;
|
||||
event PasteTextEventArgs PasteText;
|
||||
|
||||
@@ -12,6 +12,15 @@ using namespace winrt::Microsoft::Terminal::Settings;
|
||||
using namespace ::TerminalApp;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace Microsoft::Console;
|
||||
|
||||
// {2bde4a90-d05f-401c-9492-e40884ead1d8}
|
||||
// uuidv5 properties: name format is UTF-16LE bytes
|
||||
static constexpr GUID TERMINAL_PROFILE_NAMESPACE_GUID =
|
||||
{ 0x2bde4a90, 0xd05f, 0x401c, { 0x94, 0x92, 0xe4, 0x8, 0x84, 0xea, 0xd1, 0xd8 } };
|
||||
|
||||
static constexpr std::wstring_view PACKAGED_PROFILE_ICON_PATH{ L"ms-appx:///ProfileIcons/" };
|
||||
static constexpr std::wstring_view PACKAGED_PROFILE_ICON_EXTENSION{ L".png" };
|
||||
|
||||
CascadiaSettings::CascadiaSettings() :
|
||||
_globals{},
|
||||
@@ -32,8 +41,8 @@ ColorScheme _CreateCampbellScheme()
|
||||
RGB(12, 12, 12) };
|
||||
auto& campbellTable = campbellScheme.GetTable();
|
||||
auto campbellSpan = gsl::span<COLORREF>(&campbellTable[0], gsl::narrow<ptrdiff_t>(COLOR_TABLE_SIZE));
|
||||
Microsoft::Console::Utils::InitializeCampbellColorTable(campbellSpan);
|
||||
Microsoft::Console::Utils::SetColorTableAlpha(campbellSpan, 0xff);
|
||||
Utils::InitializeCampbellColorTable(campbellSpan);
|
||||
Utils::SetColorTableAlpha(campbellSpan, 0xff);
|
||||
|
||||
return campbellScheme;
|
||||
}
|
||||
@@ -64,7 +73,7 @@ ColorScheme _CreateOneHalfDarkScheme()
|
||||
oneHalfDarkTable[13] = RGB(198, 120, 221); // magenta
|
||||
oneHalfDarkTable[14] = RGB( 86, 182, 194); // cyan
|
||||
oneHalfDarkTable[15] = RGB(220, 223, 228); // white
|
||||
Microsoft::Console::Utils::SetColorTableAlpha(oneHalfDarkSpan, 0xff);
|
||||
Utils::SetColorTableAlpha(oneHalfDarkSpan, 0xff);
|
||||
|
||||
return oneHalfDarkScheme;
|
||||
}
|
||||
@@ -94,7 +103,7 @@ ColorScheme _CreateOneHalfLightScheme()
|
||||
oneHalfLightTable[13] = RGB(197, 119, 221); // magenta
|
||||
oneHalfLightTable[14] = RGB( 86, 181, 193); // cyan
|
||||
oneHalfLightTable[15] = RGB(255, 255, 255); // white
|
||||
Microsoft::Console::Utils::SetColorTableAlpha(oneHalfLightSpan, 0xff);
|
||||
Utils::SetColorTableAlpha(oneHalfLightSpan, 0xff);
|
||||
|
||||
return oneHalfLightScheme;
|
||||
}
|
||||
@@ -122,7 +131,7 @@ ColorScheme _CreateSolarizedDarkScheme()
|
||||
solarizedDarkTable[13] = RGB(108, 113, 196);
|
||||
solarizedDarkTable[14] = RGB(147, 161, 161);
|
||||
solarizedDarkTable[15] = RGB(253, 246, 227);
|
||||
Microsoft::Console::Utils::SetColorTableAlpha(solarizedDarkSpan, 0xff);
|
||||
Utils::SetColorTableAlpha(solarizedDarkSpan, 0xff);
|
||||
|
||||
return solarizedDarkScheme;
|
||||
}
|
||||
@@ -150,7 +159,7 @@ ColorScheme _CreateSolarizedLightScheme()
|
||||
solarizedLightTable[13] = RGB(108, 113, 196);
|
||||
solarizedLightTable[14] = RGB(147, 161, 161);
|
||||
solarizedLightTable[15] = RGB(253, 246, 227);
|
||||
Microsoft::Console::Utils::SetColorTableAlpha(solarizedLightSpan, 0xff);
|
||||
Utils::SetColorTableAlpha(solarizedLightSpan, 0xff);
|
||||
|
||||
return solarizedLightScheme;
|
||||
}
|
||||
@@ -174,48 +183,50 @@ void CascadiaSettings::_CreateDefaultSchemes()
|
||||
|
||||
// Method Description:
|
||||
// - Create a set of profiles to use as the "default" profiles when initializing
|
||||
// the terminal. Currently, we create two profiles: one for cmd.exe, and
|
||||
// one for powershell.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
// the terminal. Currently, we create two or three profiles:
|
||||
// * one for cmd.exe
|
||||
// * one for powershell.exe (inbox Windows Powershell)
|
||||
// * if Powershell Core (pwsh.exe) is installed, we'll create another for
|
||||
// Powershell Core.
|
||||
void CascadiaSettings::_CreateDefaultProfiles()
|
||||
{
|
||||
Profile cmdProfile{};
|
||||
auto cmdProfile{ _CreateDefaultProfile(L"cmd") };
|
||||
cmdProfile.SetFontFace(L"Consolas");
|
||||
cmdProfile.SetCommandline(L"cmd.exe");
|
||||
cmdProfile.SetStartingDirectory(DEFAULT_STARTING_DIRECTORY);
|
||||
cmdProfile.SetColorScheme({ L"Campbell" });
|
||||
cmdProfile.SetAcrylicOpacity(0.75);
|
||||
cmdProfile.SetUseAcrylic(true);
|
||||
cmdProfile.SetName(L"cmd");
|
||||
|
||||
Profile powershellProfile{};
|
||||
// If the user has installed PowerShell Core, we add PowerShell Core as a default.
|
||||
// PowerShell Core default folder is "%PROGRAMFILES%\PowerShell\[Version]\".
|
||||
std::wstring psCmdline = L"powershell.exe";
|
||||
std::filesystem::path psCoreCmdline{};
|
||||
if (_IsPowerShellCoreInstalled(L"%ProgramFiles%", psCoreCmdline))
|
||||
{
|
||||
psCmdline = psCoreCmdline;
|
||||
}
|
||||
else if (_IsPowerShellCoreInstalled(L"%ProgramFiles(x86)%", psCoreCmdline))
|
||||
{
|
||||
psCmdline = psCoreCmdline;
|
||||
}
|
||||
powershellProfile.SetFontFace(L"Courier New");
|
||||
powershellProfile.SetCommandline(psCmdline);
|
||||
auto powershellProfile{ _CreateDefaultProfile(L"Windows PowerShell") };
|
||||
powershellProfile.SetCommandline(L"powershell.exe");
|
||||
powershellProfile.SetStartingDirectory(DEFAULT_STARTING_DIRECTORY);
|
||||
powershellProfile.SetColorScheme({ L"Campbell" });
|
||||
powershellProfile.SetDefaultBackground(RGB(1, 36, 86));
|
||||
powershellProfile.SetDefaultBackground(POWERSHELL_BLUE);
|
||||
powershellProfile.SetUseAcrylic(false);
|
||||
powershellProfile.SetName(L"PowerShell");
|
||||
|
||||
// If the user has installed PowerShell Core, we add PowerShell Core as a default.
|
||||
// PowerShell Core default folder is "%PROGRAMFILES%\PowerShell\[Version]\".
|
||||
std::filesystem::path psCoreCmdline{};
|
||||
if (_isPowerShellCoreInstalled(psCoreCmdline))
|
||||
{
|
||||
auto pwshProfile{ _CreateDefaultProfile(L"PowerShell Core") };
|
||||
pwshProfile.SetCommandline(psCoreCmdline);
|
||||
pwshProfile.SetStartingDirectory(DEFAULT_STARTING_DIRECTORY);
|
||||
pwshProfile.SetColorScheme({ L"Campbell" });
|
||||
|
||||
// If powershell core is installed, we'll use that as the default.
|
||||
// Otherwise, we'll use normal Windows Powershell as the default.
|
||||
_profiles.emplace_back(pwshProfile);
|
||||
_globals.SetDefaultProfile(pwshProfile.GetGuid());
|
||||
}
|
||||
else
|
||||
{
|
||||
_globals.SetDefaultProfile(powershellProfile.GetGuid());
|
||||
}
|
||||
|
||||
_profiles.emplace_back(powershellProfile);
|
||||
_profiles.emplace_back(cmdProfile);
|
||||
|
||||
_globals.SetDefaultProfile(powershellProfile.GetGuid());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -278,9 +289,6 @@ void CascadiaSettings::_CreateDefaultKeybindings()
|
||||
keyBindings.SetKeyBinding(ShortcutAction::NewTabProfile8,
|
||||
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
|
||||
static_cast<int>('9') });
|
||||
keyBindings.SetKeyBinding(ShortcutAction::NewTabProfile9,
|
||||
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
|
||||
static_cast<int>('0') });
|
||||
|
||||
keyBindings.SetKeyBinding(ShortcutAction::ScrollUp,
|
||||
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
|
||||
@@ -321,9 +329,6 @@ void CascadiaSettings::_CreateDefaultKeybindings()
|
||||
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab8,
|
||||
KeyChord{ KeyModifiers::Alt,
|
||||
static_cast<int>('9') });
|
||||
keyBindings.SetKeyBinding(ShortcutAction::SwitchToTab9,
|
||||
KeyChord{ KeyModifiers::Alt,
|
||||
static_cast<int>('0') });
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -332,7 +337,7 @@ void CascadiaSettings::_CreateDefaultKeybindings()
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CascadiaSettings::_CreateDefaults()
|
||||
void CascadiaSettings::CreateDefaults()
|
||||
{
|
||||
_CreateDefaultProfiles();
|
||||
_CreateDefaultSchemes();
|
||||
@@ -421,14 +426,28 @@ GlobalAppSettings& CascadiaSettings::GlobalSettings()
|
||||
return _globals;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Returns true if the user has installed PowerShell Core. This will check
|
||||
// both %ProgramFiles% and %ProgramFiles(x86)%, and will return true if
|
||||
// powershell core was installed in either location.
|
||||
// Arguments:
|
||||
// - A ref of a path that receives the result of PowerShell Core pwsh.exe full path.
|
||||
// Return Value:
|
||||
// - true iff powershell core (pwsh.exe) is present.
|
||||
bool CascadiaSettings::_isPowerShellCoreInstalled(std::filesystem::path& cmdline)
|
||||
{
|
||||
return _isPowerShellCoreInstalledInPath(L"%ProgramFiles%", cmdline) ||
|
||||
_isPowerShellCoreInstalledInPath(L"%ProgramFiles(x86)%", cmdline);
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Returns true if the user has installed PowerShell Core.
|
||||
// Arguments:
|
||||
// - A string that contains an environment-variable string in the form: %variableName%.
|
||||
// - A ref of a path that receives the result of PowerShell Core pwsh.exe full path.
|
||||
// Return Value:
|
||||
// - true or false.
|
||||
bool CascadiaSettings::_IsPowerShellCoreInstalled(std::wstring_view programFileEnv, std::filesystem::path& cmdline)
|
||||
// - true iff powershell core (pwsh.exe) is present in the given path
|
||||
bool CascadiaSettings::_isPowerShellCoreInstalledInPath(const std::wstring_view programFileEnv, std::filesystem::path& cmdline)
|
||||
{
|
||||
std::filesystem::path psCorePath = ExpandEnvironmentVariableString(programFileEnv.data());
|
||||
psCorePath /= L"PowerShell";
|
||||
@@ -468,3 +487,26 @@ std::wstring CascadiaSettings::ExpandEnvironmentVariableString(std::wstring_view
|
||||
result.resize(requiredSize-1);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper function for creating a skeleton default profile with a pre-populated
|
||||
// guid and name.
|
||||
// Arguments:
|
||||
// - name: the name of the new profile.
|
||||
// Return Value:
|
||||
// - A Profile, ready to be filled in
|
||||
Profile CascadiaSettings::_CreateDefaultProfile(const std::wstring_view name)
|
||||
{
|
||||
auto profileGuid{ Utils::CreateV5Uuid(TERMINAL_PROFILE_NAMESPACE_GUID, gsl::as_bytes(gsl::make_span(name))) };
|
||||
Profile newProfile{ profileGuid };
|
||||
|
||||
newProfile.SetName(static_cast<std::wstring>(name));
|
||||
|
||||
std::wstring iconPath{ PACKAGED_PROFILE_ICON_PATH };
|
||||
iconPath.append(Utils::GuidToString(profileGuid));
|
||||
iconPath.append(PACKAGED_PROFILE_ICON_EXTENSION);
|
||||
|
||||
newProfile.SetIconPath(iconPath);
|
||||
|
||||
return newProfile;
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ public:
|
||||
static winrt::hstring GetSettingsPath();
|
||||
|
||||
const Profile* FindProfile(GUID profileGuid) const noexcept;
|
||||
|
||||
void CreateDefaults();
|
||||
private:
|
||||
GlobalAppSettings _globals;
|
||||
std::vector<Profile> _profiles;
|
||||
@@ -58,7 +60,6 @@ private:
|
||||
void _CreateDefaultKeybindings();
|
||||
void _CreateDefaultSchemes();
|
||||
void _CreateDefaultProfiles();
|
||||
void _CreateDefaults();
|
||||
|
||||
static bool _IsPackaged();
|
||||
static void _SaveAsPackagedApp(const winrt::hstring content);
|
||||
@@ -67,6 +68,8 @@ private:
|
||||
static winrt::hstring _GetPackagedSettingsPath();
|
||||
static std::optional<winrt::hstring> _LoadAsPackagedApp();
|
||||
static std::optional<winrt::hstring> _LoadAsUnpackagedApp();
|
||||
static bool _IsPowerShellCoreInstalled(std::wstring_view programFileEnv, std::filesystem::path& cmdline);
|
||||
static bool _isPowerShellCoreInstalledInPath(const std::wstring_view programFileEnv, std::filesystem::path& cmdline);
|
||||
static bool _isPowerShellCoreInstalled(std::filesystem::path& cmdline);
|
||||
static std::wstring ExpandEnvironmentVariableString(std::wstring_view source);
|
||||
static Profile _CreateDefaultProfile(const std::wstring_view name);
|
||||
};
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
#include "CascadiaSettings.h"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
#include <appmodel.h>
|
||||
#include <wil/com.h>
|
||||
#include <wil/filesystem.h>
|
||||
#include <shlobj.h>
|
||||
|
||||
using namespace ::TerminalApp;
|
||||
@@ -18,12 +16,12 @@ using namespace winrt::Windows::Storage;
|
||||
using namespace winrt::Windows::Storage::Streams;
|
||||
using namespace ::Microsoft::Console;
|
||||
|
||||
static const std::wstring FILENAME { L"profiles.json" };
|
||||
static const std::wstring SETTINGS_FOLDER_NAME{ L"\\Microsoft\\Windows Terminal\\" };
|
||||
static constexpr std::wstring_view FILENAME { L"profiles.json" };
|
||||
static constexpr std::wstring_view SETTINGS_FOLDER_NAME{ L"\\Microsoft\\Windows Terminal\\" };
|
||||
|
||||
static const std::wstring PROFILES_KEY{ L"profiles" };
|
||||
static const std::wstring KEYBINDINGS_KEY{ L"keybindings" };
|
||||
static const std::wstring SCHEMES_KEY{ L"schemes" };
|
||||
static constexpr std::wstring_view PROFILES_KEY{ L"profiles" };
|
||||
static constexpr std::wstring_view KEYBINDINGS_KEY{ L"keybindings" };
|
||||
static constexpr std::wstring_view SCHEMES_KEY{ L"schemes" };
|
||||
|
||||
// Method Description:
|
||||
// - Creates a CascadiaSettings from whatever's saved on disk, or instantiates
|
||||
@@ -47,39 +45,29 @@ std::unique_ptr<CascadiaSettings> CascadiaSettings::LoadAll(const bool saveOnLoa
|
||||
{
|
||||
const auto actualData = fileData.value();
|
||||
|
||||
JsonValue root{ nullptr };
|
||||
bool parsedSuccessfully = JsonValue::TryParse(actualData, root);
|
||||
// TODO:MSFT:20737698 - Display an error if we failed to parse settings
|
||||
if (parsedSuccessfully)
|
||||
{
|
||||
JsonObject obj = root.GetObjectW();
|
||||
resultPtr = FromJson(obj);
|
||||
// If Parse fails, it'll throw a hresult_error
|
||||
JsonObject root = JsonObject::Parse(actualData);
|
||||
|
||||
// Update profile only if it has changed.
|
||||
if (saveOnLoad)
|
||||
resultPtr = FromJson(root);
|
||||
|
||||
// Update profile only if it has changed.
|
||||
if (saveOnLoad)
|
||||
{
|
||||
const JsonObject json = resultPtr->ToJson();
|
||||
auto serializedSettings = json.Stringify();
|
||||
|
||||
if (actualData != serializedSettings)
|
||||
{
|
||||
const JsonObject json = resultPtr->ToJson();
|
||||
auto serializedSettings = json.Stringify();
|
||||
|
||||
if (actualData != serializedSettings)
|
||||
{
|
||||
resultPtr->SaveAll();
|
||||
}
|
||||
resultPtr->SaveAll();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Until 20737698 is done, throw an error, so debugging can trace
|
||||
// the exception here, instead of later on in unrelated code
|
||||
THROW_HR(E_INVALIDARG);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
resultPtr = std::make_unique<CascadiaSettings>();
|
||||
resultPtr->_CreateDefaults();
|
||||
resultPtr->CreateDefaults();
|
||||
|
||||
// The settings file does not exist. Let's commit one.
|
||||
// The settings file does not exist. Let's commit one.
|
||||
resultPtr->SaveAll();
|
||||
}
|
||||
|
||||
@@ -137,6 +125,9 @@ JsonObject CascadiaSettings::ToJson() const
|
||||
jsonObject.Insert(PROFILES_KEY, profilesArray);
|
||||
jsonObject.Insert(SCHEMES_KEY, schemesArray);
|
||||
|
||||
jsonObject.Insert(KEYBINDINGS_KEY,
|
||||
_globals.GetKeybindings().ToJson());
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
@@ -190,9 +181,18 @@ std::unique_ptr<CascadiaSettings> CascadiaSettings::FromJson(JsonObject json)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO:MSFT:20700157
|
||||
// Load the keybindings from the file as well
|
||||
resultPtr->_CreateDefaultKeybindings();
|
||||
if (json.HasKey(KEYBINDINGS_KEY))
|
||||
{
|
||||
const auto keybindingsObj = json.GetNamedArray(KEYBINDINGS_KEY);
|
||||
auto loadedBindings = AppKeyBindings::FromJson(keybindingsObj);
|
||||
resultPtr->_globals.SetKeybindings(loadedBindings);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create the default keybindings if we couldn't find any keybindings.
|
||||
resultPtr->_CreateDefaultKeybindings();
|
||||
}
|
||||
|
||||
return resultPtr;
|
||||
}
|
||||
@@ -366,7 +366,7 @@ std::optional<winrt::hstring> CascadiaSettings::_LoadAsUnpackagedApp()
|
||||
|
||||
// function Description:
|
||||
// - Returns the full path to the settings file, either within the application
|
||||
// package, or in it's unpackaged location.
|
||||
// package, or in its unpackaged location.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
@@ -378,7 +378,7 @@ winrt::hstring CascadiaSettings::GetSettingsPath()
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Get the full path to settings file in it's packaged location.
|
||||
// - Get the full path to settings file in its packaged location.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
|
||||
@@ -12,10 +12,10 @@ using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace winrt::Windows::Data::Json;
|
||||
|
||||
static const std::wstring NAME_KEY{ L"name" };
|
||||
static const std::wstring TABLE_KEY{ L"colors" };
|
||||
static const std::wstring FOREGROUND_KEY{ L"foreground" };
|
||||
static const std::wstring BACKGROUND_KEY{ L"background" };
|
||||
static constexpr std::wstring_view NAME_KEY{ L"name" };
|
||||
static constexpr std::wstring_view TABLE_KEY{ L"colors" };
|
||||
static constexpr std::wstring_view FOREGROUND_KEY{ L"foreground" };
|
||||
static constexpr std::wstring_view BACKGROUND_KEY{ L"background" };
|
||||
static const std::array<std::wstring, 16> TABLE_COLORS =
|
||||
{
|
||||
L"black",
|
||||
|
||||
@@ -13,28 +13,27 @@ using namespace winrt::Windows::Data::Json;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace ::Microsoft::Console;
|
||||
|
||||
static const std::wstring DEFAULTPROFILE_KEY{ L"defaultProfile" };
|
||||
static const std::wstring ALWAYS_SHOW_TABS_KEY{ L"alwaysShowTabs" };
|
||||
static const std::wstring INITIALROWS_KEY{ L"initialRows" };
|
||||
static const std::wstring INITIALCOLS_KEY{ L"initialCols" };
|
||||
static const std::wstring SHOW_TITLE_IN_TITLEBAR_KEY{ L"showTerminalTitleInTitlebar" };
|
||||
static const std::wstring REQUESTED_THEME_KEY{ L"requestedTheme" };
|
||||
static constexpr std::wstring_view DEFAULTPROFILE_KEY{ L"defaultProfile" };
|
||||
static constexpr std::wstring_view ALWAYS_SHOW_TABS_KEY{ L"alwaysShowTabs" };
|
||||
static constexpr std::wstring_view INITIALROWS_KEY{ L"initialRows" };
|
||||
static constexpr std::wstring_view INITIALCOLS_KEY{ L"initialCols" };
|
||||
static constexpr std::wstring_view SHOW_TITLE_IN_TITLEBAR_KEY{ L"showTerminalTitleInTitlebar" };
|
||||
static constexpr std::wstring_view REQUESTED_THEME_KEY{ L"requestedTheme" };
|
||||
static constexpr std::wstring_view SHOW_TABS_IN_TITLEBAR_KEY{ L"showTabsInTitlebar" };
|
||||
|
||||
static const std::wstring SHOW_TABS_IN_TITLEBAR_KEY{ L"experimental_showTabsInTitlebar" };
|
||||
|
||||
static const std::wstring LIGHT_THEME_VALUE{ L"light" };
|
||||
static const std::wstring DARK_THEME_VALUE{ L"dark" };
|
||||
static const std::wstring SYSTEM_THEME_VALUE{ L"system" };
|
||||
static constexpr std::wstring_view LIGHT_THEME_VALUE{ L"light" };
|
||||
static constexpr std::wstring_view DARK_THEME_VALUE{ L"dark" };
|
||||
static constexpr std::wstring_view SYSTEM_THEME_VALUE{ L"system" };
|
||||
|
||||
GlobalAppSettings::GlobalAppSettings() :
|
||||
_keybindings{},
|
||||
_colorSchemes{},
|
||||
_defaultProfile{},
|
||||
_alwaysShowTabs{ false },
|
||||
_alwaysShowTabs{ true },
|
||||
_initialRows{ DEFAULT_ROWS },
|
||||
_initialCols{ DEFAULT_COLS },
|
||||
_showTitleInTitlebar{ true },
|
||||
_showTabsInTitlebar{ false },
|
||||
_showTabsInTitlebar{ true },
|
||||
_requestedTheme{ ElementTheme::Default }
|
||||
{
|
||||
|
||||
@@ -71,6 +70,11 @@ AppKeyBindings GlobalAppSettings::GetKeybindings() const noexcept
|
||||
return _keybindings;
|
||||
}
|
||||
|
||||
void GlobalAppSettings::SetKeybindings(winrt::TerminalApp::AppKeyBindings newBindings) noexcept
|
||||
{
|
||||
_keybindings = newBindings;
|
||||
}
|
||||
|
||||
bool GlobalAppSettings::GetAlwaysShowTabs() const noexcept
|
||||
{
|
||||
return _alwaysShowTabs;
|
||||
@@ -96,6 +100,10 @@ ElementTheme GlobalAppSettings::GetRequestedTheme() const noexcept
|
||||
return _requestedTheme;
|
||||
}
|
||||
|
||||
void GlobalAppSettings::SetRequestedTheme(const ElementTheme requestedTheme) noexcept
|
||||
{
|
||||
_requestedTheme = requestedTheme;
|
||||
}
|
||||
|
||||
#pragma region ExperimentalSettings
|
||||
bool GlobalAppSettings::GetShowTabsInTitlebar() const noexcept
|
||||
@@ -147,11 +155,11 @@ JsonObject GlobalAppSettings::ToJson() const
|
||||
|
||||
jsonObject.Insert(SHOW_TABS_IN_TITLEBAR_KEY,
|
||||
JsonValue::CreateBooleanValue(_showTabsInTitlebar));
|
||||
if (_requestedTheme != ElementTheme::Default)
|
||||
{
|
||||
jsonObject.Insert(REQUESTED_THEME_KEY,
|
||||
JsonValue::CreateStringValue(_SerializeTheme(_requestedTheme)));
|
||||
}
|
||||
jsonObject.Insert(REQUESTED_THEME_KEY,
|
||||
JsonValue::CreateStringValue(_SerializeTheme(_requestedTheme)));
|
||||
|
||||
// We'll add the keybindings later in CascadiaSettings, because if we do it
|
||||
// here, they'll appear before the profiles.
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
@@ -227,13 +235,13 @@ ElementTheme GlobalAppSettings::_ParseTheme(const std::wstring& themeString) noe
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper function for converting a CursorStyle to it's corresponding string
|
||||
// - Helper function for converting a CursorStyle to its corresponding string
|
||||
// value.
|
||||
// Arguments:
|
||||
// - theme: The enum value to convert to a string.
|
||||
// Return Value:
|
||||
// - The string value for the given CursorStyle
|
||||
std::wstring GlobalAppSettings::_SerializeTheme(const ElementTheme theme) noexcept
|
||||
std::wstring_view GlobalAppSettings::_SerializeTheme(const ElementTheme theme) noexcept
|
||||
{
|
||||
switch (theme)
|
||||
{
|
||||
|
||||
@@ -35,6 +35,7 @@ public:
|
||||
GUID GetDefaultProfile() const noexcept;
|
||||
|
||||
winrt::TerminalApp::AppKeyBindings GetKeybindings() const noexcept;
|
||||
void SetKeybindings(winrt::TerminalApp::AppKeyBindings newBindings) noexcept;
|
||||
|
||||
bool GetAlwaysShowTabs() const noexcept;
|
||||
void SetAlwaysShowTabs(const bool showTabs) noexcept;
|
||||
@@ -42,6 +43,8 @@ public:
|
||||
bool GetShowTitleInTitlebar() const noexcept;
|
||||
void SetShowTitleInTitlebar(const bool showTitleInTitlebar) noexcept;
|
||||
|
||||
void SetRequestedTheme(const winrt::Windows::UI::Xaml::ElementTheme requestedTheme) noexcept;
|
||||
|
||||
bool GetShowTabsInTitlebar() const noexcept;
|
||||
void SetShowTabsInTitlebar(const bool showTabsInTitlebar) noexcept;
|
||||
|
||||
@@ -69,6 +72,6 @@ private:
|
||||
winrt::Windows::UI::Xaml::ElementTheme _requestedTheme;
|
||||
|
||||
static winrt::Windows::UI::Xaml::ElementTheme _ParseTheme(const std::wstring& themeString) noexcept;
|
||||
static std::wstring _SerializeTheme(const winrt::Windows::UI::Xaml::ElementTheme theme) noexcept;
|
||||
static std::wstring_view _SerializeTheme(const winrt::Windows::UI::Xaml::ElementTheme theme) noexcept;
|
||||
|
||||
};
|
||||
|
||||
249
src/cascadia/TerminalApp/KeyChordSerialization.cpp
Normal file
@@ -0,0 +1,249 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "KeyChordSerialization.h"
|
||||
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
|
||||
static constexpr std::wstring_view CTRL_KEY{ L"ctrl" };
|
||||
static constexpr std::wstring_view SHIFT_KEY{ L"shift" };
|
||||
static constexpr std::wstring_view ALT_KEY{ L"alt" };
|
||||
|
||||
static constexpr int MAX_CHORD_PARTS = 4;
|
||||
|
||||
static const std::unordered_map<int32_t, std::wstring_view> vkeyNamePairs {
|
||||
{ VK_BACK , L"backspace"},
|
||||
{ VK_TAB , L"tab"},
|
||||
{ VK_RETURN , L"enter" },
|
||||
{ VK_ESCAPE , L"esc" },
|
||||
{ VK_SPACE , L"space" },
|
||||
{ VK_PRIOR , L"pgup" },
|
||||
{ VK_NEXT , L"pgdn" },
|
||||
{ VK_END , L"end" },
|
||||
{ VK_HOME , L"home" },
|
||||
{ VK_LEFT , L"left" },
|
||||
{ VK_UP , L"up" },
|
||||
{ VK_RIGHT , L"right" },
|
||||
{ VK_DOWN , L"down" },
|
||||
{ VK_INSERT , L"insert" },
|
||||
{ VK_DELETE , L"delete" },
|
||||
{ VK_NUMPAD0 , L"numpad_0" },
|
||||
{ VK_NUMPAD1 , L"numpad_1" },
|
||||
{ VK_NUMPAD2 , L"numpad_2" },
|
||||
{ VK_NUMPAD3 , L"numpad_3" },
|
||||
{ VK_NUMPAD4 , L"numpad_4" },
|
||||
{ VK_NUMPAD5 , L"numpad_5" },
|
||||
{ VK_NUMPAD6 , L"numpad_6" },
|
||||
{ VK_NUMPAD7 , L"numpad_7" },
|
||||
{ VK_NUMPAD8 , L"numpad_8" },
|
||||
{ VK_NUMPAD9 , L"numpad_9" },
|
||||
{ VK_MULTIPLY , L"numpad_multiply" },
|
||||
{ VK_ADD , L"numpad_plus" },
|
||||
{ VK_SUBTRACT , L"numpad_minus" },
|
||||
{ VK_DECIMAL , L"numpad_period" },
|
||||
{ VK_DIVIDE , L"numpad_divide" },
|
||||
{ VK_F1 , L"f1" },
|
||||
{ VK_F2 , L"f2" },
|
||||
{ VK_F3 , L"f3" },
|
||||
{ VK_F4 , L"f4" },
|
||||
{ VK_F5 , L"f5" },
|
||||
{ VK_F6 , L"f6" },
|
||||
{ VK_F7 , L"f7" },
|
||||
{ VK_F8 , L"f8" },
|
||||
{ VK_F9 , L"f9" },
|
||||
{ VK_F10 , L"f10" },
|
||||
{ VK_F11 , L"f11" },
|
||||
{ VK_F12 , L"f12" },
|
||||
{ VK_F13 , L"f13" },
|
||||
{ VK_F14 , L"f14" },
|
||||
{ VK_F15 , L"f15" },
|
||||
{ VK_F16 , L"f16" },
|
||||
{ VK_F17 , L"f17" },
|
||||
{ VK_F18 , L"f18" },
|
||||
{ VK_F19 , L"f19" },
|
||||
{ VK_F20 , L"f20" },
|
||||
{ VK_F21 , L"f21" },
|
||||
{ VK_F22 , L"f22" },
|
||||
{ VK_F23 , L"f23" },
|
||||
{ VK_F24 , L"f24" },
|
||||
{ VK_OEM_PLUS , L"plus" },
|
||||
{ VK_OEM_COMMA , L"," },
|
||||
{ VK_OEM_MINUS , L"-" },
|
||||
{ VK_OEM_PERIOD , L"." }
|
||||
// TODO:
|
||||
// These all look like they'd be good keybindings, but change based on keyboard
|
||||
// layout. How do we deal with that?
|
||||
// #define VK_OEM_NEC_EQUAL 0x92 // '=' key on numpad
|
||||
// #define VK_OEM_1 0xBA // ';:' for US
|
||||
// #define VK_OEM_2 0xBF // '/?' for US
|
||||
// #define VK_OEM_3 0xC0 // '`~' for US
|
||||
// #define VK_OEM_4 0xDB // '[{' for US
|
||||
// #define VK_OEM_5 0xDC // '\|' for US
|
||||
// #define VK_OEM_6 0xDD // ']}' for US
|
||||
// #define VK_OEM_7 0xDE // ''"' for US
|
||||
};
|
||||
|
||||
// Function Description:
|
||||
// - Deserializes the given string into a new KeyChord instance. If this
|
||||
// fails to translate the string into a keychord, it will throw a
|
||||
// hresult_invalid_argument exception.
|
||||
// - The string should fit the format "[ctrl+][alt+][shift+]<keyName>",
|
||||
// where each modifier is optional, and keyName is either one of the
|
||||
// names listed in the vkeyNamePairs vector above, or is one of 0-9a-zA-Z.
|
||||
// Arguments:
|
||||
// - hstr: the string to parse into a keychord.
|
||||
// Return Value:
|
||||
// - a newly constructed KeyChord
|
||||
winrt::Microsoft::Terminal::Settings::KeyChord KeyChordSerialization::FromString(const winrt::hstring& hstr)
|
||||
{
|
||||
std::wstring wstr{ hstr };
|
||||
|
||||
// Split the string on '+'
|
||||
std::wstring temp;
|
||||
std::vector<std::wstring> parts;
|
||||
std::wstringstream wss(wstr);
|
||||
|
||||
while(std::getline(wss, temp, L'+'))
|
||||
{
|
||||
parts.push_back(temp);
|
||||
|
||||
// If we have > 4, something's wrong.
|
||||
if (parts.size() > MAX_CHORD_PARTS)
|
||||
{
|
||||
throw winrt::hresult_invalid_argument();
|
||||
}
|
||||
}
|
||||
|
||||
KeyModifiers modifiers = KeyModifiers::None;
|
||||
int32_t vkey = 0;
|
||||
|
||||
// Look for ctrl, shift, alt. Anything else might be a key
|
||||
for (const auto& part : parts)
|
||||
{
|
||||
std::wstring lowercase = part;
|
||||
std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(), std::towlower);
|
||||
if (lowercase == CTRL_KEY)
|
||||
{
|
||||
modifiers |= KeyModifiers::Ctrl;
|
||||
}
|
||||
else if (lowercase == ALT_KEY)
|
||||
{
|
||||
modifiers |= KeyModifiers::Alt;
|
||||
}
|
||||
else if (lowercase == SHIFT_KEY)
|
||||
{
|
||||
modifiers |= KeyModifiers::Shift;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool foundKey = false;
|
||||
// For potential keys, look through the pairs of strings and vkeys
|
||||
if (part.size() == 1)
|
||||
{
|
||||
const wchar_t wch = part.at(0);
|
||||
// Quick lookup: ranges of vkeys that correlate directly to a key.
|
||||
if (wch >= L'0' && wch <= L'9')
|
||||
{
|
||||
vkey = static_cast<int32_t>(wch);
|
||||
foundKey = true;
|
||||
}
|
||||
else if (wch >= L'a' && wch <= L'z')
|
||||
{
|
||||
// subtract 0x20 to shift to uppercase
|
||||
vkey = static_cast<int32_t>(wch - 0x20);
|
||||
foundKey = true;
|
||||
}
|
||||
else if (wch >= L'A' && wch <= L'Z')
|
||||
{
|
||||
vkey = static_cast<int32_t>(wch);
|
||||
foundKey = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't find the key with a quick lookup, search the
|
||||
// table to see if we have a matching name.
|
||||
if (!foundKey)
|
||||
{
|
||||
for (const auto& pair : vkeyNamePairs)
|
||||
{
|
||||
if (pair.second == part)
|
||||
{
|
||||
vkey = pair.first;
|
||||
foundKey = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we weren't able to find a match, throw an exception.
|
||||
if (!foundKey)
|
||||
{
|
||||
throw winrt::hresult_invalid_argument();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return winrt::Microsoft::Terminal::Settings::KeyChord{ modifiers, vkey };
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Serialize this keychord into a string represenation.
|
||||
// - The string will fit the format "[ctrl+][alt+][shift+]<keyName>",
|
||||
// where each modifier is optional, and keyName is either one of the
|
||||
// names listed in the vkeyNamePairs vector above, or is one of 0-9a-z.
|
||||
// Return Value:
|
||||
// - a string which is an equivalent serialization of this object.
|
||||
winrt::hstring KeyChordSerialization::ToString(const KeyChord& chord)
|
||||
{
|
||||
bool serializedSuccessfully = false;
|
||||
const auto modifiers = chord.Modifiers();
|
||||
const auto vkey = chord.Vkey();
|
||||
|
||||
std::wstring buffer{ L"" };
|
||||
|
||||
// Add modifiers
|
||||
if (WI_IsFlagSet(modifiers, KeyModifiers::Ctrl))
|
||||
{
|
||||
buffer += CTRL_KEY;
|
||||
buffer += L"+";
|
||||
}
|
||||
if (WI_IsFlagSet(modifiers, KeyModifiers::Alt))
|
||||
{
|
||||
buffer += ALT_KEY;
|
||||
buffer += L"+";
|
||||
}
|
||||
if (WI_IsFlagSet(modifiers, KeyModifiers::Shift))
|
||||
{
|
||||
buffer += SHIFT_KEY;
|
||||
buffer += L"+";
|
||||
}
|
||||
|
||||
// Quick lookup: ranges of vkeys that correlate directly to a key.
|
||||
if (vkey >= L'0' && vkey <= L'9')
|
||||
{
|
||||
buffer += std::wstring(1, static_cast<wchar_t>(vkey));
|
||||
serializedSuccessfully = true;
|
||||
}
|
||||
else if (vkey >= L'A' && vkey <= L'Z')
|
||||
{
|
||||
// add 0x20 to shift to lowercase
|
||||
buffer += std::wstring(1, static_cast<wchar_t>(vkey + 0x20));
|
||||
serializedSuccessfully = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (vkeyNamePairs.find(vkey) != vkeyNamePairs.end())
|
||||
{
|
||||
buffer += vkeyNamePairs.at(vkey);
|
||||
serializedSuccessfully = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!serializedSuccessfully)
|
||||
{
|
||||
buffer = L"";
|
||||
}
|
||||
|
||||
return winrt::hstring{ buffer };
|
||||
}
|
||||
12
src/cascadia/TerminalApp/KeyChordSerialization.h
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
#include <winrt/Microsoft.Terminal.Settings.h>
|
||||
|
||||
class KeyChordSerialization final
|
||||
{
|
||||
public:
|
||||
static winrt::Microsoft::Terminal::Settings::KeyChord FromString(const winrt::hstring& str);
|
||||
static winrt::hstring ToString(const winrt::Microsoft::Terminal::Settings::KeyChord& chord);
|
||||
};
|
||||
@@ -12,44 +12,58 @@ using namespace winrt::TerminalApp;
|
||||
using namespace winrt::Windows::Data::Json;
|
||||
using namespace ::Microsoft::Console;
|
||||
|
||||
static constexpr std::wstring_view NAME_KEY{ L"name" };
|
||||
static constexpr std::wstring_view GUID_KEY{ L"guid" };
|
||||
static constexpr std::wstring_view COLORSCHEME_KEY{ L"colorScheme" };
|
||||
static constexpr std::wstring_view COLORSCHEME_KEY_OLD{ L"colorscheme" };
|
||||
|
||||
static const std::wstring NAME_KEY{ L"name" };
|
||||
static const std::wstring GUID_KEY{ L"guid" };
|
||||
static const std::wstring COLORSCHEME_KEY{ L"colorscheme" };
|
||||
static constexpr std::wstring_view FOREGROUND_KEY{ L"foreground" };
|
||||
static constexpr std::wstring_view BACKGROUND_KEY{ L"background" };
|
||||
static constexpr std::wstring_view COLORTABLE_KEY{ L"colorTable" };
|
||||
static constexpr std::wstring_view HISTORYSIZE_KEY{ L"historySize" };
|
||||
static constexpr std::wstring_view SNAPONINPUT_KEY{ L"snapOnInput" };
|
||||
static constexpr std::wstring_view CURSORCOLOR_KEY{ L"cursorColor" };
|
||||
static constexpr std::wstring_view CURSORSHAPE_KEY{ L"cursorShape" };
|
||||
static constexpr std::wstring_view CURSORHEIGHT_KEY{ L"cursorHeight" };
|
||||
|
||||
static const std::wstring FOREGROUND_KEY{ L"foreground" };
|
||||
static const std::wstring BACKGROUND_KEY{ L"background" };
|
||||
static const std::wstring COLORTABLE_KEY{ L"colorTable" };
|
||||
static const std::wstring HISTORYSIZE_KEY{ L"historySize" };
|
||||
static const std::wstring SNAPONINPUT_KEY{ L"snapOnInput" };
|
||||
static const std::wstring CURSORCOLOR_KEY{ L"cursorColor" };
|
||||
static const std::wstring CURSORSHAPE_KEY{ L"cursorShape" };
|
||||
static const std::wstring CURSORHEIGHT_KEY{ L"cursorHeight" };
|
||||
|
||||
static const std::wstring COMMANDLINE_KEY{ L"commandline" };
|
||||
static const std::wstring FONTFACE_KEY{ L"fontFace" };
|
||||
static const std::wstring FONTSIZE_KEY{ L"fontSize" };
|
||||
static const std::wstring ACRYLICTRANSPARENCY_KEY{ L"acrylicOpacity" };
|
||||
static const std::wstring USEACRYLIC_KEY{ L"useAcrylic" };
|
||||
static const std::wstring SCROLLBARSTATE_KEY{ L"scrollbarState" };
|
||||
static const std::wstring CLOSEONEXIT_KEY{ L"closeOnExit" };
|
||||
static const std::wstring PADDING_KEY{ L"padding" };
|
||||
static const std::wstring STARTINGDIRECTORY_KEY{ L"startingDirectory" };
|
||||
static const std::wstring ICON_KEY{ L"icon" };
|
||||
static constexpr std::wstring_view COMMANDLINE_KEY{ L"commandline" };
|
||||
static constexpr std::wstring_view FONTFACE_KEY{ L"fontFace" };
|
||||
static constexpr std::wstring_view FONTSIZE_KEY{ L"fontSize" };
|
||||
static constexpr std::wstring_view ACRYLICTRANSPARENCY_KEY{ L"acrylicOpacity" };
|
||||
static constexpr std::wstring_view USEACRYLIC_KEY{ L"useAcrylic" };
|
||||
static constexpr std::wstring_view SCROLLBARSTATE_KEY{ L"scrollbarState" };
|
||||
static constexpr std::wstring_view CLOSEONEXIT_KEY{ L"closeOnExit" };
|
||||
static constexpr std::wstring_view PADDING_KEY{ L"padding" };
|
||||
static constexpr std::wstring_view STARTINGDIRECTORY_KEY{ L"startingDirectory" };
|
||||
static constexpr std::wstring_view ICON_KEY{ L"icon" };
|
||||
static constexpr std::wstring_view BACKGROUNDIMAGE_KEY{ L"backgroundImage" };
|
||||
static constexpr std::wstring_view BACKGROUNDIMAGEOPACITY_KEY{ L"backgroundImageOpacity" };
|
||||
static constexpr std::wstring_view BACKGROUNDIMAGESTRETCHMODE_KEY{ L"backgroundImageStretchMode" };
|
||||
|
||||
// Possible values for Scrollbar state
|
||||
static const std::wstring ALWAYS_VISIBLE{ L"visible" };
|
||||
static const std::wstring ALWAYS_HIDE{ L"hidden" };
|
||||
static constexpr std::wstring_view ALWAYS_VISIBLE{ L"visible" };
|
||||
static constexpr std::wstring_view ALWAYS_HIDE{ L"hidden" };
|
||||
|
||||
// Possible values for Cursor Shape
|
||||
static const std::wstring CURSORSHAPE_VINTAGE{ L"vintage" };
|
||||
static const std::wstring CURSORSHAPE_BAR{ L"bar" };
|
||||
static const std::wstring CURSORSHAPE_UNDERSCORE{ L"underscore" };
|
||||
static const std::wstring CURSORSHAPE_FILLEDBOX{ L"filledBox" };
|
||||
static const std::wstring CURSORSHAPE_EMPTYBOX{ L"emptyBox" };
|
||||
static constexpr std::wstring_view CURSORSHAPE_VINTAGE{ L"vintage" };
|
||||
static constexpr std::wstring_view CURSORSHAPE_BAR{ L"bar" };
|
||||
static constexpr std::wstring_view CURSORSHAPE_UNDERSCORE{ L"underscore" };
|
||||
static constexpr std::wstring_view CURSORSHAPE_FILLEDBOX{ L"filledBox" };
|
||||
static constexpr std::wstring_view CURSORSHAPE_EMPTYBOX{ L"emptyBox" };
|
||||
|
||||
// Possible values for Image Stretch Mode
|
||||
static constexpr std::wstring_view IMAGESTRETCHMODE_NONE{ L"none" };
|
||||
static constexpr std::wstring_view IMAGESTRETCHMODE_FILL{ L"fill" };
|
||||
static constexpr std::wstring_view IMAGESTRETCHMODE_UNIFORM{ L"uniform" };
|
||||
static constexpr std::wstring_view IMAGESTRETCHMODE_UNIFORMTOFILL{ L"uniformToFill" };
|
||||
|
||||
Profile::Profile() :
|
||||
_guid{},
|
||||
Profile(Utils::CreateGuid())
|
||||
{
|
||||
}
|
||||
|
||||
Profile::Profile(const winrt::guid& guid):
|
||||
_guid(guid),
|
||||
_name{ L"Default" },
|
||||
_schemeName{},
|
||||
|
||||
@@ -71,9 +85,11 @@ Profile::Profile() :
|
||||
_scrollbarState{ },
|
||||
_closeOnExit{ true },
|
||||
_padding{ DEFAULT_PADDING },
|
||||
_icon{ }
|
||||
_icon{ },
|
||||
_backgroundImage{ },
|
||||
_backgroundImageOpacity{ },
|
||||
_backgroundImageStretchMode{ }
|
||||
{
|
||||
UuidCreate(&_guid);
|
||||
}
|
||||
|
||||
Profile::~Profile()
|
||||
@@ -109,7 +125,7 @@ const ColorScheme* _FindScheme(const std::vector<ColorScheme>& schemes,
|
||||
|
||||
// Method Description:
|
||||
// - Create a TerminalSettings from this object. Apply our settings, as well as
|
||||
// any colors from our colorscheme, if we have one.
|
||||
// any colors from our color scheme, if we have one.
|
||||
// Arguments:
|
||||
// - schemes: a list of schemes to look for our color scheme in, if we have one.
|
||||
// Return Value:
|
||||
@@ -169,6 +185,21 @@ TerminalSettings Profile::CreateTerminalSettings(const std::vector<ColorScheme>&
|
||||
terminalSettings.ScrollState(result);
|
||||
}
|
||||
|
||||
if (_backgroundImage)
|
||||
{
|
||||
terminalSettings.BackgroundImage(_backgroundImage.value());
|
||||
}
|
||||
|
||||
if (_backgroundImageOpacity)
|
||||
{
|
||||
terminalSettings.BackgroundImageOpacity(_backgroundImageOpacity.value());
|
||||
}
|
||||
|
||||
if (_backgroundImageStretchMode)
|
||||
{
|
||||
terminalSettings.BackgroundImageStretchMode(_backgroundImageStretchMode.value());
|
||||
}
|
||||
|
||||
return terminalSettings;
|
||||
}
|
||||
|
||||
@@ -270,6 +301,25 @@ JsonObject Profile::ToJson() const
|
||||
jsonObject.Insert(ICON_KEY, icon);
|
||||
}
|
||||
|
||||
if (_backgroundImage)
|
||||
{
|
||||
const auto backgroundImage = JsonValue::CreateStringValue(_backgroundImage.value());
|
||||
jsonObject.Insert(BACKGROUNDIMAGE_KEY, backgroundImage);
|
||||
}
|
||||
|
||||
if (_backgroundImageOpacity)
|
||||
{
|
||||
const auto opacity = JsonValue::CreateNumberValue(_backgroundImageOpacity.value());
|
||||
jsonObject.Insert(BACKGROUNDIMAGEOPACITY_KEY, opacity);
|
||||
}
|
||||
|
||||
if (_backgroundImageStretchMode)
|
||||
{
|
||||
const auto imageStretchMode = JsonValue::CreateStringValue(
|
||||
SerializeImageStretchMode(_backgroundImageStretchMode.value()));
|
||||
jsonObject.Insert(BACKGROUNDIMAGESTRETCHMODE_KEY, imageStretchMode);
|
||||
}
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
@@ -315,23 +365,25 @@ Profile Profile::FromJson(winrt::Windows::Data::Json::JsonObject json)
|
||||
{
|
||||
result._schemeName = json.GetNamedString(COLORSCHEME_KEY);
|
||||
}
|
||||
else
|
||||
else if (json.HasKey(COLORSCHEME_KEY_OLD))
|
||||
{
|
||||
if (json.HasKey(COLORTABLE_KEY))
|
||||
// TODO: deprecate old settings key
|
||||
result._schemeName = json.GetNamedString(COLORSCHEME_KEY_OLD);
|
||||
}
|
||||
else if (json.HasKey(COLORTABLE_KEY))
|
||||
{
|
||||
const auto table = json.GetNamedArray(COLORTABLE_KEY);
|
||||
int i = 0;
|
||||
for (auto v : table)
|
||||
{
|
||||
const auto table = json.GetNamedArray(COLORTABLE_KEY);
|
||||
int i = 0;
|
||||
for (auto v : table)
|
||||
if (v.ValueType() == JsonValueType::String)
|
||||
{
|
||||
if (v.ValueType() == JsonValueType::String)
|
||||
{
|
||||
const auto str = v.GetString();
|
||||
// TODO: MSFT:20737698 - if this fails, display an approriate error
|
||||
const auto color = Utils::ColorFromHexString(str.c_str());
|
||||
result._colorTable[i] = color;
|
||||
}
|
||||
i++;
|
||||
const auto str = v.GetString();
|
||||
// TODO: MSFT:20737698 - if this fails, display an approriate error
|
||||
const auto color = Utils::ColorFromHexString(str.c_str());
|
||||
result._colorTable[i] = color;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (json.HasKey(HISTORYSIZE_KEY))
|
||||
@@ -401,6 +453,19 @@ Profile Profile::FromJson(winrt::Windows::Data::Json::JsonObject json)
|
||||
{
|
||||
result._icon = json.GetNamedString(ICON_KEY);
|
||||
}
|
||||
if (json.HasKey(BACKGROUNDIMAGE_KEY))
|
||||
{
|
||||
result._backgroundImage = json.GetNamedString(BACKGROUNDIMAGE_KEY);
|
||||
}
|
||||
if (json.HasKey(BACKGROUNDIMAGEOPACITY_KEY))
|
||||
{
|
||||
result._backgroundImageOpacity = json.GetNamedNumber(BACKGROUNDIMAGEOPACITY_KEY);
|
||||
}
|
||||
if (json.HasKey(BACKGROUNDIMAGESTRETCHMODE_KEY))
|
||||
{
|
||||
const auto stretchMode = json.GetNamedString(BACKGROUNDIMAGESTRETCHMODE_KEY);
|
||||
result._backgroundImageStretchMode = ParseImageStretchMode(stretchMode.c_str());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -457,6 +522,15 @@ bool Profile::HasIcon() const noexcept
|
||||
return _icon.has_value();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets this profile's icon path.
|
||||
// Arguments:
|
||||
// - path: the path
|
||||
void Profile::SetIconPath(std::wstring_view path) noexcept
|
||||
{
|
||||
_icon.emplace(path);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns this profile's icon path, if one is set. Otherwise returns the empty string.
|
||||
// Return Value:
|
||||
@@ -538,6 +612,58 @@ ScrollbarState Profile::ParseScrollbarState(const std::wstring& scrollbarState)
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper function for converting a user-specified image stretch mode
|
||||
// to the appropriate enum value
|
||||
// Arguments:
|
||||
// - The value from the profiles.json file
|
||||
// Return Value:
|
||||
// - The corresponding enum value which maps to the string provided by the user
|
||||
winrt::Windows::UI::Xaml::Media::Stretch Profile::ParseImageStretchMode(const std::wstring& imageStretchMode)
|
||||
{
|
||||
if (imageStretchMode == IMAGESTRETCHMODE_NONE)
|
||||
{
|
||||
return winrt::Windows::UI::Xaml::Media::Stretch::None;
|
||||
}
|
||||
else if (imageStretchMode == IMAGESTRETCHMODE_FILL)
|
||||
{
|
||||
return winrt::Windows::UI::Xaml::Media::Stretch::Fill;
|
||||
}
|
||||
else if (imageStretchMode == IMAGESTRETCHMODE_UNIFORM)
|
||||
{
|
||||
return winrt::Windows::UI::Xaml::Media::Stretch::Uniform;
|
||||
}
|
||||
else // Fall through to default behavior
|
||||
{
|
||||
return winrt::Windows::UI::Xaml::Media::Stretch::UniformToFill;
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper function for converting an ImageStretchMode to the
|
||||
// correct string value.
|
||||
// Arguments:
|
||||
// - imageStretchMode: The enum value to convert to a string.
|
||||
// Return Value:
|
||||
// - The string value for the given ImageStretchMode
|
||||
std::wstring_view Profile::SerializeImageStretchMode(const winrt::Windows::UI::Xaml::Media::Stretch imageStretchMode)
|
||||
{
|
||||
switch (imageStretchMode)
|
||||
{
|
||||
case winrt::Windows::UI::Xaml::Media::Stretch::None:
|
||||
return IMAGESTRETCHMODE_NONE;
|
||||
case winrt::Windows::UI::Xaml::Media::Stretch::Fill:
|
||||
return IMAGESTRETCHMODE_FILL;
|
||||
case winrt::Windows::UI::Xaml::Media::Stretch::Uniform:
|
||||
return IMAGESTRETCHMODE_UNIFORM;
|
||||
default:
|
||||
case winrt::Windows::UI::Xaml::Media::Stretch::UniformToFill:
|
||||
return IMAGESTRETCHMODE_UNIFORMTOFILL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Method Description:
|
||||
// - Helper function for converting a user-specified cursor style corresponding
|
||||
// CursorStyle enum value
|
||||
@@ -572,13 +698,13 @@ CursorStyle Profile::_ParseCursorShape(const std::wstring& cursorShapeString)
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper function for converting a CursorStyle to it's corresponding string
|
||||
// - Helper function for converting a CursorStyle to its corresponding string
|
||||
// value.
|
||||
// Arguments:
|
||||
// - cursorShape: The enum value to convert to a string.
|
||||
// Return Value:
|
||||
// - The string value for the given CursorStyle
|
||||
std::wstring Profile::_SerializeCursorStyle(const CursorStyle cursorShape)
|
||||
std::wstring_view Profile::_SerializeCursorStyle(const CursorStyle cursorShape)
|
||||
{
|
||||
switch (cursorShape)
|
||||
{
|
||||
|
||||
@@ -25,7 +25,9 @@ class TerminalApp::Profile final
|
||||
{
|
||||
|
||||
public:
|
||||
Profile(const winrt::guid& guid);
|
||||
Profile();
|
||||
|
||||
~Profile();
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::TerminalSettings CreateTerminalSettings(const std::vector<::TerminalApp::ColorScheme>& schemes) const;
|
||||
@@ -48,6 +50,7 @@ public:
|
||||
|
||||
bool HasIcon() const noexcept;
|
||||
std::wstring_view GetIconPath() const noexcept;
|
||||
void SetIconPath(std::wstring_view path) noexcept;
|
||||
|
||||
bool GetCloseOnExit() const noexcept;
|
||||
|
||||
@@ -56,8 +59,10 @@ private:
|
||||
static std::wstring EvaluateStartingDirectory(const std::wstring& directory);
|
||||
|
||||
static winrt::Microsoft::Terminal::Settings::ScrollbarState ParseScrollbarState(const std::wstring& scrollbarState);
|
||||
static winrt::Windows::UI::Xaml::Media::Stretch ParseImageStretchMode(const std::wstring& imageStretchMode);
|
||||
static std::wstring_view SerializeImageStretchMode(const winrt::Windows::UI::Xaml::Media::Stretch imageStretchMode);
|
||||
static winrt::Microsoft::Terminal::Settings::CursorStyle _ParseCursorShape(const std::wstring& cursorShapeString);
|
||||
static std::wstring _SerializeCursorStyle(const winrt::Microsoft::Terminal::Settings::CursorStyle cursorShape);
|
||||
static std::wstring_view _SerializeCursorStyle(const winrt::Microsoft::Terminal::Settings::CursorStyle cursorShape);
|
||||
|
||||
GUID _guid;
|
||||
std::wstring _name;
|
||||
@@ -81,6 +86,10 @@ private:
|
||||
double _acrylicTransparency;
|
||||
bool _useAcrylic;
|
||||
|
||||
std::optional<std::wstring> _backgroundImage;
|
||||
std::optional<double> _backgroundImageOpacity;
|
||||
std::optional<winrt::Windows::UI::Xaml::Media::Stretch> _backgroundImageStretchMode;
|
||||
|
||||
std::optional<std::wstring> _scrollbarState;
|
||||
bool _closeOnExit;
|
||||
std::wstring _padding;
|
||||
|
||||
@@ -20,7 +20,7 @@ Tab::~Tab()
|
||||
{
|
||||
// When we're destructed, winrt will automatically decrement the refcount
|
||||
// of our terminalcontrol.
|
||||
// Assuming that refcount hits 0, it'll destruct it on it's own, including
|
||||
// Assuming that refcount hits 0, it'll destruct it on its own, including
|
||||
// calling Close on the terminal and connection.
|
||||
}
|
||||
|
||||
@@ -87,6 +87,6 @@ void Tab::Scroll(int delta)
|
||||
{
|
||||
_control.GetControl().Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=](){
|
||||
const auto currentOffset = _control.GetScrollOffset();
|
||||
_control.ScrollViewport(currentOffset + delta);
|
||||
_control.KeyboardScrollViewport(currentOffset + delta);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
<ClInclude Include="GlobalAppSettings.h" />
|
||||
<ClInclude Include="Profile.h" />
|
||||
<ClInclude Include="CascadiaSettings.h" />
|
||||
<ClInclude Include="KeyChordSerialization.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="AppKeyBindings.h">
|
||||
<DependentUpon>AppKeyBindings.idl</DependentUpon>
|
||||
@@ -58,6 +59,7 @@
|
||||
<ClCompile Include="Profile.cpp" />
|
||||
<ClCompile Include="CascadiaSettings.cpp" />
|
||||
<ClCompile Include="CascadiaSettingsSerialization.cpp" />
|
||||
<ClCompile Include="KeyChordSerialization.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
@@ -128,4 +130,4 @@
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.1.190405001-prerelease\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.1.190405001-prerelease\build\native\Microsoft.UI.Xaml.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <winrt/Windows.Data.Json.h>
|
||||
#include <winrt/windows.ui.core.h>
|
||||
#include <winrt/Windows.ui.input.h>
|
||||
#include <winrt/Windows.UI.Text.h>
|
||||
#include <winrt/Windows.UI.Xaml.Controls.h>
|
||||
#include <winrt/Windows.UI.Xaml.Controls.Primitives.h>
|
||||
#include <winrt/Windows.ui.xaml.media.h>
|
||||
|
||||
@@ -11,28 +11,35 @@
|
||||
#define STARTF_USESTDHANDLES 0x00000100
|
||||
#endif
|
||||
|
||||
#include "ConhostConnection.g.cpp"
|
||||
|
||||
#include <conpty-universal.h>
|
||||
#include "../../types/inc/Utils.hpp"
|
||||
|
||||
using namespace ::Microsoft::Console;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
ConhostConnection::ConhostConnection(hstring const& commandline,
|
||||
hstring const& startingDirectory,
|
||||
uint32_t initialRows,
|
||||
uint32_t initialCols) :
|
||||
_connected{ false },
|
||||
_inPipe{ INVALID_HANDLE_VALUE },
|
||||
_outPipe{ INVALID_HANDLE_VALUE },
|
||||
_signalPipe{ INVALID_HANDLE_VALUE },
|
||||
_outputThreadId{ 0 },
|
||||
_hOutputThread{ INVALID_HANDLE_VALUE },
|
||||
_piConhost{ 0 },
|
||||
_closing{ false }
|
||||
ConhostConnection::ConhostConnection(const hstring& commandline,
|
||||
const hstring& startingDirectory,
|
||||
const uint32_t initialRows,
|
||||
const uint32_t initialCols,
|
||||
const guid& initialGuid) :
|
||||
_initialRows{ initialRows },
|
||||
_initialCols{ initialCols },
|
||||
_commandline{ commandline },
|
||||
_startingDirectory{ startingDirectory },
|
||||
_guid{ initialGuid }
|
||||
{
|
||||
_commandline = commandline;
|
||||
_startingDirectory = startingDirectory;
|
||||
_initialRows = initialRows;
|
||||
_initialCols = initialCols;
|
||||
if (_guid == guid())
|
||||
{
|
||||
_guid = Utils::CreateGuid();
|
||||
}
|
||||
}
|
||||
|
||||
winrt::guid ConhostConnection::Guid() const noexcept
|
||||
{
|
||||
return _guid;
|
||||
}
|
||||
|
||||
winrt::event_token ConhostConnection::TerminalOutput(Microsoft::Terminal::TerminalConnection::TerminalOutputEventArgs const& handler)
|
||||
@@ -57,30 +64,44 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
|
||||
void ConhostConnection::Start()
|
||||
{
|
||||
std::wstring cmdline = _commandline.c_str();
|
||||
std::wstring cmdline{ _commandline.c_str() };
|
||||
std::optional<std::wstring> startingDirectory;
|
||||
if (!_startingDirectory.empty())
|
||||
{
|
||||
startingDirectory = _startingDirectory;
|
||||
}
|
||||
|
||||
CreateConPty(cmdline,
|
||||
startingDirectory,
|
||||
static_cast<short>(_initialCols),
|
||||
static_cast<short>(_initialRows),
|
||||
&_inPipe,
|
||||
&_outPipe,
|
||||
&_signalPipe,
|
||||
&_piConhost);
|
||||
EnvironmentVariableMapW extraEnvVars;
|
||||
{
|
||||
// Convert connection Guid to string and ignore the enclosing '{}'.
|
||||
std::wstring wsGuid{ Utils::GuidToString(_guid) };
|
||||
wsGuid.pop_back();
|
||||
|
||||
const wchar_t* const pwszGuid{ wsGuid.data() + 1 };
|
||||
|
||||
// Ensure every connection has the unique identifier in the environment.
|
||||
extraEnvVars.emplace(L"WT_SESSION", pwszGuid);
|
||||
}
|
||||
|
||||
THROW_IF_FAILED(
|
||||
CreateConPty(cmdline,
|
||||
startingDirectory,
|
||||
static_cast<short>(_initialCols),
|
||||
static_cast<short>(_initialRows),
|
||||
&_inPipe,
|
||||
&_outPipe,
|
||||
&_signalPipe,
|
||||
&_piConhost,
|
||||
extraEnvVars));
|
||||
|
||||
_connected = true;
|
||||
|
||||
// Create our own output handling thread
|
||||
// Each console needs to make sure to drain the output from it's backing host.
|
||||
_outputThreadId = (DWORD)-1;
|
||||
// Each console needs to make sure to drain the output from its backing host.
|
||||
_outputThreadId = static_cast<DWORD>(-1);
|
||||
_hOutputThread = CreateThread(nullptr,
|
||||
0,
|
||||
(LPTHREAD_START_ROUTINE)StaticOutputThreadProc,
|
||||
StaticOutputThreadProc,
|
||||
this,
|
||||
0,
|
||||
&_outputThreadId);
|
||||
@@ -131,7 +152,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
CloseHandle(_piConhost.hProcess);
|
||||
}
|
||||
|
||||
DWORD ConhostConnection::StaticOutputThreadProc(LPVOID lpParameter)
|
||||
DWORD WINAPI ConhostConnection::StaticOutputThreadProc(LPVOID lpParameter)
|
||||
{
|
||||
ConhostConnection* const pInstance = (ConhostConnection*)lpParameter;
|
||||
return pInstance->_OutputThread();
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
struct ConhostConnection : ConhostConnectionT<ConhostConnection>
|
||||
{
|
||||
ConhostConnection(const hstring& cmdline, const hstring& startingDirectory, uint32_t rows, uint32_t cols);
|
||||
ConhostConnection(const hstring& cmdline, const hstring& startingDirectory, const uint32_t rows, const uint32_t cols, const guid& guid);
|
||||
|
||||
winrt::event_token TerminalOutput(TerminalConnection::TerminalOutputEventArgs const& handler);
|
||||
void TerminalOutput(winrt::event_token const& token) noexcept;
|
||||
@@ -20,26 +20,28 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
void Resize(uint32_t rows, uint32_t columns);
|
||||
void Close();
|
||||
|
||||
winrt::guid Guid() const noexcept;
|
||||
|
||||
private:
|
||||
winrt::event<TerminalConnection::TerminalOutputEventArgs> _outputHandlers;
|
||||
winrt::event<TerminalConnection::TerminalDisconnectedEventArgs> _disconnectHandlers;
|
||||
|
||||
uint32_t _initialRows;
|
||||
uint32_t _initialCols;
|
||||
hstring _commandline;
|
||||
hstring _startingDirectory;
|
||||
uint32_t _initialRows{};
|
||||
uint32_t _initialCols{};
|
||||
hstring _commandline{};
|
||||
hstring _startingDirectory{};
|
||||
|
||||
bool _connected;
|
||||
HANDLE _inPipe; // The pipe for writing input to
|
||||
HANDLE _outPipe; // The pipe for reading output from
|
||||
HANDLE _signalPipe;
|
||||
//HPCON _hPC;
|
||||
DWORD _outputThreadId;
|
||||
HANDLE _hOutputThread;
|
||||
PROCESS_INFORMATION _piConhost;
|
||||
bool _closing;
|
||||
bool _connected{};
|
||||
HANDLE _inPipe{ INVALID_HANDLE_VALUE }; // The pipe for writing input to
|
||||
HANDLE _outPipe{ INVALID_HANDLE_VALUE }; // The pipe for reading output from
|
||||
HANDLE _signalPipe{ INVALID_HANDLE_VALUE };
|
||||
DWORD _outputThreadId{};
|
||||
HANDLE _hOutputThread{ INVALID_HANDLE_VALUE };
|
||||
PROCESS_INFORMATION _piConhost{};
|
||||
guid _guid{}; // A "unique" session identifier for connected client
|
||||
bool _closing{};
|
||||
|
||||
static DWORD StaticOutputThreadProc(LPVOID lpParameter);
|
||||
static DWORD WINAPI StaticOutputThreadProc(LPVOID lpParameter);
|
||||
DWORD _OutputThread();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ namespace Microsoft.Terminal.TerminalConnection
|
||||
[default_interface]
|
||||
runtimeclass ConhostConnection : ITerminalConnection
|
||||
{
|
||||
ConhostConnection(String cmdline, String startingDirectory, UInt32 rows, UInt32 columns);
|
||||
ConhostConnection(String cmdline, String startingDirectory, UInt32 rows, UInt32 columns, Guid guid);
|
||||
|
||||
Guid Guid { get; };
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
_outputThreadId = (DWORD)-1;
|
||||
_hOutputThread = CreateThread(nullptr,
|
||||
0,
|
||||
(LPTHREAD_START_ROUTINE)StaticOutputThreadProc,
|
||||
StaticOutputThreadProc,
|
||||
this,
|
||||
0,
|
||||
&_outputThreadId);
|
||||
@@ -222,7 +222,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
}
|
||||
|
||||
|
||||
DWORD ConptyConnection::StaticOutputThreadProc(LPVOID lpParameter)
|
||||
DWORD WINAPI ConptyConnection::StaticOutputThreadProc(LPVOID lpParameter)
|
||||
{
|
||||
ConptyConnection* const pInstance = (ConptyConnection*)lpParameter;
|
||||
return pInstance->_OutputThread();
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
HANDLE _hOutputThread;
|
||||
PROCESS_INFORMATION _piClient;
|
||||
|
||||
static DWORD StaticOutputThreadProc(LPVOID lpParameter);
|
||||
static DWORD WINAPI StaticOutputThreadProc(LPVOID lpParameter);
|
||||
void _CreatePseudoConsole();
|
||||
DWORD _OutputThread();
|
||||
};
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "EchoConnection.h"
|
||||
#include <sstream>
|
||||
|
||||
#include "EchoConnection.g.cpp"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
{
|
||||
EchoConnection::EchoConnection()
|
||||
|
||||
@@ -52,6 +52,18 @@
|
||||
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<!-- ========================= Project References ======================== -->
|
||||
<ItemGroup>
|
||||
<!--
|
||||
the packaging project won't recurse through our dependencies, you have to
|
||||
make sure that if you add a cppwinrt dependency to any of these projects,
|
||||
you also update all the consumers
|
||||
-->
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj">
|
||||
<Project>{18D09A24-8240-42D6-8CB6-236EEE820263}</Project>
|
||||
</ProjectReference>
|
||||
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
DON'T REDIRECT OUR OUTPUT.
|
||||
|
||||
@@ -7,5 +7,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibraryIncludes.h>
|
||||
|
||||
// Must be included before any WinRT headers.
|
||||
#include <unknwn.h>
|
||||
|
||||
#include "winrt/Windows.Foundation.h"
|
||||
#include <Windows.h>
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <WinUser.h>
|
||||
#include "..\..\types\inc\GlyphWidth.hpp"
|
||||
|
||||
#include "TermControl.g.cpp"
|
||||
|
||||
using namespace ::Microsoft::Console::Types;
|
||||
using namespace ::Microsoft::Terminal::Core;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
@@ -26,7 +28,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
}
|
||||
|
||||
TermControl::TermControl(Settings::IControlSettings settings) :
|
||||
_connection{ TerminalConnection::ConhostConnection(winrt::to_hstring("cmd.exe"), winrt::hstring(), 30, 80) },
|
||||
_connection{ TerminalConnection::ConhostConnection(winrt::to_hstring("cmd.exe"), winrt::hstring(), 30, 80, winrt::guid()) },
|
||||
_initializedTerminal{ false },
|
||||
_root{ nullptr },
|
||||
_controlRoot{ nullptr },
|
||||
@@ -133,7 +135,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal,[this](){
|
||||
// Update our control settings
|
||||
_ApplyUISettings();
|
||||
// Update the terminal core with it's new Core settings
|
||||
// Update the terminal core with its new Core settings
|
||||
_terminal->UpdateSettings(_settings);
|
||||
|
||||
// Refresh our font with the renderer
|
||||
@@ -156,8 +158,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - Style our UI elements based on the values in our _settings, and set up
|
||||
// other control-specific settings. This method will be called whenever
|
||||
// the settings are reloaded.
|
||||
// * Sets up the background of the control with the provided BG color,
|
||||
// acrylic or not, and if acrylic, then uses the opacity from _settings.
|
||||
// * Calls _InitializeBackgroundBrush to set up the Xaml brush responsible
|
||||
// for the control's background
|
||||
// * Calls _BackgroundColorChanged to style the background of the control
|
||||
// - Core settings will be passed to the terminal in _InitializeTerminal
|
||||
// Arguments:
|
||||
// - <none>
|
||||
@@ -165,37 +168,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - <none>
|
||||
void TermControl::_ApplyUISettings()
|
||||
{
|
||||
winrt::Windows::UI::Color bgColor{};
|
||||
_InitializeBackgroundBrush();
|
||||
|
||||
uint32_t bg = _settings.DefaultBackground();
|
||||
const auto R = GetRValue(bg);
|
||||
const auto G = GetGValue(bg);
|
||||
const auto B = GetBValue(bg);
|
||||
bgColor.R = R;
|
||||
bgColor.G = G;
|
||||
bgColor.B = B;
|
||||
bgColor.A = 255;
|
||||
|
||||
if (_settings.UseAcrylic())
|
||||
{
|
||||
Media::AcrylicBrush acrylic{};
|
||||
acrylic.BackgroundSource(Media::AcrylicBackgroundSource::HostBackdrop);
|
||||
acrylic.FallbackColor(bgColor);
|
||||
acrylic.TintColor(bgColor);
|
||||
acrylic.TintOpacity(_settings.TintOpacity());
|
||||
_root.Background(acrylic);
|
||||
|
||||
// If we're acrylic, we want to make sure that the default BG color
|
||||
// is transparent, so we can see the acrylic effect on text with the
|
||||
// default BG color.
|
||||
_settings.DefaultBackground(ARGB(0, R, G, B));
|
||||
}
|
||||
else
|
||||
{
|
||||
Media::SolidColorBrush solidColor{};
|
||||
solidColor.Color(bgColor);
|
||||
_root.Background(solidColor);
|
||||
_settings.DefaultBackground(RGB(R, G, B));
|
||||
}
|
||||
_BackgroundColorChanged(bg);
|
||||
|
||||
// Apply padding to the root Grid
|
||||
auto thickness = _ParseThicknessFromPadding(_settings.Padding());
|
||||
@@ -214,6 +190,147 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
_desiredFont = { _actualFont };
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Set up the brush used to display the control's background.
|
||||
// - Respects the settings for acrylic, background image and opacity from
|
||||
// _settings.
|
||||
// * Prioritizes the acrylic background if chosen, respecting acrylicOpacity
|
||||
// from _settings.
|
||||
// * If acrylic is not enabled and a backgroundImage is present, it is used,
|
||||
// respecting the opacity and stretch mode settings from _settings.
|
||||
// * Falls back to a solid color background from _settings if acrylic is not
|
||||
// enabled and no background image is present.
|
||||
// - Avoids image flickering and acrylic brush redraw if settings are changed
|
||||
// but the appropriate brush is still in place.
|
||||
// - Does not apply background color; _BackgroundColorChanged must be called
|
||||
// to do so.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TermControl::_InitializeBackgroundBrush()
|
||||
{
|
||||
if (_settings.UseAcrylic())
|
||||
{
|
||||
// See if we've already got an acrylic background brush
|
||||
// to avoid the flicker when setting up a new one
|
||||
auto acrylic = _root.Background().try_as<Media::AcrylicBrush>();
|
||||
|
||||
// Instantiate a brush if there's not already one there
|
||||
if (acrylic == nullptr)
|
||||
{
|
||||
acrylic = Media::AcrylicBrush{};
|
||||
acrylic.BackgroundSource(Media::AcrylicBackgroundSource::HostBackdrop);
|
||||
}
|
||||
|
||||
// Apply brush settings
|
||||
acrylic.TintOpacity(_settings.TintOpacity());
|
||||
|
||||
// Apply brush to control if it's not already there
|
||||
if (_root.Background() != acrylic)
|
||||
{
|
||||
_root.Background(acrylic);
|
||||
}
|
||||
}
|
||||
else if (!_settings.BackgroundImage().empty())
|
||||
{
|
||||
Windows::Foundation::Uri imageUri{ _settings.BackgroundImage() };
|
||||
|
||||
// Check if the existing brush is an image brush, and if not
|
||||
// construct a new one
|
||||
auto brush = _root.Background().try_as<Media::ImageBrush>();
|
||||
|
||||
if (brush == nullptr)
|
||||
{
|
||||
brush = Media::ImageBrush{};
|
||||
}
|
||||
|
||||
// Check if the image brush is already pointing to the image
|
||||
// in the modified settings; if it isn't (or isn't there),
|
||||
// set a new image source for the brush
|
||||
auto imageSource = brush.ImageSource().try_as<Media::Imaging::BitmapImage>();
|
||||
|
||||
if (imageSource == nullptr || imageSource.UriSource() == nullptr
|
||||
|| imageSource.UriSource().RawUri() != imageUri.RawUri())
|
||||
{
|
||||
// Note that BitmapImage handles the image load asynchronously,
|
||||
// which is especially important since the image
|
||||
// may well be both large and somewhere out on the
|
||||
// internet.
|
||||
Media::Imaging::BitmapImage image(imageUri);
|
||||
brush.ImageSource(image);
|
||||
}
|
||||
|
||||
// Apply stretch and opacity settings
|
||||
brush.Stretch(_settings.BackgroundImageStretchMode());
|
||||
brush.Opacity(_settings.BackgroundImageOpacity());
|
||||
|
||||
// Apply brush if it isn't already there
|
||||
if (_root.Background() != brush)
|
||||
{
|
||||
_root.Background(brush);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Media::SolidColorBrush solidColor{};
|
||||
_root.Background(solidColor);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Style the background of the control with the provided background color
|
||||
// Arguments:
|
||||
// - color: The background color to use as a uint32 (aka DWORD COLORREF)
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TermControl::_BackgroundColorChanged(const uint32_t color)
|
||||
{
|
||||
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this, color]() {
|
||||
const auto R = GetRValue(color);
|
||||
const auto G = GetGValue(color);
|
||||
const auto B = GetBValue(color);
|
||||
|
||||
winrt::Windows::UI::Color bgColor{};
|
||||
bgColor.R = R;
|
||||
bgColor.G = G;
|
||||
bgColor.B = B;
|
||||
bgColor.A = 255;
|
||||
|
||||
if (_settings.UseAcrylic())
|
||||
{
|
||||
if (auto acrylic = _root.Background().try_as<Media::AcrylicBrush>())
|
||||
{
|
||||
acrylic.FallbackColor(bgColor);
|
||||
acrylic.TintColor(bgColor);
|
||||
}
|
||||
|
||||
// If we're acrylic, we want to make sure that the default BG color
|
||||
// is transparent, so we can see the acrylic effect on text with the
|
||||
// default BG color.
|
||||
_settings.DefaultBackground(ARGB(0, R, G, B));
|
||||
}
|
||||
else if (!_settings.BackgroundImage().empty())
|
||||
{
|
||||
// This currently applies no changes to the image background
|
||||
// brush itself.
|
||||
|
||||
// Set the default background as transparent to prevent the
|
||||
// DX layer from overwriting the background image
|
||||
_settings.DefaultBackground(ARGB(0, R, G, B));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto solidColor = _root.Background().try_as<Media::SolidColorBrush>())
|
||||
{
|
||||
solidColor.Color(bgColor);
|
||||
}
|
||||
|
||||
_settings.DefaultBackground(RGB(R, G, B));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Create a connection based on the values in our settings object.
|
||||
// * Gets the commandline and working directory out of the _settings and
|
||||
@@ -221,7 +338,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// directory.
|
||||
void TermControl::_ApplyConnectionSettings()
|
||||
{
|
||||
_connection = TerminalConnection::ConhostConnection(_settings.Commandline(), _settings.StartingDirectory(), 30, 80);
|
||||
_connection = TerminalConnection::ConhostConnection(_settings.Commandline(), _settings.StartingDirectory(), 30, 80, winrt::guid());
|
||||
}
|
||||
|
||||
TermControl::~TermControl()
|
||||
@@ -278,22 +395,28 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
const auto windowWidth = _swapChainPanel.ActualWidth(); // Width() and Height() are NaN?
|
||||
const auto windowHeight = _swapChainPanel.ActualHeight();
|
||||
|
||||
_terminal = new ::Microsoft::Terminal::Core::Terminal();
|
||||
_terminal = std::make_unique<::Microsoft::Terminal::Core::Terminal>();
|
||||
|
||||
// First create the render thread.
|
||||
// Then stash a local pointer to the render thread so we can initialize it and enable it
|
||||
// to paint itself *after* we hand off its ownership to the renderer.
|
||||
// We split up construction and initialization of the render thread object this way
|
||||
// because the renderer and render thread have circular references to each other.
|
||||
auto renderThread = std::make_unique<::Microsoft::Console::Render::RenderThread>();
|
||||
// Stash a local pointer to the render thread, so we can enable it after
|
||||
// we hand off ownership to the renderer.
|
||||
auto* const localPointerToThread = renderThread.get();
|
||||
_renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(_terminal, nullptr, 0, std::move(renderThread));
|
||||
|
||||
// Now create the renderer and initialize the render thread.
|
||||
_renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(_terminal.get(), nullptr, 0, std::move(renderThread));
|
||||
::Microsoft::Console::Render::IRenderTarget& renderTarget = *_renderer;
|
||||
|
||||
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
|
||||
|
||||
// Set up the DX Engine
|
||||
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
|
||||
_renderer->AddRenderEngine(dxEngine.get());
|
||||
|
||||
// Set up the renderer to be used to calculate the width of a glyph,
|
||||
// should we be unable to figure out it's width another way.
|
||||
// should we be unable to figure out its width another way.
|
||||
auto pfn = std::bind(&::Microsoft::Console::Render::Renderer::IsGlyphWideByFont, _renderer.get(), std::placeholders::_1);
|
||||
SetGlyphWidthFallback(pfn);
|
||||
|
||||
@@ -334,8 +457,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
auto inputFn = std::bind(&TermControl::_SendInputToConnection, this, std::placeholders::_1);
|
||||
_terminal->SetWriteInputCallback(inputFn);
|
||||
|
||||
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
|
||||
|
||||
auto chain = _renderEngine->GetSwapChain();
|
||||
_swapChainPanel.Dispatcher().RunAsync(CoreDispatcherPriority::High, [this, chain]()
|
||||
{
|
||||
@@ -404,6 +525,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
auto pfnTitleChanged = std::bind(&TermControl::_TerminalTitleChanged, this, std::placeholders::_1);
|
||||
_terminal->SetTitleChangedCallback(pfnTitleChanged);
|
||||
|
||||
auto pfnBackgroundColorChanged = std::bind(&TermControl::_BackgroundColorChanged, this, std::placeholders::_1);
|
||||
_terminal->SetBackgroundCallback(pfnBackgroundColorChanged);
|
||||
|
||||
auto pfnScrollPositionChanged = std::bind(&TermControl::_TerminalScrollPositionChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
|
||||
_terminal->SetScrollPositionChangedCallback(pfnScrollPositionChanged);
|
||||
|
||||
@@ -415,9 +539,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
_cursorTimer = std::make_optional(DispatcherTimer());
|
||||
_cursorTimer.value().Interval(std::chrono::milliseconds(blinkTime));
|
||||
_cursorTimer.value().Tick({ this, &TermControl::_BlinkCursor });
|
||||
|
||||
_controlRoot.GotFocus({ this, &TermControl::_GotFocusHandler });
|
||||
_controlRoot.LostFocus({ this, &TermControl::_LostFocusHandler });
|
||||
_cursorTimer.value().Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -425,6 +547,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
_cursorTimer = std::nullopt;
|
||||
}
|
||||
|
||||
_controlRoot.GotFocus({ this, &TermControl::_GotFocusHandler });
|
||||
_controlRoot.LostFocus({ this, &TermControl::_LostFocusHandler });
|
||||
|
||||
// Focus the control here. If we do it up above (in _Create_), then the
|
||||
// focus won't actually get passed to us. I believe this is because
|
||||
// we're not technically a part of the UI tree yet, so focusing us
|
||||
@@ -533,6 +658,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
}
|
||||
}
|
||||
|
||||
// Manually prevent keyboard navigation with tab. We want to send tab to
|
||||
// the terminal, and we don't want to be able to escape focus of the
|
||||
// control with tab.
|
||||
if (e.OriginalKey() == VirtualKey::Tab)
|
||||
{
|
||||
handled = true;
|
||||
}
|
||||
|
||||
e.Handled(handled);
|
||||
}
|
||||
|
||||
@@ -549,6 +682,15 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse)
|
||||
{
|
||||
// Ignore mouse events while the terminal does not have focus.
|
||||
// This prevents the user from selecting and copying text if they
|
||||
// click inside the current tab to refocus the terminal window.
|
||||
if (!_focused)
|
||||
{
|
||||
args.Handled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto modifiers = args.KeyModifiers();
|
||||
const auto altEnabled = WI_IsFlagSet(modifiers, VirtualKeyModifiers::Menu);
|
||||
const auto shiftEnabled = WI_IsFlagSet(modifiers, VirtualKeyModifiers::Shift);
|
||||
@@ -556,13 +698,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
if (point.Properties().IsLeftButtonPressed())
|
||||
{
|
||||
const auto cursorPosition = point.Position();
|
||||
|
||||
const auto fontSize = _actualFont.GetSize();
|
||||
|
||||
const COORD terminalPosition = {
|
||||
static_cast<SHORT>(cursorPosition.X / fontSize.X),
|
||||
static_cast<SHORT>(cursorPosition.Y / fontSize.Y)
|
||||
};
|
||||
const auto terminalPosition = _GetTerminalPosition(cursorPosition);
|
||||
|
||||
// save location before rendering
|
||||
_terminal->SetSelectionAnchor(terminalPosition);
|
||||
@@ -618,13 +754,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
if (point.Properties().IsLeftButtonPressed())
|
||||
{
|
||||
const auto cursorPosition = point.Position();
|
||||
|
||||
const auto fontSize = _actualFont.GetSize();
|
||||
|
||||
const COORD terminalPosition = {
|
||||
static_cast<SHORT>(cursorPosition.X / fontSize.X),
|
||||
static_cast<SHORT>(cursorPosition.Y / fontSize.Y)
|
||||
};
|
||||
const auto terminalPosition = _GetTerminalPosition(cursorPosition);
|
||||
|
||||
// save location (for rendering) + render
|
||||
_terminal->SetEndSelectionPosition(terminalPosition);
|
||||
@@ -828,8 +958,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_GotFocusHandler(Windows::Foundation::IInspectable const& /* sender */,
|
||||
RoutedEventArgs const& /* args */)
|
||||
{
|
||||
_focused = true;
|
||||
|
||||
if (_cursorTimer.has_value())
|
||||
{
|
||||
_cursorTimer.value().Start();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -838,6 +972,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_LostFocusHandler(Windows::Foundation::IInspectable const& /* sender */,
|
||||
RoutedEventArgs const& /* args */)
|
||||
{
|
||||
_focused = false;
|
||||
|
||||
if (_cursorTimer.has_value())
|
||||
{
|
||||
_cursorTimer.value().Stop();
|
||||
@@ -907,6 +1043,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_BlinkCursor(Windows::Foundation::IInspectable const& /* sender */,
|
||||
Windows::Foundation::IInspectable const& /* e */)
|
||||
{
|
||||
if (!_terminal->IsCursorBlinkingAllowed() && _terminal->IsCursorVisible())
|
||||
{
|
||||
return;
|
||||
}
|
||||
_terminal->SetCursorVisible(!_terminal->IsCursorVisible());
|
||||
}
|
||||
|
||||
@@ -1035,6 +1175,20 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
_terminal->UserScrollViewport(viewTop);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Scrolls the viewport of the terminal and updates the scroll bar accordingly
|
||||
// Arguments:
|
||||
// - viewTop: the viewTop to scroll to
|
||||
// The difference between this function and ScrollViewport is that this one also
|
||||
// updates the _scrollBar after the viewport scroll. The reason _scrollBar is not updated in
|
||||
// ScrollViewport is because ScrollViewport is being called by _ScrollbarChangeHandler
|
||||
void TermControl::KeyboardScrollViewport(int viewTop)
|
||||
{
|
||||
_terminal->UserScrollViewport(viewTop);
|
||||
_lastScrollOffset = std::nullopt;
|
||||
_scrollBar.Value(static_cast<int>(viewTop));
|
||||
}
|
||||
|
||||
int TermControl::GetScrollOffset()
|
||||
{
|
||||
return _terminal->GetScrollOffset();
|
||||
@@ -1077,8 +1231,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
FontInfo actualFont = { fontFace, 0, 10, { 0, fontHeight }, CP_UTF8, false };
|
||||
FontInfoDesired desiredFont = { actualFont };
|
||||
|
||||
const auto cols = settings.InitialCols();
|
||||
const auto rows = settings.InitialRows();
|
||||
// If the settings have negative or zero row or column counts, ignore those counts.
|
||||
// (The lower TerminalCore layer also has upper bounds as well, but at this layer
|
||||
// we may eventually impose different ones depending on how many pixels we can address.)
|
||||
const auto cols = std::max(settings.InitialCols(), 1);
|
||||
const auto rows = std::max(settings.InitialRows(), 1);
|
||||
|
||||
// Create a DX engine and initialize it with our font and DPI. We'll
|
||||
// then use it to measure how much space the requested rows and columns
|
||||
@@ -1199,6 +1356,35 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
(shift ? Settings::KeyModifiers::Shift : Settings::KeyModifiers::None) };
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Gets the corresponding viewport terminal position for the cursor
|
||||
// by excluding the padding and normalizing with the font size.
|
||||
// This is used for selection.
|
||||
// Arguments:
|
||||
// - cursorPosition: the (x,y) position of a given cursor (i.e.: mouse cursor).
|
||||
// NOTE: origin (0,0) is top-left.
|
||||
// Return Value:
|
||||
// - the corresponding viewport terminal position for the given Point parameter
|
||||
const COORD TermControl::_GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition)
|
||||
{
|
||||
// Exclude padding from cursor position calculation
|
||||
COORD terminalPosition =
|
||||
{
|
||||
static_cast<SHORT>(cursorPosition.X - _root.Padding().Left),
|
||||
static_cast<SHORT>(cursorPosition.Y - _root.Padding().Top)
|
||||
};
|
||||
|
||||
const auto fontSize = _actualFont.GetSize();
|
||||
FAIL_FAST_IF(fontSize.X == 0);
|
||||
FAIL_FAST_IF(fontSize.Y == 0);
|
||||
|
||||
// Normalize to terminal coordinates by using font size
|
||||
terminalPosition.X /= fontSize.X;
|
||||
terminalPosition.Y /= fontSize.Y;
|
||||
|
||||
return terminalPosition;
|
||||
}
|
||||
|
||||
// -------------------------------- 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.
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void Close();
|
||||
|
||||
void ScrollViewport(int viewTop);
|
||||
void KeyboardScrollViewport(int viewTop);
|
||||
int GetScrollOffset();
|
||||
int GetViewHeight() const;
|
||||
|
||||
@@ -69,12 +70,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
Windows::UI::Xaml::Controls::Primitives::ScrollBar _scrollBar;
|
||||
event_token _connectionOutputEventToken;
|
||||
|
||||
::Microsoft::Terminal::Core::Terminal* _terminal;
|
||||
std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal;
|
||||
|
||||
std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer;
|
||||
std::unique_ptr<::Microsoft::Console::Render::DxEngine> _renderEngine;
|
||||
|
||||
Settings::IControlSettings _settings;
|
||||
bool _focused;
|
||||
bool _closing;
|
||||
|
||||
FontInfoDesired _desiredFont;
|
||||
@@ -93,6 +95,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
void _Create();
|
||||
void _ApplyUISettings();
|
||||
void _InitializeBackgroundBrush();
|
||||
void _BackgroundColorChanged(const uint32_t color);
|
||||
void _ApplyConnectionSettings();
|
||||
void _InitializeTerminal();
|
||||
void _UpdateFont();
|
||||
@@ -123,6 +127,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
Settings::KeyModifiers _GetPressedModifierKeys() const;
|
||||
|
||||
const COORD _GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ namespace Microsoft.Terminal.TerminalControl
|
||||
void Close();
|
||||
|
||||
void ScrollViewport(Int32 viewTop);
|
||||
void KeyboardScrollViewport(Int32 viewTop);
|
||||
Int32 GetScrollOffset();
|
||||
Int32 GetViewHeight();
|
||||
event ScrollPositionChangedEventArgs ScrollPositionChanged;
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <winrt/Windows.UI.Xaml.Controls.h>
|
||||
#include <winrt/Windows.UI.Xaml.Controls.Primitives.h>
|
||||
#include <winrt/Windows.ui.xaml.media.h>
|
||||
#include <winrt/Windows.ui.xaml.media.imaging.h>
|
||||
#include <winrt/Windows.ui.xaml.input.h>
|
||||
|
||||
#include <windows.ui.xaml.media.dxinterop.h>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../terminal/adapter/DispatchTypes.hpp"
|
||||
|
||||
namespace Microsoft::Terminal::Core
|
||||
{
|
||||
class ITerminalApi
|
||||
@@ -27,5 +29,10 @@ namespace Microsoft::Terminal::Core
|
||||
virtual bool SetWindowTitle(std::wstring_view title) = 0;
|
||||
|
||||
virtual bool SetColorTableEntry(const size_t tableIndex, const DWORD dwColor) = 0;
|
||||
|
||||
virtual bool SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle) = 0;
|
||||
|
||||
virtual bool SetDefaultForeground(const DWORD dwColor) = 0;
|
||||
virtual bool SetDefaultBackground(const DWORD dwColor) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,7 +18,12 @@ using namespace Microsoft::Console::Render;
|
||||
using namespace Microsoft::Console::Types;
|
||||
using namespace Microsoft::Console::VirtualTerminal;
|
||||
|
||||
std::wstring _KeyEventsToText(std::deque<std::unique_ptr<IInputEvent>>& inEventsToWrite)
|
||||
static constexpr short _ClampToShortMax(int value, short min)
|
||||
{
|
||||
return static_cast<short>(std::clamp(value, static_cast<int>(min), SHRT_MAX));
|
||||
}
|
||||
|
||||
static std::wstring _KeyEventsToText(std::deque<std::unique_ptr<IInputEvent>>& inEventsToWrite)
|
||||
{
|
||||
std::wstring wstr = L"";
|
||||
for(auto& ev : inEventsToWrite)
|
||||
@@ -65,23 +70,23 @@ void Terminal::Create(COORD viewportSize, SHORT scrollbackLines, IRenderTarget&
|
||||
{
|
||||
_mutableViewport = Viewport::FromDimensions({ 0,0 }, viewportSize);
|
||||
_scrollbackLines = scrollbackLines;
|
||||
COORD bufferSize { viewportSize.X, viewportSize.Y + scrollbackLines };
|
||||
TextAttribute attr{};
|
||||
UINT cursorSize = 12;
|
||||
const COORD bufferSize { viewportSize.X, _ClampToShortMax(viewportSize.Y + scrollbackLines, 1) };
|
||||
const TextAttribute attr{};
|
||||
const UINT cursorSize = 12;
|
||||
_buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, renderTarget);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Initializes the Temrinal from the given set of settings.
|
||||
// - Initializes the Terminal from the given set of settings.
|
||||
// Arguments:
|
||||
// - settings: the set of CoreSettings we need to use to initialize the terminal
|
||||
// - renderTarget: A render target the terminal can use for paint invalidation.
|
||||
void Terminal::CreateFromSettings(winrt::Microsoft::Terminal::Settings::ICoreSettings settings,
|
||||
Microsoft::Console::Render::IRenderTarget& renderTarget)
|
||||
{
|
||||
const COORD viewportSize{ static_cast<short>(settings.InitialCols()), static_cast<short>(settings.InitialRows()) };
|
||||
const COORD viewportSize{ _ClampToShortMax(settings.InitialCols(), 1), _ClampToShortMax(settings.InitialRows(), 1) };
|
||||
// TODO:MSFT:20642297 - Support infinite scrollback here, if HistorySize is -1
|
||||
Create(viewportSize, static_cast<short>(settings.HistorySize()), renderTarget);
|
||||
Create(viewportSize, _ClampToShortMax(settings.HistorySize(), 0), renderTarget);
|
||||
|
||||
UpdateSettings(settings);
|
||||
}
|
||||
@@ -437,6 +442,15 @@ void Terminal::SetScrollPositionChangedCallback(std::function<void(const int, co
|
||||
_pfnScrollPositionChanged = pfn;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Allows setting a callback for when the background color is changed
|
||||
// Arguments:
|
||||
// - pfn: a function callback that takes a uint32 (DWORD COLORREF) color in the format 0x00BBGGRR
|
||||
void Terminal::SetBackgroundCallback(std::function<void(const uint32_t)> pfn) noexcept
|
||||
{
|
||||
_pfnBackgroundColorChanged = pfn;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Checks if selection is active
|
||||
// Return Value:
|
||||
@@ -600,3 +614,9 @@ void Terminal::SetCursorVisible(const bool isVisible) noexcept
|
||||
auto& cursor = _buffer->GetCursor();
|
||||
cursor.SetIsVisible(isVisible);
|
||||
}
|
||||
|
||||
bool Terminal::IsCursorBlinkingAllowed() const noexcept
|
||||
{
|
||||
const auto& cursor = _buffer->GetCursor();
|
||||
return cursor.IsBlinkingAllowed();
|
||||
}
|
||||
|
||||
@@ -70,7 +70,10 @@ public:
|
||||
COORD GetCursorPosition() override;
|
||||
bool EraseCharacters(const unsigned int numChars) override;
|
||||
bool SetWindowTitle(std::wstring_view title) override;
|
||||
bool SetColorTableEntry(const size_t tableIndex, const DWORD dwColor) override;
|
||||
bool SetColorTableEntry(const size_t tableIndex, const COLORREF dwColor) override;
|
||||
bool SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle) override;
|
||||
bool SetDefaultForeground(const COLORREF dwColor) override;
|
||||
bool SetDefaultBackground(const COLORREF dwColor) override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region ITerminalInput
|
||||
@@ -112,8 +115,10 @@ public:
|
||||
void SetWriteInputCallback(std::function<void(std::wstring&)> pfn) noexcept;
|
||||
void SetTitleChangedCallback(std::function<void(const std::wstring_view&)> pfn) noexcept;
|
||||
void SetScrollPositionChangedCallback(std::function<void(const int, const int, const int)> pfn) noexcept;
|
||||
void SetBackgroundCallback(std::function<void(const uint32_t)> pfn) noexcept;
|
||||
|
||||
void SetCursorVisible(const bool isVisible) noexcept;
|
||||
bool IsCursorBlinkingAllowed() const noexcept;
|
||||
|
||||
#pragma region TextSelection
|
||||
const bool IsSelectionActive() const noexcept;
|
||||
@@ -129,6 +134,7 @@ public:
|
||||
std::function<void(std::wstring&)> _pfnWriteInput;
|
||||
std::function<void(const std::wstring_view&)> _pfnTitleChanged;
|
||||
std::function<void(const int, const int, const int)> _pfnScrollPositionChanged;
|
||||
std::function<void(const uint32_t)> _pfnBackgroundColorChanged;
|
||||
|
||||
std::unique_ptr<::Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine;
|
||||
std::unique_ptr<::Microsoft::Console::VirtualTerminal::TerminalInput> _terminalInput;
|
||||
|
||||
@@ -157,7 +157,7 @@ bool Terminal::SetWindowTitle(std::wstring_view title)
|
||||
// - dwColor: the new COLORREF to use as that color table value.
|
||||
// Return Value:
|
||||
// - true iff we successfully updated the color table entry.
|
||||
bool Terminal::SetColorTableEntry(const size_t tableIndex, const DWORD dwColor)
|
||||
bool Terminal::SetColorTableEntry(const size_t tableIndex, const COLORREF dwColor)
|
||||
{
|
||||
if (tableIndex > _colorTable.size())
|
||||
{
|
||||
@@ -169,3 +169,84 @@ bool Terminal::SetColorTableEntry(const size_t tableIndex, const DWORD dwColor)
|
||||
_buffer->GetRenderTarget().TriggerRedrawAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets the cursor style to the given style.
|
||||
// Arguments:
|
||||
// - cursorStyle: the style to be set for the cursor
|
||||
// Return Value:
|
||||
// - true iff we successfully set the cursor style
|
||||
bool Terminal::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle)
|
||||
{
|
||||
CursorType finalCursorType;
|
||||
bool fShouldBlink;
|
||||
|
||||
switch (cursorStyle)
|
||||
{
|
||||
case DispatchTypes::CursorStyle::BlinkingBlockDefault:
|
||||
[[fallthrough]];
|
||||
case DispatchTypes::CursorStyle::BlinkingBlock:
|
||||
finalCursorType = CursorType::FullBox;
|
||||
fShouldBlink = true;
|
||||
break;
|
||||
case DispatchTypes::CursorStyle::SteadyBlock:
|
||||
finalCursorType = CursorType::FullBox;
|
||||
fShouldBlink = false;
|
||||
break;
|
||||
case DispatchTypes::CursorStyle::BlinkingUnderline:
|
||||
finalCursorType = CursorType::Underscore;
|
||||
fShouldBlink = true;
|
||||
break;
|
||||
case DispatchTypes::CursorStyle::SteadyUnderline:
|
||||
finalCursorType = CursorType::Underscore;
|
||||
fShouldBlink = false;
|
||||
break;
|
||||
case DispatchTypes::CursorStyle::BlinkingBar:
|
||||
finalCursorType = CursorType::VerticalBar;
|
||||
fShouldBlink = true;
|
||||
break;
|
||||
case DispatchTypes::CursorStyle::SteadyBar:
|
||||
finalCursorType = CursorType::VerticalBar;
|
||||
fShouldBlink = false;
|
||||
break;
|
||||
default:
|
||||
finalCursorType = CursorType::Legacy;
|
||||
fShouldBlink = false;
|
||||
}
|
||||
|
||||
_buffer->GetCursor().SetType(finalCursorType);
|
||||
_buffer->GetCursor().SetBlinkingAllowed(fShouldBlink);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Updates the default foreground color from a COLORREF, format 0x00BBGGRR.
|
||||
// Arguments:
|
||||
// - dwColor: the new COLORREF to use as the default foreground color
|
||||
// Return Value:
|
||||
// - true
|
||||
bool Terminal::SetDefaultForeground(const COLORREF dwColor)
|
||||
{
|
||||
_defaultFg = dwColor;
|
||||
|
||||
// Repaint everything - the colors might have changed
|
||||
_buffer->GetRenderTarget().TriggerRedrawAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Updates the default background color from a COLORREF, format 0x00BBGGRR.
|
||||
// Arguments:
|
||||
// - dwColor: the new COLORREF to use as the default background color
|
||||
// Return Value:
|
||||
// - true
|
||||
bool Terminal::SetDefaultBackground(const COLORREF dwColor)
|
||||
{
|
||||
_defaultBg = dwColor;
|
||||
_pfnBackgroundColorChanged(dwColor);
|
||||
|
||||
// Repaint everything - the colors might have changed
|
||||
_buffer->GetRenderTarget().TriggerRedrawAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -64,9 +64,36 @@ bool TerminalDispatch::SetWindowTitle(std::wstring_view title)
|
||||
// - tableIndex: The VT color table index
|
||||
// - dwColor: The new RGB color value to use.
|
||||
// Return Value:
|
||||
// True if handled successfully. False othewise.
|
||||
// True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::SetColorTableEntry(const size_t tableIndex,
|
||||
const DWORD dwColor)
|
||||
{
|
||||
return _terminalApi.SetColorTableEntry(tableIndex, dwColor);
|
||||
}
|
||||
|
||||
bool TerminalDispatch::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle)
|
||||
{
|
||||
return _terminalApi.SetCursorStyle(cursorStyle);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets the default foreground color to a new value
|
||||
// Arguments:
|
||||
// - dwColor: The new RGB color value to use, in 0x00BBGGRR form
|
||||
// Return Value:
|
||||
// True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::SetDefaultForeground(const DWORD dwColor)
|
||||
{
|
||||
return _terminalApi.SetDefaultForeground(dwColor);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets the default background color to a new value
|
||||
// Arguments:
|
||||
// - dwColor: The new RGB color value to use, in 0x00BBGGRR form
|
||||
// Return Value:
|
||||
// True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::SetDefaultBackground(const DWORD dwColor)
|
||||
{
|
||||
return _terminalApi.SetDefaultBackground(dwColor);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,10 @@ public:
|
||||
bool SetWindowTitle(std::wstring_view title) override;
|
||||
|
||||
bool SetColorTableEntry(const size_t tableIndex, const DWORD dwColor) override;
|
||||
bool SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle) override;
|
||||
|
||||
bool SetDefaultForeground(const DWORD dwColor) override;
|
||||
bool SetDefaultBackground(const DWORD dwColor) override;
|
||||
|
||||
private:
|
||||
::Microsoft::Terminal::Core::ITerminalApi& _terminalApi;
|
||||
|
||||
@@ -34,5 +34,8 @@ namespace Microsoft.Terminal.Settings
|
||||
String StartingDirectory;
|
||||
String EnvironmentVariables;
|
||||
|
||||
String BackgroundImage;
|
||||
Double BackgroundImageOpacity;
|
||||
Windows.UI.Xaml.Media.Stretch BackgroundImageStretchMode;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "pch.h"
|
||||
#include "KeyChord.h"
|
||||
|
||||
#include "KeyChord.g.cpp"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
{
|
||||
KeyChord::KeyChord(bool ctrl, bool alt, bool shift, int32_t vkey) :
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "TerminalSettings.h"
|
||||
#include <DefaultSettings.h>
|
||||
|
||||
#include "TerminalSettings.g.cpp"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
{
|
||||
TerminalSettings::TerminalSettings() :
|
||||
@@ -24,6 +26,9 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
_padding{ DEFAULT_PADDING },
|
||||
_fontFace{ DEFAULT_FONT_FACE },
|
||||
_fontSize{ DEFAULT_FONT_SIZE },
|
||||
_backgroundImage{},
|
||||
_backgroundImageOpacity{ 1.0 },
|
||||
_backgroundImageStretchMode{ winrt::Windows::UI::Xaml::Media::Stretch::UniformToFill },
|
||||
_keyBindings{ nullptr },
|
||||
_scrollbarState{ ScrollbarState::Visible }
|
||||
{
|
||||
@@ -191,6 +196,36 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
_fontSize = value;
|
||||
}
|
||||
|
||||
void TerminalSettings::BackgroundImage(hstring const& value)
|
||||
{
|
||||
_backgroundImage = value;
|
||||
}
|
||||
|
||||
hstring TerminalSettings::BackgroundImage()
|
||||
{
|
||||
return _backgroundImage;
|
||||
}
|
||||
|
||||
void TerminalSettings::BackgroundImageOpacity(double value)
|
||||
{
|
||||
_backgroundImageOpacity = value;
|
||||
}
|
||||
|
||||
double TerminalSettings::BackgroundImageOpacity()
|
||||
{
|
||||
return _backgroundImageOpacity;
|
||||
}
|
||||
|
||||
winrt::Windows::UI::Xaml::Media::Stretch TerminalSettings::BackgroundImageStretchMode()
|
||||
{
|
||||
return _backgroundImageStretchMode;
|
||||
}
|
||||
|
||||
void TerminalSettings::BackgroundImageStretchMode(winrt::Windows::UI::Xaml::Media::Stretch value)
|
||||
{
|
||||
_backgroundImageStretchMode = value;
|
||||
}
|
||||
|
||||
Settings::IKeyBindings TerminalSettings::KeyBindings()
|
||||
{
|
||||
return _keyBindings;
|
||||
|
||||
@@ -61,6 +61,13 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
int32_t FontSize();
|
||||
void FontSize(int32_t value);
|
||||
|
||||
hstring BackgroundImage();
|
||||
void BackgroundImage(hstring const& value);
|
||||
double BackgroundImageOpacity();
|
||||
void BackgroundImageOpacity(double value);
|
||||
winrt::Windows::UI::Xaml::Media::Stretch BackgroundImageStretchMode();
|
||||
void BackgroundImageStretchMode(winrt::Windows::UI::Xaml::Media::Stretch value);
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::IKeyBindings KeyBindings();
|
||||
void KeyBindings(winrt::Microsoft::Terminal::Settings::IKeyBindings const& value);
|
||||
|
||||
@@ -94,6 +101,9 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
|
||||
hstring _fontFace;
|
||||
int32_t _fontSize;
|
||||
hstring _padding;
|
||||
hstring _backgroundImage;
|
||||
double _backgroundImageOpacity;
|
||||
winrt::Windows::UI::Xaml::Media::Stretch _backgroundImageStretchMode;
|
||||
hstring _commandline;
|
||||
hstring _startingDir;
|
||||
hstring _envVars;
|
||||
|
||||
136
src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include <WexTestClass.h>
|
||||
|
||||
#include "DefaultSettings.h"
|
||||
#include "../cascadia/TerminalCore/Terminal.hpp"
|
||||
#include "../renderer/inc/DummyRenderTarget.hpp"
|
||||
#include "consoletaeftemplates.hpp"
|
||||
|
||||
#include "winrt/Microsoft.Terminal.Settings.h"
|
||||
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
using namespace Microsoft::Terminal::Core;
|
||||
|
||||
namespace TerminalCoreUnitTests
|
||||
{
|
||||
class MockTermSettings : public winrt::implements<MockTermSettings, ICoreSettings>
|
||||
{
|
||||
public:
|
||||
MockTermSettings(int32_t historySize, int32_t initialRows, int32_t initialCols) :
|
||||
_historySize(historySize),
|
||||
_initialRows(initialRows),
|
||||
_initialCols(initialCols)
|
||||
{ }
|
||||
|
||||
// property getters - all implemented
|
||||
int32_t HistorySize() { return _historySize; }
|
||||
int32_t InitialRows() { return _initialRows; }
|
||||
int32_t InitialCols() { return _initialCols; }
|
||||
uint32_t DefaultForeground() { return COLOR_WHITE; }
|
||||
uint32_t DefaultBackground() { return COLOR_BLACK; }
|
||||
bool SnapOnInput() { return false; }
|
||||
uint32_t CursorColor() { return COLOR_WHITE; }
|
||||
CursorStyle CursorShape() const noexcept { return CursorStyle::Vintage; }
|
||||
uint32_t CursorHeight() { return 42UL; }
|
||||
|
||||
// other implemented methods
|
||||
uint32_t GetColorTableEntry(int32_t) const { return 123; }
|
||||
|
||||
// property setters - all unimplemented
|
||||
void HistorySize(int32_t) { }
|
||||
void InitialRows(int32_t) { }
|
||||
void InitialCols(int32_t) { }
|
||||
void DefaultForeground(uint32_t) { }
|
||||
void DefaultBackground(uint32_t) { }
|
||||
void SnapOnInput(bool) { }
|
||||
void CursorColor(uint32_t) { }
|
||||
void CursorShape(CursorStyle const&) noexcept { }
|
||||
void CursorHeight(uint32_t) { }
|
||||
|
||||
// other unimplemented methods
|
||||
void SetColorTableEntry(int32_t /* index */, uint32_t /* value */) { }
|
||||
|
||||
private:
|
||||
int32_t _historySize;
|
||||
int32_t _initialRows;
|
||||
int32_t _initialCols;
|
||||
};
|
||||
|
||||
#define WCS(x) WCSHELPER(x)
|
||||
#define WCSHELPER(x) L#x
|
||||
|
||||
class ScreenSizeLimitsTest
|
||||
{
|
||||
TEST_CLASS(ScreenSizeLimitsTest);
|
||||
|
||||
TEST_METHOD(ScreenWidthAndHeightAreClampedToBounds)
|
||||
{
|
||||
DummyRenderTarget emptyRenderTarget;
|
||||
|
||||
// Negative values for initial visible row count or column count
|
||||
// are clamped to 1. Too-large positive values are clamped to SHRT_MAX.
|
||||
auto negativeColumnsSettings = winrt::make<MockTermSettings>(10000, 9999999, -1234);
|
||||
Terminal negativeColumnsTerminal;
|
||||
negativeColumnsTerminal.CreateFromSettings(negativeColumnsSettings, emptyRenderTarget);
|
||||
COORD actualDimensions = negativeColumnsTerminal.GetViewport().Dimensions();
|
||||
VERIFY_ARE_EQUAL(actualDimensions.Y, SHRT_MAX, L"Row count clamped to SHRT_MAX == " WCS(SHRT_MAX));
|
||||
VERIFY_ARE_EQUAL(actualDimensions.X, 1, L"Column count clamped to 1");
|
||||
|
||||
// Zero values are clamped to 1 as well.
|
||||
auto zeroRowsSettings = winrt::make<MockTermSettings>(10000, 0, 9999999);
|
||||
Terminal zeroRowsTerminal;
|
||||
zeroRowsTerminal.CreateFromSettings(zeroRowsSettings, emptyRenderTarget);
|
||||
actualDimensions = zeroRowsTerminal.GetViewport().Dimensions();
|
||||
VERIFY_ARE_EQUAL(actualDimensions.Y, 1, L"Row count clamped to 1");
|
||||
VERIFY_ARE_EQUAL(actualDimensions.X, SHRT_MAX, L"Column count clamped to SHRT_MAX == " WCS(SHRT_MAX));
|
||||
}
|
||||
|
||||
TEST_METHOD(ScrollbackHistorySizeIsClampedToBounds)
|
||||
{
|
||||
// What is actually clamped is the number of rows in the internal history buffer,
|
||||
// which is the *sum* of the history size plus the number of rows
|
||||
// actually visible on screen at the moment.
|
||||
|
||||
const unsigned int visibleRowCount = 100;
|
||||
DummyRenderTarget emptyRenderTarget;
|
||||
|
||||
// Zero history size is acceptable.
|
||||
auto noHistorySettings = winrt::make<MockTermSettings>(0, visibleRowCount, 100);
|
||||
Terminal noHistoryTerminal;
|
||||
noHistoryTerminal.CreateFromSettings(noHistorySettings, emptyRenderTarget);
|
||||
VERIFY_ARE_EQUAL(noHistoryTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount,
|
||||
L"History size of 0 is accepted");
|
||||
|
||||
// Negative history sizes are clamped to zero.
|
||||
auto negativeHistorySizeSettings = winrt::make<MockTermSettings>(-100, visibleRowCount, 100);
|
||||
Terminal negativeHistorySizeTerminal;
|
||||
negativeHistorySizeTerminal.CreateFromSettings(negativeHistorySizeSettings, emptyRenderTarget);
|
||||
VERIFY_ARE_EQUAL(negativeHistorySizeTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount,
|
||||
L"Negative history size is clamped to 0");
|
||||
|
||||
// History size + initial visible rows == SHRT_MAX is acceptable.
|
||||
auto maxHistorySizeSettings = winrt::make<MockTermSettings>(SHRT_MAX - visibleRowCount, visibleRowCount, 100);
|
||||
Terminal maxHistorySizeTerminal;
|
||||
maxHistorySizeTerminal.CreateFromSettings(maxHistorySizeSettings, emptyRenderTarget);
|
||||
VERIFY_ARE_EQUAL(maxHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX),
|
||||
L"History size == SHRT_MAX - initial row count is accepted");
|
||||
|
||||
// History size + initial visible rows == SHRT_MAX + 1 will be clamped slightly.
|
||||
auto justTooBigHistorySizeSettings = winrt::make<MockTermSettings>(SHRT_MAX - visibleRowCount + 1, visibleRowCount, 100);
|
||||
Terminal justTooBigHistorySizeTerminal;
|
||||
justTooBigHistorySizeTerminal.CreateFromSettings(justTooBigHistorySizeSettings, emptyRenderTarget);
|
||||
VERIFY_ARE_EQUAL(justTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX),
|
||||
L"History size == 1 + SHRT_MAX - initial row count is clamped to SHRT_MAX - initial row count");
|
||||
|
||||
// Ridiculously large history sizes are also clamped.
|
||||
auto farTooBigHistorySizeSettings = winrt::make<MockTermSettings>(99999999, visibleRowCount, 100);
|
||||
Terminal farTooBigHistorySizeTerminal;
|
||||
farTooBigHistorySizeTerminal.CreateFromSettings(farTooBigHistorySizeSettings, emptyRenderTarget);
|
||||
VERIFY_ARE_EQUAL(farTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX),
|
||||
L"History size that is far too large is clamped to SHRT_MAX - initial row count");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(SolutionDir)src\common.build.pre.props" />
|
||||
<Import Project="$(SolutionDir)\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(SolutionDir)\src\common.build.pre.props" />
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ScreenSizeLimitsTest.cpp" />
|
||||
<ClCompile Include="SelectionTest.cpp" />
|
||||
<ClCompile Include="precomp.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
@@ -36,7 +38,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>..;$(SolutionDir)src\inc;$(SolutionDir)src\inc\test;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..;$(SolutionDir)src\inc;$(SolutionDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalSettings\Generated Files";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
|
||||
@@ -16,6 +16,7 @@ public:
|
||||
return reinterpret_cast<T *>(GetWindowLongPtr(window, GWLP_USERDATA));
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static LRESULT __stdcall WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
|
||||
{
|
||||
WINRT_ASSERT(window);
|
||||
@@ -40,6 +41,7 @@ public:
|
||||
return DefWindowProc(window, message, wparam, lparam);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
|
||||
{
|
||||
switch (message) {
|
||||
@@ -89,7 +91,6 @@ public:
|
||||
}
|
||||
case CM_UPDATE_TITLE:
|
||||
{
|
||||
|
||||
SetWindowTextW(_window, _title.c_str());
|
||||
break;
|
||||
}
|
||||
@@ -99,6 +100,7 @@ public:
|
||||
}
|
||||
|
||||
// DPI Change handler. on WM_DPICHANGE resize the window
|
||||
[[nodiscard]]
|
||||
LRESULT HandleDpiChange(const HWND hWnd, const WPARAM wParam, const LPARAM lParam)
|
||||
{
|
||||
_inDpiChange = true;
|
||||
@@ -115,30 +117,67 @@ public:
|
||||
SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
|
||||
_currentDpi = uDpi;
|
||||
NewScale(uDpi);
|
||||
}
|
||||
_inDpiChange = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual void NewScale(UINT dpi) = 0;
|
||||
|
||||
virtual void OnResize(const UINT width, const UINT height) = 0;
|
||||
virtual void OnMinimize() = 0;
|
||||
virtual void OnRestore() = 0;
|
||||
|
||||
RECT GetWindowRect() const
|
||||
RECT GetWindowRect() const noexcept
|
||||
{
|
||||
RECT rc = { 0 };
|
||||
::GetWindowRect(_window, &rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
HWND GetHandle() noexcept
|
||||
HWND GetHandle() const noexcept
|
||||
{
|
||||
return _window;
|
||||
};
|
||||
|
||||
float GetCurrentDpiScale() const noexcept
|
||||
{
|
||||
const auto dpi = ::GetDpiForWindow(_window);
|
||||
const auto scale = static_cast<float>(dpi) / static_cast<float>(USER_DEFAULT_SCREEN_DPI);
|
||||
return scale;
|
||||
}
|
||||
|
||||
//// Gets the physical size of the client area of the HWND in _window
|
||||
SIZE GetPhysicalSize() const noexcept
|
||||
{
|
||||
RECT rect = {};
|
||||
GetClientRect(_window, &rect);
|
||||
const auto windowsWidth = rect.right - rect.left;
|
||||
const auto windowsHeight = rect.bottom - rect.top;
|
||||
return SIZE{ windowsWidth, windowsHeight };
|
||||
}
|
||||
|
||||
//// Gets the logical (in DIPs) size of a physical size specified by the parameter physicalSize
|
||||
//// Remarks:
|
||||
//// XAML coordinate system is always in Display Indepenent Pixels (a.k.a DIPs or Logical). However Win32 GDI (because of legacy reasons)
|
||||
//// in DPI mode "Per-Monitor and Per-Monitor (V2) DPI Awareness" is always in physical pixels.
|
||||
//// The formula to transform is:
|
||||
//// logical = (physical / dpi) + 0.5 // 0.5 is to ensure that we pixel snap correctly at the edges, this is necessary with odd DPIs like 1.25, 1.5, 1, .75
|
||||
//// See also:
|
||||
//// https://docs.microsoft.com/en-us/windows/desktop/LearnWin32/dpi-and-device-independent-pixels
|
||||
//// https://docs.microsoft.com/en-us/windows/desktop/hidpi/high-dpi-desktop-application-development-on-windows#per-monitor-and-per-monitor-v2-dpi-awareness
|
||||
winrt::Windows::Foundation::Size GetLogicalSize(const SIZE physicalSize) const noexcept
|
||||
{
|
||||
const auto dpi = GetCurrentDpiScale();
|
||||
// 0.5 is to ensure that we pixel snap correctly at the edges, this is necessary with odd DPIs like 1.25, 1.5, 1, .75
|
||||
const auto logicalWidth = (physicalSize.cx / dpi) + 0.5f;
|
||||
const auto logicalHeigth = (physicalSize.cy / dpi) + 0.5f;
|
||||
return winrt::Windows::Foundation::Size(logicalWidth, logicalHeigth);
|
||||
}
|
||||
|
||||
winrt::Windows::Foundation::Size GetLogicalSize() const noexcept
|
||||
{
|
||||
return GetLogicalSize(GetPhysicalSize());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sends a message to our message loop to update the title of the window.
|
||||
// Arguments:
|
||||
|
||||
@@ -17,10 +17,7 @@ using namespace ::Microsoft::Console::Types;
|
||||
#define XAML_HOSTING_WINDOW_CLASS_NAME L"CASCADIA_HOSTING_WINDOW_CLASS"
|
||||
|
||||
IslandWindow::IslandWindow() noexcept :
|
||||
_currentWidth{ 0 },
|
||||
_currentHeight{ 0 },
|
||||
_interopWindowHandle{ nullptr },
|
||||
_scale{ nullptr },
|
||||
_rootGrid{ nullptr },
|
||||
_source{ nullptr },
|
||||
_pfnCreateCallback{ nullptr }
|
||||
@@ -49,7 +46,7 @@ void IslandWindow::MakeWindow() noexcept
|
||||
WINRT_ASSERT(!_window);
|
||||
|
||||
// Create the window with the default size here - During the creation of the
|
||||
// window, the system will give us a chance to set it's size in WM_CREATE.
|
||||
// window, the system will give us a chance to set its size in WM_CREATE.
|
||||
// WM_CREATE will be handled synchronously, before CreateWindow returns.
|
||||
WINRT_VERIFY(CreateWindow(wc.lpszClassName,
|
||||
L"Windows Terminal",
|
||||
@@ -126,42 +123,28 @@ void IslandWindow::Initialize()
|
||||
// stash the child interop handle so we can resize it when the main hwnd is resized
|
||||
interop->get_WindowHandle(&_interopWindowHandle);
|
||||
|
||||
if (!initialized)
|
||||
{
|
||||
_InitXamlContent();
|
||||
}
|
||||
|
||||
_rootGrid = winrt::Windows::UI::Xaml::Controls::Grid();
|
||||
_source.Content(_rootGrid);
|
||||
|
||||
// Do a quick resize to force the island to paint
|
||||
OnSize();
|
||||
}
|
||||
|
||||
void IslandWindow::_InitXamlContent()
|
||||
{
|
||||
// setup a root grid that will be used to apply DPI scaling
|
||||
winrt::Windows::UI::Xaml::Media::ScaleTransform dpiScaleTransform;
|
||||
winrt::Windows::UI::Xaml::Controls::Grid dpiAdjustmentGrid;
|
||||
|
||||
const auto dpi = GetDpiForWindow(_window);
|
||||
const double scale = double(dpi) / double(USER_DEFAULT_SCREEN_DPI);
|
||||
|
||||
_rootGrid = dpiAdjustmentGrid;
|
||||
_scale = dpiScaleTransform;
|
||||
|
||||
_scale.ScaleX(scale);
|
||||
_scale.ScaleY(scale);
|
||||
}
|
||||
|
||||
|
||||
void IslandWindow::OnSize()
|
||||
{
|
||||
const auto physicalSize = GetPhysicalSize();
|
||||
// update the interop window size
|
||||
SetWindowPos(_interopWindowHandle, 0, 0, 0, _currentWidth, _currentHeight, SWP_SHOWWINDOW);
|
||||
_rootGrid.Width(_currentWidth);
|
||||
_rootGrid.Height(_currentHeight);
|
||||
SetWindowPos(_interopWindowHandle, 0, 0, 0, physicalSize.cx, physicalSize.cy, SWP_SHOWWINDOW);
|
||||
|
||||
if (_rootGrid)
|
||||
{
|
||||
const auto size = GetLogicalSize();
|
||||
_rootGrid.Width(size.Width);
|
||||
_rootGrid.Height(size.Height);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
LRESULT IslandWindow::MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
|
||||
{
|
||||
switch (message) {
|
||||
@@ -180,83 +163,27 @@ LRESULT IslandWindow::MessageHandler(UINT const message, WPARAM const wparam, LP
|
||||
return 0; // eat the message
|
||||
}
|
||||
}
|
||||
case WM_MENUCHAR:
|
||||
{
|
||||
// GH#891: return this LRESULT here to prevent the app from making a
|
||||
// bell when alt+key is pressed. A menu is active and the user presses a
|
||||
// key that does not correspond to any mnemonic or accelerator key,
|
||||
return MAKELRESULT(0, MNC_CLOSE);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: handle messages here...
|
||||
return base_type::MessageHandler(message, wparam, lparam);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Called when the DPI of this window changes. Updates the XAML content sizing to match the client area of our window.
|
||||
// Arguments:
|
||||
// - dpi: new DPI to use. The default is 96, as defined by USER_DEFAULT_SCREEN_DPI.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void IslandWindow::NewScale(UINT dpi)
|
||||
{
|
||||
const double scaleFactor = static_cast<double>(dpi) / static_cast<double>(USER_DEFAULT_SCREEN_DPI);
|
||||
|
||||
if (_scale != nullptr)
|
||||
{
|
||||
_scale.ScaleX(scaleFactor);
|
||||
_scale.ScaleY(scaleFactor);
|
||||
}
|
||||
|
||||
ApplyCorrection(scaleFactor);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This method updates the padding that exists off the edge of the window to
|
||||
// make sure to keep the XAML content size the same as the actual window size.
|
||||
// Arguments:
|
||||
// - scaleFactor: the DPI scaling multiplier to use. for a dpi of 96, this would
|
||||
// be 1, for 144, this would be 1.5.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void IslandWindow::ApplyCorrection(double scaleFactor)
|
||||
{
|
||||
// Get the dimensions of the XAML content grid.
|
||||
const auto realWidth = _rootGrid.Width();
|
||||
const auto realHeight = _rootGrid.Height();
|
||||
|
||||
// Scale those dimensions by our dpi scaling. This is how big the XAML
|
||||
// content thinks it should be.
|
||||
const auto dpiAwareWidth = realWidth * scaleFactor;
|
||||
const auto dpiAwareHeight = realHeight * scaleFactor;
|
||||
|
||||
// Get the difference between what xaml thinks and the actual client area
|
||||
// of our window.
|
||||
const auto deltaX = dpiAwareWidth - realWidth;
|
||||
const auto deltaY = dpiAwareHeight - realHeight;
|
||||
|
||||
// correct for the scaling we applied above
|
||||
const auto dividedDeltaX = deltaX / scaleFactor;
|
||||
const auto dividedDeltaY = deltaY / scaleFactor;
|
||||
|
||||
const double rightCorrection = dividedDeltaX;
|
||||
const double bottomCorrection = dividedDeltaY;
|
||||
|
||||
// Apply padding to the root grid, so that it's content is the same size as
|
||||
// our actual window size.
|
||||
// Without this, XAML content will seem to spill off the side/bottom of the window
|
||||
_rootGrid.Padding(Xaml::ThicknessHelper::FromLengths(0, 0, rightCorrection, bottomCorrection));
|
||||
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Called when the window has been resized (or maximized)
|
||||
// Arguments:
|
||||
// - width: the new width of the window _in pixels_
|
||||
// - height: the new height of the window _in pixels_
|
||||
void IslandWindow::OnResize(const UINT width, const UINT height)
|
||||
void IslandWindow::OnResize(const UINT /*width*/, const UINT /*height*/)
|
||||
{
|
||||
_currentWidth = width;
|
||||
_currentHeight = height;
|
||||
if (nullptr != _rootGrid)
|
||||
{
|
||||
OnSize();
|
||||
ApplyCorrection(_scale.ScaleX());
|
||||
}
|
||||
OnSize();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -276,6 +203,6 @@ void IslandWindow::OnRestore()
|
||||
void IslandWindow::SetRootContent(winrt::Windows::UI::Xaml::UIElement content)
|
||||
{
|
||||
_rootGrid.Children().Clear();
|
||||
ApplyCorrection(_scale.ScaleX());
|
||||
_rootGrid.Children().Append(content);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,8 @@ public:
|
||||
void Close();
|
||||
virtual void OnSize();
|
||||
|
||||
[[nodiscard]]
|
||||
virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override;
|
||||
void ApplyCorrection(double scaleFactor);
|
||||
void NewScale(UINT dpi) override;
|
||||
void OnResize(const UINT width, const UINT height) override;
|
||||
void OnMinimize() override;
|
||||
void OnRestore() override;
|
||||
@@ -29,18 +28,13 @@ public:
|
||||
void SetCreateCallback(std::function<void(const HWND, const RECT)> pfn) noexcept;
|
||||
|
||||
protected:
|
||||
unsigned int _currentWidth;
|
||||
unsigned int _currentHeight;
|
||||
|
||||
HWND _interopWindowHandle;
|
||||
|
||||
winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource _source;
|
||||
|
||||
winrt::Windows::UI::Xaml::Media::ScaleTransform _scale;
|
||||
winrt::Windows::UI::Xaml::Controls::Grid _rootGrid;
|
||||
|
||||
std::function<void(const HWND, const RECT)> _pfnCreateCallback;
|
||||
|
||||
void _InitXamlContent();
|
||||
void _HandleCreateWindow(const WPARAM wParam, const LPARAM lParam) noexcept;
|
||||
};
|
||||
|
||||
@@ -68,7 +68,6 @@ void NonClientIslandWindow::Initialize()
|
||||
void NonClientIslandWindow::SetNonClientContent(winrt::Windows::UI::Xaml::UIElement content)
|
||||
{
|
||||
_nonClientRootGrid.Children().Clear();
|
||||
ApplyCorrection(_scale.ScaleX());
|
||||
_nonClientRootGrid.Children().Append(content);
|
||||
}
|
||||
|
||||
@@ -91,13 +90,15 @@ void NonClientIslandWindow::SetNonClientHeight(const int contentHeight) noexcept
|
||||
// content, in window coordinates.
|
||||
Viewport NonClientIslandWindow::GetTitlebarContentArea() const noexcept
|
||||
{
|
||||
const auto dpi = GetDpiForWindow(_window);
|
||||
const double scale = static_cast<double>(dpi) / static_cast<double>(USER_DEFAULT_SCREEN_DPI);
|
||||
const auto scale = GetCurrentDpiScale();
|
||||
|
||||
const auto titlebarContentHeight = _titlebarUnscaledContentHeight * scale;
|
||||
const auto titlebarMarginRight = _titlebarMarginRight;
|
||||
|
||||
auto titlebarWidth = _currentWidth - (_windowMarginSides + titlebarMarginRight);
|
||||
const auto physicalSize = GetPhysicalSize();
|
||||
const auto clientWidth = physicalSize.cx;
|
||||
|
||||
auto titlebarWidth = clientWidth - (_windowMarginSides + titlebarMarginRight);
|
||||
// Adjust for maximized margins
|
||||
titlebarWidth -= (_maximizedMargins.cxLeftWidth + _maximizedMargins.cxRightWidth);
|
||||
|
||||
@@ -133,8 +134,9 @@ Viewport NonClientIslandWindow::GetClientContentArea() const noexcept
|
||||
COORD clientOrigin = { static_cast<short>(margins.cxLeftWidth),
|
||||
static_cast<short>(margins.cyTopHeight) };
|
||||
|
||||
auto clientWidth = _currentWidth;
|
||||
auto clientHeight = _currentHeight;
|
||||
const auto physicalSize = GetPhysicalSize();
|
||||
auto clientWidth = physicalSize.cx;
|
||||
auto clientHeight = physicalSize.cy;
|
||||
|
||||
// If we're maximized, we don't want to use the frame as our margins,
|
||||
// instead we want to use the margins from the maximization. If we included
|
||||
@@ -177,8 +179,13 @@ void NonClientIslandWindow::OnSize()
|
||||
clientArea.Height(),
|
||||
SWP_SHOWWINDOW);
|
||||
|
||||
_rootGrid.Width(clientArea.Width());
|
||||
_rootGrid.Height(clientArea.Height());
|
||||
if (_rootGrid)
|
||||
{
|
||||
const SIZE physicalSize{ clientArea.Width(), clientArea.Height() };
|
||||
const auto logicalSize = GetLogicalSize(physicalSize);
|
||||
_rootGrid.Width(logicalSize.Width);
|
||||
_rootGrid.Height(logicalSize.Height);
|
||||
}
|
||||
|
||||
// update the interop window size
|
||||
SetWindowPos(_nonClientInteropWindowHandle, 0,
|
||||
@@ -202,6 +209,7 @@ void NonClientIslandWindow::OnSize()
|
||||
// NOTE:
|
||||
// Largely taken from code on:
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/dwm/customframe
|
||||
[[nodiscard]]
|
||||
LRESULT NonClientIslandWindow::HitTestNCA(POINT ptMouse) const noexcept
|
||||
{
|
||||
// Get the window rectangle.
|
||||
@@ -277,6 +285,7 @@ MARGINS NonClientIslandWindow::GetFrameMargins() const noexcept
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the HRESULT returned by DwmExtendFrameIntoClientArea.
|
||||
[[nodiscard]]
|
||||
HRESULT NonClientIslandWindow::_UpdateFrameMargins() const noexcept
|
||||
{
|
||||
// Get the size of the borders we want to use. The sides and bottom will
|
||||
@@ -380,6 +389,7 @@ RECT NonClientIslandWindow::GetMaxWindowRectInPixels(const RECT * const prcSugge
|
||||
// Return Value:
|
||||
// - The return value is the result of the message processing and depends on the
|
||||
// message sent.
|
||||
[[nodiscard]]
|
||||
LRESULT NonClientIslandWindow::MessageHandler(UINT const message,
|
||||
WPARAM const wParam,
|
||||
LPARAM const lParam) noexcept
|
||||
@@ -482,7 +492,7 @@ void NonClientIslandWindow::_HandleActivateWindow()
|
||||
// _titlebarUnscaledContentHeight is set with SetNonClientHeight by the app
|
||||
// hosting us.
|
||||
|
||||
_UpdateFrameMargins();
|
||||
THROW_IF_FAILED(_UpdateFrameMargins());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -626,7 +636,7 @@ bool NonClientIslandWindow::_HandleWindowPosChanging(WINDOWPOS* const windowPos)
|
||||
_maximizedMargins.cyBottomHeight = -offset;
|
||||
|
||||
_isMaximized = true;
|
||||
_UpdateFrameMargins();
|
||||
THROW_IF_FAILED(_UpdateFrameMargins());
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -640,7 +650,7 @@ bool NonClientIslandWindow::_HandleWindowPosChanging(WINDOWPOS* const windowPos)
|
||||
// keep this here _in general_ for dragging across DPI boundaries.
|
||||
if (!_isMaximized)
|
||||
{
|
||||
_UpdateFrameMargins();
|
||||
THROW_IF_FAILED(_UpdateFrameMargins());
|
||||
}
|
||||
|
||||
_isMaximized = false;
|
||||
|
||||