Compare commits

..

13 Commits

Author SHA1 Message Date
Mike Griese
491ec14291 Passthrough input too 2019-07-19 15:13:46 -05:00
Mike Griese
90271426e9 accidentally the whole thing 2019-07-19 11:07:20 -05:00
Mike Griese
f5939ebd61 Implement a basic passthrough mode
* [ ] Doesn't immediately stop a frame when passthrough mode is entered.
  * [ ] Exiting passthrough will leave us in a torn state
  * [ ] Pretty sure that's the wrong place to put that flag

  Otherwise this was scary easy
2019-07-19 10:24:29 -05:00
Mike Griese
5074335392 Add a keybinding for ClosePane (#2012)
Closes #993
  When the last pane in a tab is closed, the tab will close.
  Bound to Ctrl+Shift+W by default. See #1417 for discussion on the default
  keybindings. The Ctrl+W->CloseTab keybinding is being removed in favor of
  ClosePane.
2019-07-18 17:23:40 -07:00
Mike Griese
8ffff8ea37 Enable dragging with the entire titlebar (#1948)
* This definitely works for getting shadow, pointy corners back

  Don't do anything in NCPAINT. If you do, you have to do everything. But the
  whole point of DwmExtendFrameIntoClientArea is to let you paint the NC area in
  your normal paint. So just do that dummy.

  * This doesn't transition across monitors.
  * This has a window style change I think is wrong.
  * I'm not sure the margins change is important.

* The window style was _not_ important

* Still getting a black xaml islands area (the HRGN) when we switch to high DPI

* I don't know if this affects anything.

* heyo this works.

  I'm not entirely sure why. But if we only update the titlebar drag region when
  that actually changes, it's a _lot_ smoother. I'm not super happy with the
  duplicated work in _UpdateDragRegion and OnSize, but checking this in in case
  I can't figure that out.

* Add more comments and cleanup

* Try making the button RightCustomContent

* * Make the MinMaxClose's drag bar's min size the same as a caption button
* Make the new tab button transparent, to see how that looks
* Make sure the TabView doesn't push the MMC off the window

* Create a TitlebarControl

  * The TitlebarControl is owned by the NCIW. It consists of a Content, DragBar,
    and MMCControl.
  * The App instatntiates a TabRowControl at runtime, and either places it in
    the UI (for tabs below titlebar) or hangs on to it, and gives it to the NCIW
    when the NCIW creates its UI.
  * When the NCIW is created, it creates a grid with two rows, one for the
    titlebar and one for the app content.
  * The MMCControl is only responsible for Min Max Close now, and is closer to
    the window implementation.
  * The drag bar takes up all the space from the right of the TabRow to the left
    of the MMC
  * Things that **DON'T** work:
    - When you add tabs, the drag bar doesn't update it's size. It only updates
      OnSize
    - The MMCControl's Min and Max buttons don't seem to work anymore.
      - They should probably just expose their OnMinimizeClick and
        OnMaximizeClick events for the Titlebar to handle minimizing and
        maximizing.
    - The drag bar is Magenta (#ff00ff) currently.
    - I'm not _sure_ we need a TabRowControl. We could probably get away with
      removing it from the UI tree, I was just being dumb before.

* Fix the MMC buttons not working

  I forgot to plumb the window handle through

* Make the titlebar less magenta

* Resize the drag region as we add/remove tabs

* Move the actual MMC handling to the TitlebarControl

* Some PR nits, fix the titlebar painting on maximize

* Put the TabRow in our XAML

* Remove dead code in preparation for review

* Horrifyingly try Gdi Plus as a solution, that is _wrong_ though

* Revert "Horrifyingly try Gdi Plus as a solution, that is _wrong_ though"

This reverts commit e038b5d921.

* This fixes the bottom border but breaks the titlebar painting

* Fix the NC bottom border

* A bunch of the more minor PR nits

* Add a MinimizeClick event to the MMCControl

  This works for Minimize. This is what I wanted to do originally.

* Add events for _all_ of the buttons, not just the Minimize btn

* Change hoe setting the titlebar content works

  Now the app triggers a callcack on the host to set the content, instead of the host querying the app.

* Move the tab row to the bottom of it's available space

* Fix the theme reloading

* PR nits from @miniksa

* Update src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp

Co-Authored-By: Michael Niksa <miniksa@microsoft.com>

* This needed to be fixed, was missed in other PR nits

* runformat

  wait _what_

* Does this fix the CI build?
2019-07-18 17:21:33 -05:00
Dustin L. Howett (MSFT)
57ad2d57fd Roll up dependencies through TerminalApp so the package is right (#2018)
This commit includes a script and build step to make sure the MSIX doesn't continue to regress
2019-07-18 11:23:34 -07:00
Michael Ratanapintha
f1441a589c Fix test runner commands (runut.cmd and friends; Invoke-OpenConsoleTests) (#2020)
In commit 0905140955 (PR #1164),
we updated the version of the Taef.Redist.Wlk NuGet package
for the TAEF test harness and framework. However, the helper commands
to run the various test cases hard-code the path to the TAEF executable,
which because of NuGet's design includes the TAEF NuGet package version.
These commands weren't updated to reflect the new TAEF version
and so have been broken since then.

This commit fixes the issue and makes running tests possible again.
2019-07-18 09:31:25 -07:00
Dustin L. Howett (MSFT)
988fe0ba60 Fix the static UTF8OutPipeReader & tests (#1998)
This commit addresses some lingering issues in UTF8OutPipeReader and cleans up its termination logic. It also fixes some issues exposed in the test.

Fixes #1997.
2019-07-17 16:27:09 -07:00
Dustin L. Howett (MSFT)
de1de4425e Roll up WindowsTerminal's subprojects into packaging outputs (#2007)
This commit introduces a GetPackagingOutputs override to WindowsTerminal that
rolls up its child projects' outputs.

It also introduces an atrocity that fixes a new regression in VS 16.2/16.3.
2019-07-17 14:02:20 -07:00
Mike Griese
8d52ba0990 Add support for moving focus between panes with the keyboard (#1910)
Enables the user to set keybindings to move focus between panes with the keyboard. 
This is highly based off the work done for resizing panes. Same logic applies - 
  moving focus will move up the panes tree until we find a pane to move the focus to.
2019-07-17 09:30:15 -05:00
Dustin L. Howett
a0782bfd6c Mark ESC as handled so that it doesn't come back in CharacterHandler (#1974) 2019-07-16 13:56:46 -07:00
Steffen
fa5b9b06bd Fix for UTF-8 partials in function ConhostConnection::_OutputThread. (#1850)
* Fix for UTF-8 partials in functions `ConhostConnection::_OutputThread` and `ApiRoutines::WriteConsoleOutputCharacterAImpl`

The implementation needs to check whether or not the buffer ends with a partial character. If so, only convert the code points which are complete, and save the partial code units in a cache that gets prepended to the next chunk of text.

* Utf8OutPipeReader class added
* Unit Test added
* use specific macros and WIL classes
* avoid possible deadlock caused by unclosed pipe handle
2019-07-16 11:14:07 -07:00
Leonard Hecker
7067910862 Add a ControlKeyStates wrapper class (#1718)
* Fixed a minor build warning
* Removed an unimplemented method declaration
* Added Microsoft::Terminal::Core::ControlKeyStates
// This class will act as a safe wrapper for the ControlKeyState enum,
// found in the NT console subsystem (<um/wincon.h>).
2019-07-16 11:09:29 -07:00
97 changed files with 2294 additions and 1018 deletions

View File

@@ -46,6 +46,14 @@ steps:
clean: true
maximumCpuCount: true
- task: PowerShell@2
displayName: 'Check MSIX for common regressions'
inputs:
targetType: inline
script: |
$Package = Get-ChildItem -Recurse -Filter "CascadiaPackage_*.msix"
.\build\scripts\Test-WindowsTerminalPackage.ps1 -Verbose -Path $Package.FullName
- task: VSTest@2
displayName: 'Run Unit Tests'
inputs:

View File

@@ -0,0 +1,79 @@
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true,
HelpMessage="Path to the .appx/.msix to validate")]
[string]
$Path,
[Parameter(HelpMessage="Path to Windows Kit")]
[ValidateScript({Test-Path $_ -Type Leaf})]
[string]
$WindowsKitPath = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0"
)
$ErrorActionPreference = "Stop"
If ($null -Eq (Get-Item $WindowsKitPath -EA:SilentlyContinue)) {
Write-Error "Could not find a windows SDK at at `"$WindowsKitPath`".`nMake sure that WindowsKitPath points to a valid SDK."
Exit 1
}
$makeAppx = "$WindowsKitPath\x86\MakeAppx.exe"
$makePri = "$WindowsKitPath\x86\MakePri.exe"
Function Expand-ApplicationPackage {
Param(
[Parameter(Mandatory, ValueFromPipeline)]
[string]
$Path
)
$sentinelFile = New-TemporaryFile
$directory = New-Item -Type Directory "$($sentinelFile.FullName)_Package"
Remove-Item $sentinelFile -Force -EA:Ignore
& $makeAppx unpack /p $Path /d $directory /nv /o
If ($LastExitCode -Ne 0) {
Throw "Failed to expand AppX"
}
$directory
}
Write-Verbose "Expanding $Path"
$AppxPackageRoot = Expand-ApplicationPackage $Path
$AppxPackageRootPath = $AppxPackageRoot.FullName
Write-Verbose "Expanded to $AppxPackageRootPath"
Try {
& $makePri dump /if "$AppxPackageRootPath\resources.pri" /of "$AppxPackageRootPath\resources.pri.xml" /o
If ($LastExitCode -Ne 0) {
Throw "Failed to dump PRI"
}
$Manifest = [xml](Get-Content "$AppxPackageRootPath\AppxManifest.xml")
$PRIFile = [xml](Get-Content "$AppxPackageRootPath\resources.pri.xml")
### Check the activatable class entries for a few DLLs we need.
$inProcServers = $Manifest.Package.Extensions.Extension.InProcessServer.Path
$RequiredInProcServers = ("TerminalApp.dll", "TerminalControl.dll", "TerminalConnection.dll")
Write-Verbose "InProc Servers: $inProcServers"
ForEach ($req in $RequiredInProcServers) {
If ($req -NotIn $inProcServers) {
Throw "Failed to find $req in InProcServer list $inProcServers"
}
}
### Check that we have an App.xbf (which is a proxy for our resources having been merged)
$resourceXpath = '/PriInfo/ResourceMap/ResourceMapSubtree[@name="Files"]/NamedResource[@name="App.xbf"]'
$AppXbf = $PRIFile.SelectSingleNode($resourceXpath)
If ($null -eq $AppXbf) {
Throw "Failed to find App.xbf (TerminalApp project) in resources.pri"
}
} Finally {
Remove-Item -Recurse -Force $AppxPackageRootPath
}

97
conpty.txt Normal file
View File

@@ -0,0 +1,97 @@
Microsoft Windows [Version 10.0.18936.1001]]0;C:\WINDOWS\system32\cmd.exe[?25h
(c) 2019 Microsoft Corporation. All rights reserved.

[11:54:25.95]>c:\Users\migrie\dev\private\OpenConsole> 
[1] [dev/migrie/f/passthrough-2019] migrie@MIGRIE-SURFBOOK>wsl.exe
]0;C:\WINDOWS\system32\cmd.exe - wsl.exezadjii@migrie-surfbook:/mnt/c/Users/migrie/dev/private/OpenConsole$ ]0;zadjii@migrie-surfbook: /mnt/c/Users/migrie/dev/private/OpenConsolevi README.md
[?2004h[?2004h




























[?25h"README.md" 177L, 11259C[?25h[?2004l[?2004h[?25h[?2004l[?2004h▽[?25h[>c]10;?]11;?
[?25h README.md  ◀◀ buffers 
1 # Welcome\! 
2 #### This repository contains the source code for:
3 
4  * Windows Terminal
5  * The Windows console host (conhost.exe)
6  * Components shared between the two projects
7  * ColorTool
8  * 
8  * Sample projects that show how to consume the Window
s Console APIs
9  
10 #### Other related repositories include:
11  * Console API Documentation
12 
13 ### Build Status
14 
15 Project|Build Status
16 ---|---
17 Terminal|Build Status
17 Terminal|Build Status

18 ColorTool|

19 
20 # Terminal & Console Overview
21 
22 Please take a few minutes to review the overview below before diving into the code:
23 
24 ## Windows Terminal[?2004l[?2004h[?2004l[?2004h[?2004l[?2004h[?2004l[?2004h[?2004l[?2004h[?2004l[?2004h[?2004h
NORMAL ▶ ⎇ dev/migrie/f/passthrough-2019 ▶ < markdown ◀ [unix] ◀ 1,397 words « 3% ␊ 6/177 ㏑ : 46 ◀ Ξ [9]trailing ◀]0;/mnt/c/Users/migrie/dev/private/OpenConsole/README.md+0 ~0 -0 ⎇ dev/migrie/f/passthrough-2019 ▶ <[?25h^[[?25h [?25h[?2004h:[?25hq
[?2004l]0;[?2004lMicrosoft Windows [Version 10.0.18936.1001]
(c) 2019 Microsoft Corporation. All rights reserved.

[11:54:25.95]>c:\Users\migrie\dev\private\OpenConsole>
[1] [dev/migrie/f/passthrough-2019] migrie@MIGRIE-SURFBOOK>wsl.exe
zadjii@migrie-surfbook:/mnt/c/Users/migrie/dev/private/OpenConsole$ vi README.md 
zadjii@migrie-surfbook:/mnt/c/Users/migrie/dev/private/OpenConsole$ 






















]0;zadjii@migrie-surfbook: /mnt/c/Users/migrie/dev/private/OpenConsole[?25h

273
conpty001.txt Normal file
View File

@@ -0,0 +1,273 @@
Microsoft Windows [Version 10.0.18936.1001]]0;C:\WINDOWS\system32\cmd.exe[?25h
(c) 2019 Microsoft Corporation. All rights reserved.

[12:00:25.93]>c:\Users\migrie\dev\private\OpenConsole> 
[1] [dev/migrie/f/passthrough-2019] migrie@MIGRIE-SURFBOOK>wsl.exe
]0;C:\WINDOWS\system32\cmd.exe - wsl.exezadjii@migrie-surfbook:/mnt/c/Users/migrie/dev/private/OpenConsole$ ]0;zadjii@migrie-surfbook: /mnt/c/Users/migrie/dev/private/OpenConsolevi README.md
[?2004h[?2004h




























[?25h"README.md" 177L, 11259C[?25h[?2004l[?2004h[?25h[?2004l[?2004h▽[?25h
[?25h[>c]10;?]11;? README.md  ◀◀ buffers 
1 # Welcome\! 
2 #### This repository contains the source code for:
3 
4  * Windows Terminal
5  * The Windows console host (conhost.exe)
6  * Components shared between the two projects
7  * ColorTool
8  * 
8  * Sample projects that show how to consume the Window
s Console APIs
9  
10 #### Other related repositories include:
11  * Console API Documentation
12 
13 ### Build Status
14 
15 Project|Build Status
16 ---|---
17 Terminal|Build Status
17 Terminal|Build Status

18 ColorTool|

19 
20 # Terminal & Console Overview
21 
22 Please take a few minutes to review the overview below before diving into the code:
23 
24 ## Windows Terminal[?2004l[?2004h[?2004l[?2004h[?2004l[?2004h[?2004l[?2004h[?2004l[?2004h[?2004l[?2004h[?2004l[?2004h[?2004l[?2004h[?2004l[?2004h[?2004h
NORMAL ▶ ⎇ dev/migrie/f/passthrough-2019 ▶ < markdown ◀ [unix] ◀ 1,397 words « 3% ␊ 6/177 ㏑ : 46 ◀ Ξ [9]trailing ◀]0;/mnt/c/Users/migrie/dev/private/OpenConsole/README.md+0 ~0 -0 ⎇ dev/migrie/f/passthrough-2019 ▶ <[?25h~@k[?25h[ColorTool](https://github.com/Microsoft/Terminal/tree/master/src/tools/ColorTool)Sample projects that show how to consume the Windownsole APIs
Other related repositories include:Console API Documentation
Build Status
ect|Build Status---inal|Build Status
rTool|

rminal & Console Overview
se take a few minutes to review the overview below before diving into the code:
indows Terminal▶ +0 ~0 -0 ⎇ dev/migrie/f/passthrough-2019 ▶ < ◀ [unix] ◀ 1,397 words « 3% ␊ 7/177 ㏑ : 46 ◀ Ξ [9]trmd" 177L, 11259C[?25h~@k[?25h 7  * ColorTool
8  * [Sample projects](https://github.com/Microsoft/Terminal/tree/master/samples) that show how to consume the Window
s Console APIs
9  
10 #### Other related repositories include:
11  * Console API Documentation
12 
13 ### Build Status
14 
15 Project|Build Status
16 ---|---
17 Terminal|Build Status

18 ColorTool|

19 
20 # Terminal & Console Overview
21 
22 Please take a few minutes to review the overview below before diving into the code:
23 
24 ## Windows Terminal
NORMAL ▶ +0 ~0 -0 ⎇ dev/migrie/f/passthrough-2019 ▶ < ◀ [unix] ◀ 1,397 words « 4% ␊ 8/177 ㏑ : 46 ◀ Ξ [9]trailing ◀
"README.md" 177L, 11259C[?25h~@k[?25h 8  * Sample projects that show how to consume the Window
s Console APIs
9  
10 #### Other related repositories include:
11  * Console API Documentation
12 
13 ### Build Status
14 
15 Project|Build Status
16 ---|---
17 Terminal|Build Status

18 ColorTool|

19 
20 # Terminal & Console Overview
21 
22 Please take a few minutes to review the overview below before diving into the code:
23 
24 ## Windows Terminal
NORMAL ▶ +0 ~0 -0 ⎇ dev/migrie/f/passthrough-2019 ▶ < ◀ [unix] ◀ 1,397 words « 5% ␊ 9/177 ㏑ : 2 ◀ Ξ [9]trailing ◀
"README.md" 177L, 11259C[?25h~@k[?25h10/177 ㏑ : 40 ◀ Ξ [9]tr[?25h~@k[?25h[Console API Documentation](https://github.com/MicrosoftDocs/Console-Docs)
Build Status
ect|Build Status---inal|Build Status
rTool|

rminal & Console Overview
se take a few minutes to review the overview below before diving into the code:
indows Terminal▶ +0 ~0 -0 ⎇ dev/migrie/f/passthrough-2019 ▶ < ◀ [unix] ◀ 1,397 words « 6% ␊ 11/177 ㏑ : 46 ◀ Ξ [9]trmd" 177L, 11259C[?25h~@k[?25hConsole API Documentation
Build Status
ect|Build Status---inal|Build Status
rTool|

rminal & Console Overview
se take a few minutes to review the overview below before diving into the code:
indows Terminal▶ +0 ~0 -0 ⎇ dev/migrie/f/passthrough-2019 ▶ < ◀ [unix] ◀ 1,397 words « 6% ␊ 12/177 ㏑ : 1 ◀ Ξ [9]trmd" 177L, 11259C[?25h~@k[?25h7% ␊ 13/177 ㏑ : 16 ◀ Ξ [9]tr[?25h~@k[?25h4/177 ㏑ : 1 ◀ Ξ [9]tr[?25h~@k[?25h8% ␊ 15/177 ㏑ : 20 ◀ Ξ [9]tr[?25h~@k[?25h9% ␊ 16/177 ㏑ : 7 ◀ Ξ [9]tr[?25h~@k[?25h 17 Terminal|[![Build Status](https://dev.azure.com/ms/Terminal/_apis/build/status/Terminal%20CI?branchName=master)](htt
ps://dev.azure.com/ms/Terminal/_build?definitionId=136)
18 ColorTool|

19 
20 # Terminal & Console Overview
21 
22 Please take a few minutes to review the overview below before diving into the code:
23 
24 ## Windows Terminal
NORMAL ▶ +0 ~0 -0 ⎇ dev/migrie/f/passthrough-2019 ▶ < ◀ [unix] ◀ 1,397 words « 9% ␊ 17/177 ㏑ : 46 ◀ Ξ [9]trailing ◀
"README.md" 177L, 11259C[?25h~@k[?25h 17 Terminal|Build Status

18 ColorTool|![](https://microsoft.visualstudio.com/_apis/public/build/definitions/c93e867a-8815-43c1-92c4-e7dd5404f1e1
/17023/badge)
19 
20 # Terminal & Console Overview
21 
22 Please take a few minutes to review the overview below before diving into the code:
23 
24 ## Windows Terminal
NORMAL ▶ +0 ~0 -0 ⎇ dev/migrie/f/passthrough-2019 ▶ < ◀ [unix] ◀ 1,397 words « 10% ␊ 18/177 ㏑ : 46 ◀ Ξ [9]trailing ◀
"README.md" 177L, 11259C[?25h~@k[?25h 18 ColorTool|

19 
20 # Terminal & Console Overview
21 
22 Please take a few minutes to review the overview below before diving into the code:
23 
24 ## Windows Terminal
NORMAL ▶ +0 ~0 -0 ⎇ dev/migrie/f/passthrough-2019 ▶ < ◀ [unix] ◀ 1,397 words « 10% ␊ 19/177 ㏑ : 1 ◀ Ξ [9]trailing ◀
"README.md" 177L, 11259C[?25h~@k[?25h1% ␊ 20/177 ㏑ : 29 ◀ Ξ [9]tr[?25h~@k[?25h1/177 ㏑ : 1 ◀ Ξ [9]tr[?25h~@k[?25h 2 #### This repository contains the source code for:
3 
4  * Windows Terminal
5  * The Windows console host (conhost.exe)
6  * Components shared between the two projects
7  * ColorTool
8  * Sample projects that show how to consume the Window
s Console APIs
9  
10 #### Other related repositories include:
11  * Console API Documentation
12 
13 ### Build Status
14 
15 Project|Build Status
16 ---|---
17 Terminal|Build Status

18 ColorTool|

19 
20 # Terminal & Console Overview
21 
22 Please take a few minutes to review the overview below before diving into the code:
23 
24 ## Windows Terminal
25 
NORMAL ▶ +0 ~0 -0 ⎇ dev/migrie/f/passthrough-2019 ▶ < ◀ [unix] ◀ 1,397 words « 12% ␊ 22/177 ㏑ : 46 ◀ Ξ [9]trailing ◀
[?25h~@k[?25h 5  * The Windows console host (conhost.exe)
6  * Components shared between the two projects
7  * ColorTool
8  * Sample projects that show how to consume the Window
s Console APIs
9  
10 #### Other related repositories include:
11  * Console API Documentation
12 
13 ### Build Status
14 
15 Project|Build Status
16 ---|---
17 Terminal|Build Status

18 ColorTool|

19 
20 # Terminal & Console Overview
21 
22 Please take a few minutes to review the overview below before diving into the code:
23 
24 ## Windows Terminal
25 
26 Windows Terminal is a new, modern, feature-rich, productive terminal application for command-line users. It includes
 many of the features most frequently requested by the Windows command-line community including support for tabs, ri
ch text, globalization, configurability, theming & styling, and more.
NORMAL ▶ +0 ~0 -0 ⎇ dev/migrie/f/passthrough-2019 ▶ < ◀ [unix] ◀ 1,397 words « 12% ␊ 23/177 ㏑ : 1 ◀ Ξ [9]trailing ◀
[?25h~@k[?25h3% ␊ 24/177 ㏑ : 19 ◀ Ξ [9]tr[?25h◀ [unix] ◀ 1,397 words « 13% ␊ 24/177 ㏑ : 19 ◀ Ξ [64[?25h~@k[?25h 1 # Welcome\! 
2 #### This repository contains the source code for:
3 
4  * Windows Terminal
5  * The Windows console host (conhost.exe)
6  * Components shared between the two projects
7  * ColorTool
8  * Sample projects that show how to consume the Window
s Console APIs
9  
10 #### Other related repositories include:
11  * Console API Documentation
12 
13 ### Build Status
14 
15 Project|Build Status
16 ---|---
17 Terminal|Build Status

18 ColorTool|

19 
20 # Terminal & Console Overview
21 
22 Please take a few minutes to review the overview below before diving into the code:
23 
24 ## Windows Terminal
NORMAL ▶ +0 ~0 -0 ⎇ dev/migrie/f/passthrough-2019 ▶ <◀ [unix] ◀ 1,397 words « 3% ␊ 6/177 ㏑ : 3 ◀ Ξ [64]trailing ◀
[?25h^[[?25h [?25h[?2004h:[?25hq
[?2004l]0;[?2004lMicrosoft Windows [Version 10.0.18936.1001]
(c) 2019 Microsoft Corporation. All rights reserved.

[12:00:25.93]>c:\Users\migrie\dev\private\OpenConsole>
[1] [dev/migrie/f/passthrough-2019] migrie@MIGRIE-SURFBOOK>wsl.exe
zadjii@migrie-surfbook:/mnt/c/Users/migrie/dev/private/OpenConsole$ vi README.md 
zadjii@migrie-surfbook:/mnt/c/Users/migrie/dev/private/OpenConsole$ 




















9
passthough.txt Normal file

File diff suppressed because one or more lines are too long

38
passthrough001.txt Normal file

File diff suppressed because one or more lines are too long

View File

@@ -260,24 +260,6 @@
<PRIResource Include="Resources\en-US\Resources.resw" />
</ItemGroup>
<Import Project="$(OpenConsoleDir)src\wap-common.build.post.props" />
<!--
Microsoft.UI.Xaml contains some <Content> resource files that need to be included in our package.
For some reason, they're not rolled up through dependent projects; if they were, their paths would
be wrong.
WAP Packaging projects don't actually support nuget package references, so we added one manually.
This does mean that version changes to Microsoft.UI.Xaml must be manually reflected
here.
-->
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.2.190611001-prerelease\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.2.190611001-prerelease\build\native\Microsoft.UI.Xaml.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.2.190611001-prerelease\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.2.190611001-prerelease\build\native\Microsoft.UI.Xaml.targets'))" />
</Target>
<!-- End workaround -->
<ItemGroup>
<ProjectReference Include="..\WindowsTerminal\WindowsTerminal.vcxproj" />
<ProjectReference Include="..\..\host\exe\Host.EXE.vcxproj" />

View File

@@ -61,26 +61,13 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - <none>
void App::Create(uint64_t hWnd)
void App::Create()
{
// Assert that we've already loaded our settings. We have to do
// this as a MTA, before the app is Create()'d
WINRT_ASSERT(_loadedInitialSettings);
TraceLoggingRegister(g_hTerminalAppProvider);
_Create(hWnd);
}
App::~App()
{
TraceLoggingUnregister(g_hTerminalAppProvider);
}
// Method Description:
// - Create all of the initial UI elements of the Terminal app.
// * Initializes the first terminal control, using the default profile,
// and adds it to our list of tabs.
void App::_Create(uint64_t parentHwnd)
{
/* !!! TODO
This is not the correct way to host a XAML page. This exists today because we valued
getting a .xaml over tearing out all of the terminal logic and splitting it across App
@@ -92,15 +79,21 @@ namespace winrt::TerminalApp::implementation
_root = terminalPage.as<winrt::Windows::UI::Xaml::Controls::Control>();
_tabContent = terminalPage->TabContent();
_tabRow = terminalPage->TabRow();
_tabView = terminalPage->TabView();
_newTabButton = terminalPage->NewTabButton();
_tabView = _tabRow.TabView();
_newTabButton = _tabRow.NewTabButton();
_minMaxCloseControl = terminalPage->MinMaxCloseControl();
_minMaxCloseControl.ParentWindowHandle(parentHwnd);
if (!_settings->GlobalSettings().GetShowTabsInTitlebar())
if (_settings->GlobalSettings().GetShowTabsInTitlebar())
{
_minMaxCloseControl.Visibility(Visibility::Collapsed);
// Remove the TabView from the page. We'll hang on to it, we need to
// put it in the titlebar.
uint32_t index = 0;
if (terminalPage->Root().Children().IndexOf(_tabRow, index))
{
terminalPage->Root().Children().RemoveAt(index);
}
// Inform the host that our titlebar content has changed.
_setTitleBarContentHandlers(*this, _tabRow);
}
// Event Bindings (Early)
@@ -118,6 +111,14 @@ namespace winrt::TerminalApp::implementation
_tabContent.SizeChanged({ this, &App::_OnContentSizeChanged });
}
App::~App()
{
if (g_hTerminalAppProvider)
{
TraceLoggingUnregister(g_hTerminalAppProvider);
}
}
// Method Description:
// - Show a ContentDialog with a single button to dismiss. Uses the
// FrameworkElements provided as the title and content of this dialog, and
@@ -456,16 +457,6 @@ namespace winrt::TerminalApp::implementation
winrt::Windows::System::Launcher::LaunchUriAsync({ feedbackUriValue });
}
Windows::UI::Xaml::Controls::Border App::GetDragBar() noexcept
{
if (_minMaxCloseControl)
{
return _minMaxCloseControl.DragBar();
}
return nullptr;
}
// Method Description:
// - Called when the about button is clicked. See _ShowAboutDialog for more info.
// Arguments:
@@ -493,6 +484,7 @@ namespace winrt::TerminalApp::implementation
bindings.NewTab([this]() { _OpenNewTab(std::nullopt); });
bindings.DuplicateTab([this]() { _DuplicateTabViewItem(); });
bindings.CloseTab([this]() { _CloseFocusedTab(); });
bindings.ClosePane([this]() { _CloseFocusedPane(); });
bindings.NewTabWithProfile([this](const auto index) { _OpenNewTab({ index }); });
bindings.ScrollUp([this]() { _Scroll(-1); });
bindings.ScrollDown([this]() { _Scroll(1); });
@@ -505,6 +497,7 @@ namespace winrt::TerminalApp::implementation
bindings.SwitchToTab([this](const auto index) { _SelectTab({ index }); });
bindings.OpenSettings([this]() { _OpenSettings(); });
bindings.ResizePane([this](const auto direction) { _ResizePane(direction); });
bindings.MoveFocus([this](const auto direction) { _MoveFocus(direction); });
bindings.CopyText([this](const auto trimWhitespace) { _CopyText(trimWhitespace); });
bindings.PasteText([this]() { _PasteText(); });
}
@@ -741,13 +734,16 @@ namespace winrt::TerminalApp::implementation
}
// Method Description:
// - Update the current theme of the application. This will manually update
// all of the elements in our UI to match the given theme.
// - Update the current theme of the application. This will trigger our
// RequestedThemeChanged event, to have our host change the theme of the
// root of the application.
// Arguments:
// - newTheme: The ElementTheme to apply to our elements.
void App::_ApplyTheme(const Windows::UI::Xaml::ElementTheme& newTheme)
{
_root.RequestedTheme(newTheme);
// Propagate the event to the host layer, so it can update its own UI
_requestedThemeChangedHandlers(*this, newTheme);
}
UIElement App::GetRoot() noexcept
@@ -911,20 +907,16 @@ namespace winrt::TerminalApp::implementation
// currently displayed, it will be shown.
// Arguments:
// - settings: the TerminalSettings object to use to create the TerminalControl with.
void App::_CreateNewTabFromSettings(GUID profileGuid, TerminalSettings settings, std::optional<uint64_t> serverHandle)
void App::_CreateNewTabFromSettings(GUID profileGuid, TerminalSettings settings)
{
// Initialize the new tab
TerminalConnection::ITerminalConnection connection = nullptr;
// Create a Conhost connection based on the values in our settings object.
if (!serverHandle)
{
connection = TerminalConnection::ConhostConnection(settings.Commandline(), settings.StartingDirectory(), 30, 80, winrt::guid());
}
else
{
connection = TerminalConnection::ConhostConnection(serverHandle.value(), 30, 80, winrt::guid());
}
auto connection = TerminalConnection::ConhostConnection(settings.Commandline(),
settings.StartingDirectory(),
30,
80,
winrt::guid());
TermControl term{ settings, connection };
@@ -993,6 +985,17 @@ namespace winrt::TerminalApp::implementation
_RemoveTabViewItem(focusedTab->GetTabViewItem());
}
// Method Description:
// - Close the currently focused pane. If the pane is the last pane in the
// tab, the tab will also be closed. This will happen when we handle the
// tab's Closed event.
void App::_CloseFocusedPane()
{
int focusedTabIndex = _GetFocusedTabIndex();
std::shared_ptr<Tab> focusedTab{ _tabs[focusedTabIndex] };
focusedTab->ClosePane();
}
// Method Description:
// - Move the viewport of the terminal of the currently focused tab up or
// down a number of lines. Negative values of `delta` will move the
@@ -1036,6 +1039,20 @@ namespace winrt::TerminalApp::implementation
_tabs[focusedTabIndex]->ResizePane(direction);
}
// Method Description:
// - Attempt to move focus between panes, as to focus the child on
// the other side of the separator. See Pane::NavigateFocus for details.
// - Moves the focus of the currently focused tab.
// Arguments:
// - direction: The direction to move the focus in.
// Return Value:
// - <none>
void App::_MoveFocus(const Direction& direction)
{
const auto focusedTabIndex = _GetFocusedTabIndex();
_tabs[focusedTabIndex]->NavigateFocus(direction);
}
// Method Description:
// - Copy text from the focused terminal to the Windows Clipboard
// Arguments:
@@ -1166,33 +1183,6 @@ namespace winrt::TerminalApp::implementation
return { L"Windows Terminal" };
}
void App::IncomingConnection(uint64_t serverHandle)
{
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this, serverHandle]() {
// Getting Guid for default profile
const auto globalSettings = _settings->GlobalSettings();
auto profileGuid = globalSettings.GetDefaultProfile();
TerminalSettings settings = _settings->MakeSettings(profileGuid);
_CreateNewTabFromSettings(profileGuid, settings, serverHandle);
});
}
void App::IncomingConnection(hstring cmdline, hstring workingDir)
{
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this, cmdline, workingDir]() {
// Getting Guid for default profile
const auto globalSettings = _settings->GlobalSettings();
auto profileGuid = globalSettings.GetDefaultProfile();
TerminalSettings settings = _settings->MakeSettings(profileGuid);
settings.Commandline(cmdline);
settings.StartingDirectory(workingDir);
_CreateNewTabFromSettings(profileGuid, settings);
});
}
// Method Description:
// - Additional responses to clicking on a TabView's item. Currently, just remove tab with middle click
// Arguments:
@@ -1243,7 +1233,7 @@ namespace winrt::TerminalApp::implementation
{
if (focusedTabIndex >= _tabs.size())
{
focusedTabIndex = _tabs.size() - 1;
focusedTabIndex = static_cast<int>(_tabs.size()) - 1;
}
if (focusedTabIndex < 0)
@@ -1338,7 +1328,11 @@ namespace winrt::TerminalApp::implementation
const auto controlSettings = _settings->MakeSettings(realGuid);
// Create a Conhost connection based on the values in our settings object.
TerminalConnection::ITerminalConnection controlConnection = TerminalConnection::ConhostConnection(controlSettings.Commandline(), controlSettings.StartingDirectory(), 30, 80, winrt::guid());
auto controlConnection = TerminalConnection::ConhostConnection(controlSettings.Commandline(),
controlSettings.StartingDirectory(),
30,
80,
winrt::guid());
TermControl newControl{ controlSettings, controlConnection };
@@ -1439,4 +1433,6 @@ namespace winrt::TerminalApp::implementation
// These macros will define them both for you.
DEFINE_EVENT(App, TitleChanged, _titleChangeHandlers, TerminalControl::TitleChangedEventArgs);
DEFINE_EVENT(App, LastTabClosed, _lastTabClosedHandlers, winrt::TerminalApp::LastTabClosedEventArgs);
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(App, SetTitleBarContent, _setTitleBarContentHandlers, TerminalApp::App, winrt::Windows::UI::Xaml::UIElement);
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(App, RequestedThemeChanged, _requestedThemeChangedHandlers, TerminalApp::App, winrt::Windows::UI::Xaml::ElementTheme);
}

View File

@@ -27,10 +27,7 @@ namespace winrt::TerminalApp::implementation
Windows::UI::Xaml::UIElement GetRoot() noexcept;
// Gets the current dragglable area in the non client region of the top level window
Windows::UI::Xaml::Controls::Border GetDragBar() noexcept;
void Create(uint64_t hParentWnd);
void Create();
void LoadSettings();
Windows::Foundation::Point GetLaunchDimensions(uint32_t dpi);
@@ -40,12 +37,11 @@ namespace winrt::TerminalApp::implementation
hstring GetTitle();
void IncomingConnection(uint64_t serverHandle);
void IncomingConnection(hstring cmdline, hstring workingDir);
// -------------------------------- WinRT Events ---------------------------------
DECLARE_EVENT(TitleChanged, _titleChangeHandlers, winrt::Microsoft::Terminal::TerminalControl::TitleChangedEventArgs);
DECLARE_EVENT(LastTabClosed, _lastTabClosedHandlers, winrt::TerminalApp::LastTabClosedEventArgs);
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(SetTitleBarContent, _setTitleBarContentHandlers, TerminalApp::App, winrt::Windows::UI::Xaml::UIElement);
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(RequestedThemeChanged, _requestedThemeChangedHandlers, TerminalApp::App, winrt::Windows::UI::Xaml::ElementTheme);
private:
// If you add controls here, but forget to null them either here or in
@@ -56,10 +52,9 @@ namespace winrt::TerminalApp::implementation
// (which is a root when the tabs are in the titlebar.)
Windows::UI::Xaml::Controls::Control _root{ nullptr };
Microsoft::UI::Xaml::Controls::TabView _tabView{ nullptr };
Windows::UI::Xaml::Controls::Grid _tabRow{ nullptr };
TerminalApp::TabRowControl _tabRow{ nullptr };
Windows::UI::Xaml::Controls::Grid _tabContent{ nullptr };
Windows::UI::Xaml::Controls::SplitButton _newTabButton{ nullptr };
winrt::TerminalApp::MinMaxCloseControl _minMaxCloseControl{ nullptr };
std::vector<std::shared_ptr<Tab>> _tabs;
@@ -74,7 +69,6 @@ namespace winrt::TerminalApp::implementation
std::atomic<bool> _settingsReloadQueued{ false };
void _Create(uint64_t parentHWnd);
void _CreateNewTabFlyout();
fire_and_forget _ShowDialog(const winrt::Windows::Foundation::IInspectable& titleElement,
@@ -103,11 +97,12 @@ namespace winrt::TerminalApp::implementation
void _RegisterTerminalEvents(Microsoft::Terminal::TerminalControl::TermControl term, std::shared_ptr<Tab> hostingTab);
void _CreateNewTabFromSettings(GUID profileGuid, winrt::Microsoft::Terminal::Settings::TerminalSettings settings, std::optional<uint64_t> serverHandle = std::nullopt);
void _CreateNewTabFromSettings(GUID profileGuid, winrt::Microsoft::Terminal::Settings::TerminalSettings settings);
void _OpenNewTab(std::optional<int> profileIndex);
void _DuplicateTabViewItem();
void _CloseFocusedTab();
void _CloseFocusedPane();
void _SelectNextTab(const bool bMoveRight);
void _SelectTab(const int tabIndex);
@@ -125,6 +120,7 @@ namespace winrt::TerminalApp::implementation
// MSFT:20641986: Add keybindings for New Window
void _ScrollPage(int delta);
void _ResizePane(const Direction& direction);
void _MoveFocus(const Direction& direction);
void _OnLoaded(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
void _OnTabSelectionChanged(const IInspectable& sender, const Windows::UI::Xaml::Controls::SelectionChangedEventArgs& eventArgs);

View File

@@ -4,8 +4,7 @@
namespace TerminalApp
{
delegate void LastTabClosedEventArgs();
[default_interface]
runtimeclass App : Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication
[default_interface] runtimeclass App : Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication
{
App();
@@ -16,22 +15,20 @@ namespace TerminalApp
// then it might look like TermApp just failed to activate, which will
// cause you to chase down the rabbit hole of "why is TermApp not
// registered?" when it definitely is.
void Create(UInt64 hParentWnd);
void Create();
void LoadSettings();
Windows.UI.Xaml.UIElement GetRoot();
Windows.UI.Xaml.Controls.Border GetDragBar{ get; };
Windows.Foundation.Point GetLaunchDimensions(UInt32 dpi);
Boolean GetShowTabsInTitlebar();
event Microsoft.Terminal.TerminalControl.TitleChangedEventArgs TitleChanged;
event LastTabClosedEventArgs LastTabClosed;
event Windows.Foundation.TypedEventHandler<App, Windows.UI.Xaml.UIElement> SetTitleBarContent;
event Windows.Foundation.TypedEventHandler<App, Windows.UI.Xaml.ElementTheme> RequestedThemeChanged;
String GetTitle();
void IncomingConnection(UInt64 serverHandle);
void IncomingConnection(String cmdline, String workingDir);
}
}

View File

@@ -102,6 +102,9 @@ namespace winrt::TerminalApp::implementation
case ShortcutAction::CloseTab:
_CloseTabHandlers();
return true;
case ShortcutAction::ClosePane:
_ClosePaneHandlers();
return true;
case ShortcutAction::ScrollUp:
_ScrollUpHandlers();
@@ -169,7 +172,18 @@ namespace winrt::TerminalApp::implementation
case ShortcutAction::ResizePaneDown:
_ResizePaneHandlers(Direction::Down);
return true;
case ShortcutAction::MoveFocusLeft:
_MoveFocusHandlers(Direction::Left);
return true;
case ShortcutAction::MoveFocusRight:
_MoveFocusHandlers(Direction::Right);
return true;
case ShortcutAction::MoveFocusUp:
_MoveFocusHandlers(Direction::Up);
return true;
case ShortcutAction::MoveFocusDown:
_MoveFocusHandlers(Direction::Down);
return true;
default:
return false;
}
@@ -238,6 +252,7 @@ namespace winrt::TerminalApp::implementation
DEFINE_EVENT(AppKeyBindings, NewWindow, _NewWindowHandlers, TerminalApp::NewWindowEventArgs);
DEFINE_EVENT(AppKeyBindings, CloseWindow, _CloseWindowHandlers, TerminalApp::CloseWindowEventArgs);
DEFINE_EVENT(AppKeyBindings, CloseTab, _CloseTabHandlers, TerminalApp::CloseTabEventArgs);
DEFINE_EVENT(AppKeyBindings, ClosePane, _ClosePaneHandlers, TerminalApp::ClosePaneEventArgs);
DEFINE_EVENT(AppKeyBindings, SwitchToTab, _SwitchToTabHandlers, TerminalApp::SwitchToTabEventArgs);
DEFINE_EVENT(AppKeyBindings, NextTab, _NextTabHandlers, TerminalApp::NextTabEventArgs);
DEFINE_EVENT(AppKeyBindings, PrevTab, _PrevTabHandlers, TerminalApp::PrevTabEventArgs);
@@ -251,5 +266,6 @@ namespace winrt::TerminalApp::implementation
DEFINE_EVENT(AppKeyBindings, ScrollDownPage, _ScrollDownPageHandlers, TerminalApp::ScrollDownPageEventArgs);
DEFINE_EVENT(AppKeyBindings, OpenSettings, _OpenSettingsHandlers, TerminalApp::OpenSettingsEventArgs);
DEFINE_EVENT(AppKeyBindings, ResizePane, _ResizePaneHandlers, TerminalApp::ResizePaneEventArgs);
DEFINE_EVENT(AppKeyBindings, MoveFocus, _MoveFocusHandlers, TerminalApp::MoveFocusEventArgs);
// clang-format on
}

View File

@@ -48,6 +48,7 @@ namespace winrt::TerminalApp::implementation
DECLARE_EVENT(NewWindow, _NewWindowHandlers, TerminalApp::NewWindowEventArgs);
DECLARE_EVENT(CloseWindow, _CloseWindowHandlers, TerminalApp::CloseWindowEventArgs);
DECLARE_EVENT(CloseTab, _CloseTabHandlers, TerminalApp::CloseTabEventArgs);
DECLARE_EVENT(ClosePane, _ClosePaneHandlers, TerminalApp::ClosePaneEventArgs);
DECLARE_EVENT(SwitchToTab, _SwitchToTabHandlers, TerminalApp::SwitchToTabEventArgs);
DECLARE_EVENT(NextTab, _NextTabHandlers, TerminalApp::NextTabEventArgs);
DECLARE_EVENT(PrevTab, _PrevTabHandlers, TerminalApp::PrevTabEventArgs);
@@ -61,6 +62,7 @@ namespace winrt::TerminalApp::implementation
DECLARE_EVENT(ScrollDownPage, _ScrollDownPageHandlers, TerminalApp::ScrollDownPageEventArgs);
DECLARE_EVENT(OpenSettings, _OpenSettingsHandlers, TerminalApp::OpenSettingsEventArgs);
DECLARE_EVENT(ResizePane, _ResizePaneHandlers, TerminalApp::ResizePaneEventArgs);
DECLARE_EVENT(MoveFocus, _MoveFocusHandlers, TerminalApp::MoveFocusEventArgs);
// clang-format on
private:

View File

@@ -30,6 +30,7 @@ namespace TerminalApp
NewWindow,
CloseWindow,
CloseTab,
ClosePane,
NextTab,
PrevTab,
SplitVertical,
@@ -53,6 +54,10 @@ namespace TerminalApp
ResizePaneRight,
ResizePaneUp,
ResizePaneDown,
MoveFocusLeft,
MoveFocusRight,
MoveFocusUp,
MoveFocusDown,
OpenSettings
};
@@ -64,6 +69,7 @@ namespace TerminalApp
delegate void NewWindowEventArgs();
delegate void CloseWindowEventArgs();
delegate void CloseTabEventArgs();
delegate void ClosePaneEventArgs();
delegate void NextTabEventArgs();
delegate void PrevTabEventArgs();
delegate void SplitVerticalEventArgs();
@@ -77,9 +83,9 @@ namespace TerminalApp
delegate void ScrollDownPageEventArgs();
delegate void OpenSettingsEventArgs();
delegate void ResizePaneEventArgs(Direction direction);
delegate void MoveFocusEventArgs(Direction direction);
[default_interface]
runtimeclass AppKeyBindings : Microsoft.Terminal.Settings.IKeyBindings
[default_interface] runtimeclass AppKeyBindings : Microsoft.Terminal.Settings.IKeyBindings
{
AppKeyBindings();
@@ -94,6 +100,7 @@ namespace TerminalApp
event NewWindowEventArgs NewWindow;
event CloseWindowEventArgs CloseWindow;
event CloseTabEventArgs CloseTab;
event ClosePaneEventArgs ClosePane;
event SwitchToTabEventArgs SwitchToTab;
event NextTabEventArgs NextTab;
event PrevTabEventArgs PrevTab;
@@ -107,5 +114,6 @@ namespace TerminalApp
event ScrollDownPageEventArgs ScrollDownPage;
event OpenSettingsEventArgs OpenSettings;
event ResizePaneEventArgs ResizePane;
event MoveFocusEventArgs MoveFocus;
}
}

View File

@@ -30,6 +30,7 @@ static constexpr std::string_view NewTabWithProfile8Key{ "newTabProfile8" };
static constexpr std::string_view NewWindowKey{ "newWindow" };
static constexpr std::string_view CloseWindowKey{ "closeWindow" };
static constexpr std::string_view CloseTabKey{ "closeTab" };
static constexpr std::string_view ClosePaneKey{ "closePane" };
static constexpr std::string_view SwitchtoTabKey{ "switchToTab" };
static constexpr std::string_view NextTabKey{ "nextTab" };
static constexpr std::string_view PrevTabKey{ "prevTab" };
@@ -55,6 +56,10 @@ static constexpr std::string_view ResizePaneLeftKey{ "resizePaneLeft" };
static constexpr std::string_view ResizePaneRightKey{ "resizePaneRight" };
static constexpr std::string_view ResizePaneUpKey{ "resizePaneUp" };
static constexpr std::string_view ResizePaneDownKey{ "resizePaneDown" };
static constexpr std::string_view MoveFocusLeftKey{ "moveFocusLeft" };
static constexpr std::string_view MoveFocusRightKey{ "moveFocusRight" };
static constexpr std::string_view MoveFocusUpKey{ "moveFocusUp" };
static constexpr std::string_view MoveFocusDownKey{ "moveFocusDown" };
// 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.
@@ -82,6 +87,7 @@ static const std::map<std::string_view, ShortcutAction, std::less<>> commandName
{ NewWindowKey, ShortcutAction::NewWindow },
{ CloseWindowKey, ShortcutAction::CloseWindow },
{ CloseTabKey, ShortcutAction::CloseTab },
{ ClosePaneKey, ShortcutAction::ClosePane },
{ NextTabKey, ShortcutAction::NextTab },
{ PrevTabKey, ShortcutAction::PrevTab },
{ IncreaseFontSizeKey, ShortcutAction::IncreaseFontSize },
@@ -105,6 +111,10 @@ static const std::map<std::string_view, ShortcutAction, std::less<>> commandName
{ ResizePaneRightKey, ShortcutAction::ResizePaneRight },
{ ResizePaneUpKey, ShortcutAction::ResizePaneUp },
{ ResizePaneDownKey, ShortcutAction::ResizePaneDown },
{ MoveFocusLeftKey, ShortcutAction::MoveFocusLeft },
{ MoveFocusRightKey, ShortcutAction::MoveFocusRight },
{ MoveFocusUpKey, ShortcutAction::MoveFocusUp },
{ MoveFocusDownKey, ShortcutAction::MoveFocusDown },
{ OpenSettingsKey, ShortcutAction::OpenSettings },
};

View File

@@ -287,8 +287,8 @@ void CascadiaSettings::_CreateDefaultKeybindings()
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
static_cast<int>('D') });
keyBindings.SetKeyBinding(ShortcutAction::CloseTab,
KeyChord{ KeyModifiers::Ctrl,
keyBindings.SetKeyBinding(ShortcutAction::ClosePane,
KeyChord{ KeyModifiers::Ctrl | KeyModifiers::Shift,
static_cast<int>('W') });
keyBindings.SetKeyBinding(ShortcutAction::CopyText,

View File

@@ -1,4 +1,6 @@
//
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// MinMaxCloseControl.xaml.cpp
// Implementation of the MinMaxCloseControl class
//
@@ -8,6 +10,7 @@
#include "MinMaxCloseControl.h"
#include "MinMaxCloseControl.g.cpp"
using namespace winrt::Windows::UI::Xaml;
namespace winrt::TerminalApp::implementation
{
@@ -16,59 +19,37 @@ namespace winrt::TerminalApp::implementation
InitializeComponent();
}
uint64_t MinMaxCloseControl::ParentWindowHandle() const
void MinMaxCloseControl::Maximize()
{
return reinterpret_cast<uint64_t>(_window);
VisualStateManager::GoToState(MaximizeButton(), L"WindowStateMaximized", false);
}
void MinMaxCloseControl::ParentWindowHandle(uint64_t handle)
void MinMaxCloseControl::RestoreDown()
{
_window = reinterpret_cast<HWND>(handle);
VisualStateManager::GoToState(MaximizeButton(), L"WindowStateNormal", false);
}
void MinMaxCloseControl::_OnMaximize(byte flag)
// These event handlers simply forward each buttons click events up to the
// events we've exposed.
void MinMaxCloseControl::_MinimizeClick(winrt::Windows::Foundation::IInspectable const& sender,
RoutedEventArgs const& e)
{
if (_window)
{
POINT point1 = {};
::GetCursorPos(&point1);
const LPARAM lParam = MAKELPARAM(point1.x, point1.y);
WINDOWPLACEMENT placement = { sizeof(placement) };
::GetWindowPlacement(_window, &placement);
if (placement.showCmd == SW_SHOWNORMAL)
{
winrt::Windows::UI::Xaml::VisualStateManager::GoToState(this->Maximize(), L"WindowStateMaximized", false);
::PostMessage(_window, WM_SYSCOMMAND, SC_MAXIMIZE | flag, lParam);
}
else if (placement.showCmd == SW_SHOWMAXIMIZED)
{
winrt::Windows::UI::Xaml::VisualStateManager::GoToState(this->Maximize(), L"WindowStateNormal", false);
::PostMessage(_window, WM_SYSCOMMAND, SC_RESTORE | flag, lParam);
}
}
_minimizeClickHandlers(*this, e);
}
void MinMaxCloseControl::Maximize_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e)
void MinMaxCloseControl::_MaximizeClick(winrt::Windows::Foundation::IInspectable const& sender,
RoutedEventArgs const& e)
{
_OnMaximize(HTMAXBUTTON);
_maximizeClickHandlers(*this, e);
}
void MinMaxCloseControl::_CloseClick(winrt::Windows::Foundation::IInspectable const& sender,
RoutedEventArgs const& e)
{
_closeClickHandlers(*this, e);
}
void MinMaxCloseControl::DragBar_DoubleTapped(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Input::DoubleTappedRoutedEventArgs const& e)
{
_OnMaximize(HTCAPTION);
}
void MinMaxCloseControl::Minimize_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e)
{
if (_window)
{
::PostMessage(_window, WM_SYSCOMMAND, SC_MINIMIZE | HTMINBUTTON, 0);
}
}
void MinMaxCloseControl::Close_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e)
{
::PostQuitMessage(0);
}
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(MinMaxCloseControl, MinimizeClick, _minimizeClickHandlers, TerminalApp::MinMaxCloseControl, RoutedEventArgs);
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(MinMaxCloseControl, MaximizeClick, _maximizeClickHandlers, TerminalApp::MinMaxCloseControl, RoutedEventArgs);
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(MinMaxCloseControl, CloseClick, _closeClickHandlers, TerminalApp::MinMaxCloseControl, RoutedEventArgs);
}

View File

@@ -1,4 +1,6 @@
//
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// Declaration of the MainUserControl class.
//
@@ -8,6 +10,7 @@
#include "winrt/Windows.UI.Xaml.Markup.h"
#include "winrt/Windows.UI.Xaml.Interop.h"
#include "MinMaxCloseControl.g.h"
#include "../../cascadia/inc/cppwinrt_utils.h"
namespace winrt::TerminalApp::implementation
{
@@ -15,17 +18,19 @@ namespace winrt::TerminalApp::implementation
{
MinMaxCloseControl();
void Minimize_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
void Maximize_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
void Close_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
void DragBar_DoubleTapped(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Input::DoubleTappedRoutedEventArgs const& e);
void Maximize();
void RestoreDown();
uint64_t ParentWindowHandle() const;
void ParentWindowHandle(uint64_t handle);
void _MinimizeClick(winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
void _MaximizeClick(winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
void _CloseClick(winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
private:
void _OnMaximize(byte flag);
HWND _window{ nullptr }; // non-owning handle; should not be freed in the dtor.
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(MinimizeClick, _minimizeClickHandlers, TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs);
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(MaximizeClick, _maximizeClickHandlers, TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs);
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(CloseClick, _closeClickHandlers, TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs);
};
}

View File

@@ -1,13 +1,17 @@
namespace TerminalApp
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace TerminalApp
{
[default_interface]
runtimeclass MinMaxCloseControl : Windows.UI.Xaml.Controls.StackPanel
[default_interface] runtimeclass MinMaxCloseControl : Windows.UI.Xaml.Controls.StackPanel
{
MinMaxCloseControl();
Windows.UI.Xaml.Controls.Grid Content{ get; };
Windows.UI.Xaml.Controls.Border DragBar{ get; };
void Maximize();
void RestoreDown();
UInt64 ParentWindowHandle;
event Windows.Foundation.TypedEventHandler<MinMaxCloseControl, Windows.UI.Xaml.RoutedEventArgs> MinimizeClick;
event Windows.Foundation.TypedEventHandler<MinMaxCloseControl, Windows.UI.Xaml.RoutedEventArgs> MaximizeClick;
event Windows.Foundation.TypedEventHandler<MinMaxCloseControl, Windows.UI.Xaml.RoutedEventArgs> CloseClick;
}
}

View File

@@ -1,4 +1,6 @@
<StackPanel
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
the MIT License. See LICENSE in the project root for license information. -->
<StackPanel
x:Class="TerminalApp.MinMaxCloseControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -129,13 +131,7 @@
</ResourceDictionary>
</StackPanel.Resources>
<Grid x:Name="Content"></Grid>
<Border Height="36.0"
MinWidth="160.0"
x:Name="DragBar"
Background="{ThemeResource SystemChromeLowColor}"
DoubleTapped="DragBar_DoubleTapped"/>
<Button Height="36.0" Width="45.0" x:Name="Minimize" Style="{StaticResource CaptionButton}" Click="Minimize_Click"
<Button Height="36.0" MinWidth="45.0" Width="45.0" x:Name="MinimizeButton" Style="{StaticResource CaptionButton}" Click="_MinimizeClick"
AutomationProperties.Name="Minimize">
<Button.Resources>
<ResourceDictionary>
@@ -143,7 +139,7 @@
</ResourceDictionary>
</Button.Resources>
</Button>
<Button Height="36.0" Width="45.0" x:Name="Maximize" Style="{StaticResource CaptionButton}" Click="Maximize_Click"
<Button Height="36.0" MinWidth="45.0" Width="45.0" x:Name="MaximizeButton" Style="{StaticResource CaptionButton}" Click="_MaximizeClick"
AutomationProperties.Name="Maximize">
<Button.Resources>
<ResourceDictionary>
@@ -152,7 +148,7 @@
</ResourceDictionary>
</Button.Resources>
</Button>
<Button Height="36.0" Width="45.0" x:Name="Close" Style="{StaticResource CaptionButton}" Click="Close_Click"
<Button Height="36.0" MinWidth="45.0" Width="45.0" x:Name="CloseButton" Style="{StaticResource CaptionButton}" Click="_CloseClick"
AutomationProperties.Name="Close">
<Button.Resources>
<ResourceDictionary>

View File

@@ -187,6 +187,95 @@ bool Pane::ResizePane(const Direction& direction)
return false;
}
// Method Description:
// - Attempts to handle moving focus to one of our children. If our split
// direction isn't appropriate for the move direction, then we'll return
// false, to try and let our parent handle the move. If our child we'd move
// focus to is already focused, we'll also return false, to again let our
// parent try and handle the focus movement.
// Arguments:
// - direction: The direction to move the focus in.
// Return Value:
// - true if we handled this focus move request.
bool Pane::_NavigateFocus(const Direction& direction)
{
if (!DirectionMatchesSplit(direction, _splitState))
{
return false;
}
const bool focusSecond = (direction == Direction::Right) || (direction == Direction::Down);
const auto newlyFocusedChild = focusSecond ? _secondChild : _firstChild;
// If the child we want to move focus to is _already_ focused, return false,
// to try and let our parent figure it out.
if (newlyFocusedChild->WasLastFocused())
{
return false;
}
// Transfer focus to our child, and update the focus of our tree.
newlyFocusedChild->_FocusFirstChild();
UpdateFocus();
return true;
}
// Method Description:
// - Attempts to move focus to one of our children. If we have a focused child,
// we'll try to move the focus in the direction requested.
// - If there isn't a pane that exists as a child of this pane in the correct
// direction, we'll return false. This will indicate to our parent that they
// should try and move the focus themselves. In this way, the focus can move
// up and down the tree to the correct pane.
// - This method is _very_ similar to ResizePane. Both are trying to find the
// right separator to move (focus) in a direction.
// Arguments:
// - direction: The direction to move the focus in.
// Return Value:
// - true if we or a child handled this focus move request.
bool Pane::NavigateFocus(const Direction& direction)
{
// If we're a leaf, do nothing. We can't possibly have a descendant with a
// separator the correct direction.
if (_IsLeaf())
{
return false;
}
// Check if either our first or second child is the currently focused leaf.
// If it is, and the requested move direction matches our separator, then
// we're the pane that needs to handle this focus move.
const bool firstIsFocused = _firstChild->_IsLeaf() && _firstChild->_lastFocused;
const bool secondIsFocused = _secondChild->_IsLeaf() && _secondChild->_lastFocused;
if (firstIsFocused || secondIsFocused)
{
return _NavigateFocus(direction);
}
else
{
// If neither of our children were the focused leaf, then recurse into
// our children and see if they can handle the focus move.
// For each child, if it has a focused descendant, try having that child
// handle the focus move.
// If the child wasn't able to handle the focus move, it's possible that
// there were no descendants with a separator the correct direction. If
// our separator _is_ the correct direction, then we should be the pane
// to move focus into our other child. Otherwise, just return false, as
// we couldn't handle it either.
if ((!_firstChild->_IsLeaf()) && _firstChild->_HasFocusedChild())
{
return _firstChild->NavigateFocus(direction) || _NavigateFocus(direction);
}
else if ((!_secondChild->_IsLeaf()) && _secondChild->_HasFocusedChild())
{
return _secondChild->NavigateFocus(direction) || _NavigateFocus(direction);
}
}
return false;
}
// Method Description:
// - Called when our attached control is closed. Triggers listeners to our close
// event, if we're a leaf pane.
@@ -219,6 +308,18 @@ void Pane::_ControlClosedHandler()
}
}
// Method Description:
// - Fire our Closed event to tell our parent that we should be removed.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Pane::Close()
{
// Fire our Closed event to tell our parent that we should be removed.
_closedHandlers();
}
// Method Description:
// - Get the root UIElement of this pane. There may be a single TermControl as a
// child, or an entire tree of grids and panes as children of this element.

View File

@@ -47,10 +47,13 @@ public:
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, const GUID& profile);
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
bool ResizePane(const winrt::TerminalApp::Direction& direction);
bool NavigateFocus(const winrt::TerminalApp::Direction& direction);
void SplitHorizontal(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
void SplitVertical(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
void Close();
DECLARE_EVENT(Closed, _closedHandlers, winrt::Microsoft::Terminal::TerminalControl::ConnectionClosedEventArgs);
private:
@@ -80,7 +83,9 @@ private:
void _CreateRowColDefinitions(const winrt::Windows::Foundation::Size& rootSize);
void _CreateSplitContent();
void _ApplySplitDefinitions();
bool _Resize(const winrt::TerminalApp::Direction& direction);
bool _NavigateFocus(const winrt::TerminalApp::Direction& direction);
void _CloseChild(const bool closeFirst);

View File

@@ -237,4 +237,30 @@ void Tab::ResizePane(const winrt::TerminalApp::Direction& direction)
_rootPane->ResizePane(direction);
}
// Method Description:
// - Attempt to move focus between panes, as to focus the child on
// the other side of the separator. See Pane::NavigateFocus for details.
// Arguments:
// - direction: The direction to move the focus in.
// Return Value:
// - <none>
void Tab::NavigateFocus(const winrt::TerminalApp::Direction& direction)
{
_rootPane->NavigateFocus(direction);
}
// Method Description:
// - Closes the currently focused pane in this tab. If it's the last pane in
// this tab, our Closed event will be fired (at a later time) for anyone
// registered as a handler of our close event.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Tab::ClosePane()
{
auto focused = _rootPane->GetFocusedPane();
focused->Close();
}
DEFINE_EVENT(Tab, Closed, _closedHandlers, ConnectionClosedEventArgs);

View File

@@ -25,11 +25,14 @@ public:
void UpdateFocus();
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
void ResizePane(const winrt::TerminalApp::Direction& direction);
void NavigateFocus(const winrt::TerminalApp::Direction& direction);
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, const GUID& profile);
winrt::hstring GetFocusedTitle() const;
void SetTabText(const winrt::hstring& text);
void ClosePane();
DECLARE_EVENT(Closed, _closedHandlers, winrt::Microsoft::Terminal::TerminalControl::ConnectionClosedEventArgs);
private:

View File

@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "TabRowControl.h"
#include "TabRowControl.g.cpp"
using namespace winrt;
using namespace Windows::UI::Xaml;
namespace winrt::TerminalApp::implementation
{
TabRowControl::TabRowControl()
{
InitializeComponent();
}
// Method Description:
// - Bound in the Xaml editor to the [+] button.
// Arguments:
// <unused>
void TabRowControl::OnNewTabButtonClick(IInspectable const&, Controls::SplitButtonClickEventArgs const&)
{
}
}

View File

@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "winrt/Microsoft.UI.Xaml.Controls.h"
#include "TabRowControl.g.h"
namespace winrt::TerminalApp::implementation
{
struct TabRowControl : TabRowControlT<TabRowControl>
{
TabRowControl();
void OnNewTabButtonClick(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Controls::SplitButtonClickEventArgs const& args);
};
}
namespace winrt::TerminalApp::factory_implementation
{
struct TabRowControl : TabRowControlT<TabRowControl, implementation::TabRowControl>
{
};
}

View File

@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace TerminalApp
{
[default_interface] runtimeclass TabRowControl : Windows.UI.Xaml.Controls.Grid
{
TabRowControl();
Windows.UI.Xaml.Controls.SplitButton NewTabButton { get; };
Microsoft.UI.Xaml.Controls.TabView TabView { get; };
}
}

View File

@@ -0,0 +1,31 @@
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
the MIT License. See LICENSE in the project root for license information. -->
<Grid
x:Class="TerminalApp.TabRowControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TerminalApp"
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<mux:TabView x:Name="TabView" VerticalAlignment="Bottom">
<mux:TabView.RightCustomContent>
<SplitButton
x:Name="NewTabButton"
Click="OnNewTabButtonClick"
Background="{ThemeResource SystemChromeLowColor}"
VerticalAlignment="Stretch"
HorizontalAlignment="Left">
<Viewbox MaxHeight="15"
MaxWidth="15">
<SymbolIcon Symbol="Add" />
</Viewbox>
</SplitButton>
</mux:TabView.RightCustomContent>
</mux:TabView>
</Grid>

View File

@@ -30,6 +30,8 @@
<ClInclude Include="TerminalPage.h" />
<ClInclude Include="MinMaxCloseControl.h" />
<ClInclude Include="AppKeyBindings.h" />
<ClInclude Include="TitlebarControl.h" />
<ClInclude Include="TabRowControl.h" />
<ClInclude Include="App.h" />
</ItemGroup>
<!-- ========================= Cpp Files ======================== -->
@@ -103,4 +105,32 @@
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<!--
This is a terrible, terrible rule. There exists a bug in Visual Studio 2019 16.2 and 16.3 previews
where ResolveAssemblyReferences will try and fail to parse a .lib when it produces a .winmd.
To fix that, we have to _temporarily_ replace the %(Implementation) on any winmd-producing
static library references with the empty string so as to make ResolveAssemblyReferences
not try to read it.
Upstream problem report:
https://developercommunity.visualstudio.com/content/problem/629524/static-library-reference-causes-there-was-a-proble.html
-->
<Target Name="_RemoveTerminalAppLibImplementationFromReference" BeforeTargets="ResolveAssemblyReferences">
<ItemGroup>
<_TerminalAppLibProjectReference Include="@(_ResolvedProjectReferencePaths)" Condition="'%(Filename)' == 'TerminalApp'" />
<_ResolvedProjectReferencePaths Remove="@(_TerminalAppLibProjectReference)" />
<_ResolvedProjectReferencePaths Include="@(_TerminalAppLibProjectReference)">
<Implementation />
</_ResolvedProjectReferencePaths>
</ItemGroup>
</Target>
<Target Name="_RestoreTerminalAppLibImplementationFromReference" AfterTargets="ResolveAssemblyReferences">
<ItemGroup>
<_ResolvedProjectReferencePaths Remove="@(_TerminalAppLibProjectReference)" />
<_ResolvedProjectReferencePaths Include="@(_TerminalAppLibProjectReference)" />
</ItemGroup>
</Target>
<!-- End "terrible, terrible rule" -->
</Project>

View File

@@ -15,13 +15,4 @@ namespace winrt::TerminalApp::implementation
{
InitializeComponent();
}
// Method Description:
// - Bound in the Xaml editor to the [+] button.
// Arguments:
// - sender
// - event arguments
void TerminalPage::OnNewTabButtonClick(IInspectable const&, Controls::SplitButtonClickEventArgs const&)
{
}
}

View File

@@ -12,8 +12,6 @@ namespace winrt::TerminalApp::implementation
struct TerminalPage : TerminalPageT<TerminalPage>
{
TerminalPage();
void OnNewTabButtonClick(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Controls::SplitButtonClickEventArgs const& args);
};
}

View File

@@ -3,8 +3,7 @@
namespace TerminalApp
{
[default_interface]
runtimeclass TerminalPage : Windows.UI.Xaml.Controls.Page
[default_interface] runtimeclass TerminalPage : Windows.UI.Xaml.Controls.Page
{
TerminalPage();
}

View File

@@ -10,39 +10,13 @@ the MIT License. See LICENSE in the project root for license information. -->
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid x:Name="Root" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid x:Name="TabRow" Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<mux:TabView x:Name="TabView" Grid.Column="0" />
<SplitButton
x:Name="NewTabButton"
Grid.Column="1"
Click="OnNewTabButtonClick"
Background="{ThemeResource SystemChromeLowColor}"
VerticalAlignment="Stretch"
HorizontalAlignment="Left">
<Viewbox MaxHeight="15"
MaxWidth="15">
<SymbolIcon Symbol="Add" />
</Viewbox>
</SplitButton>
<local:MinMaxCloseControl
x:Name="MinMaxCloseControl"
Grid.Column="3"
HorizontalAlignment="Right" />
</Grid>
<local:TabRowControl x:Name="TabRow" Grid.Row="0" />
<Grid x:Name="TabContent" Grid.Row="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
</Grid>

View File

@@ -0,0 +1,89 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// TitlebarControl.xaml.cpp
// Implementation of the TitlebarControl class
//
#include "pch.h"
#include "TitlebarControl.h"
#include "TitlebarControl.g.cpp"
namespace winrt::TerminalApp::implementation
{
TitlebarControl::TitlebarControl(uint64_t handle) :
_window{ reinterpret_cast<HWND>(handle) }
{
InitializeComponent();
// Register our event handlers on the MMC buttons.
MinMaxCloseControl().MinimizeClick({ this, &TitlebarControl::Minimize_Click });
MinMaxCloseControl().MaximizeClick({ this, &TitlebarControl::Maximize_Click });
MinMaxCloseControl().CloseClick({ this, &TitlebarControl::Close_Click });
}
Windows::UI::Xaml::UIElement TitlebarControl::Content()
{
return ContentRoot().Children().Size() > 0 ? ContentRoot().Children().GetAt(0) : nullptr;
}
void TitlebarControl::Content(Windows::UI::Xaml::UIElement content)
{
ContentRoot().Children().Clear();
ContentRoot().Children().Append(content);
}
void TitlebarControl::Root_SizeChanged(const IInspectable& sender, Windows::UI::Xaml::SizeChangedEventArgs const& e)
{
const auto windowWidth = ActualWidth();
const auto minMaxCloseWidth = MinMaxCloseControl().ActualWidth();
const auto dragBarMinWidth = DragBar().MinWidth();
const auto maxWidth = windowWidth - minMaxCloseWidth - dragBarMinWidth;
ContentRoot().MaxWidth(maxWidth);
}
void TitlebarControl::_OnMaximizeOrRestore(byte flag)
{
POINT point1 = {};
::GetCursorPos(&point1);
const LPARAM lParam = MAKELPARAM(point1.x, point1.y);
WINDOWPLACEMENT placement = { sizeof(placement) };
::GetWindowPlacement(_window, &placement);
if (placement.showCmd == SW_SHOWNORMAL)
{
MinMaxCloseControl().Maximize();
::PostMessage(_window, WM_SYSCOMMAND, SC_MAXIMIZE | flag, lParam);
}
else if (placement.showCmd == SW_SHOWMAXIMIZED)
{
MinMaxCloseControl().RestoreDown();
::PostMessage(_window, WM_SYSCOMMAND, SC_RESTORE | flag, lParam);
}
}
void TitlebarControl::Maximize_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e)
{
_OnMaximizeOrRestore(HTMAXBUTTON);
}
void TitlebarControl::DragBar_DoubleTapped(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Input::DoubleTappedRoutedEventArgs const& e)
{
_OnMaximizeOrRestore(HTCAPTION);
}
void TitlebarControl::Minimize_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e)
{
if (_window)
{
::PostMessage(_window, WM_SYSCOMMAND, SC_MINIMIZE | HTMINBUTTON, 0);
}
}
void TitlebarControl::Close_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e)
{
::PostQuitMessage(0);
}
}

View File

@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// Declaration of the MainUserControl class.
//
#pragma once
#include "winrt/Windows.UI.Xaml.h"
#include "winrt/Windows.UI.Xaml.Markup.h"
#include "winrt/Windows.UI.Xaml.Interop.h"
#include "TitlebarControl.g.h"
namespace winrt::TerminalApp::implementation
{
struct TitlebarControl : TitlebarControlT<TitlebarControl>
{
TitlebarControl(uint64_t handle);
Windows::UI::Xaml::UIElement Content();
void Content(Windows::UI::Xaml::UIElement content);
void Root_SizeChanged(const IInspectable& sender, Windows::UI::Xaml::SizeChangedEventArgs const& e);
void Minimize_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
void Maximize_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
void Close_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
void DragBar_DoubleTapped(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Input::DoubleTappedRoutedEventArgs const& e);
private:
void _OnMaximizeOrRestore(byte flag);
HWND _window{ nullptr }; // non-owning handle; should not be freed in the dtor.
};
}
namespace winrt::TerminalApp::factory_implementation
{
struct TitlebarControl : TitlebarControlT<TitlebarControl, implementation::TitlebarControl>
{
};
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace TerminalApp
{
[default_interface] runtimeclass TitlebarControl : Windows.UI.Xaml.Controls.Grid
{
TitlebarControl(UInt64 parentWindowHandle);
Windows.UI.Xaml.UIElement Content;
Windows.UI.Xaml.Controls.Border DragBar { get; };
}
}

View File

@@ -0,0 +1,45 @@
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
the MIT License. See LICENSE in the project root for license information. -->
<Grid
x:Class="TerminalApp.TitlebarControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TerminalApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Name="Root"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
SizeChanged="Root_SizeChanged"
d:DesignHeight="36"
Background="{ThemeResource SystemChromeLowColor}"
d:DesignWidth="400">
<!-- TODO:GH#1988
This xaml should probably be a template thing, where the background is a
resource that the app hosting this control can override. Then, it App.xaml,
we'd make sure to set the resource for our background to the appropriate
color. SystemControlForegroundAccentBrush also works nicely, to use the
accent color. (which is GH#1963)-->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid x:Name="ContentRoot" Grid.Column="0" />
<Border
x:Name="DragBar"
Grid.Column="1"
MinWidth="45.0"
DoubleTapped="DragBar_DoubleTapped"/>
<local:MinMaxCloseControl
Grid.Column="2"
x:Name="MinMaxCloseControl"
HorizontalAlignment="Right" />
</Grid>

View File

@@ -35,6 +35,12 @@
<Page Include="../TerminalPage.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="../TitlebarControl.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="../TabRowControl.xaml">
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<!-- ========================= Headers ======================== -->
@@ -47,6 +53,12 @@
<DependentUpon>../TerminalPage.xaml</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="../TitlebarControl.h">
<DependentUpon>../TitlebarControl.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="../TabRowControl.h">
<DependentUpon>../TabRowControl.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="../Tab.h" />
<ClInclude Include="../Pane.h" />
<ClInclude Include="../ColorScheme.h" />
@@ -72,6 +84,12 @@
<DependentUpon>../TerminalPage.xaml</DependentUpon>
<SubType>Code</SubType>
</ClCompile>
<ClCompile Include="../TitlebarControl.cpp">
<DependentUpon>../TitlebarControl.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="../TabRowControl.cpp">
<DependentUpon>../TabRowControl.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="../Tab.cpp" />
<ClCompile Include="../Pane.cpp" />
<ClCompile Include="../ColorScheme.cpp" />
@@ -116,6 +134,14 @@
<DependentUpon>../TerminalPage.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="../TitlebarControl.idl">
<DependentUpon>../TitlebarControl.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="../TabRowControl.idl">
<DependentUpon>../TabRowControl.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
</ItemGroup>
<!-- ========================= Misc Files ======================== -->
@@ -232,7 +258,7 @@
<Implementation>Microsoft.UI.Xaml.dll</Implementation>
<IsWinMDFile>true</IsWinMDFile>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
<Private>false</Private>
<Private>true</Private>
</Reference>
<ReferenceCopyLocalPaths Include="$(_MUXRoot)runtimes\win10-$(Native-Platform)\native\Microsoft.UI.Xaml.dll" />
<ReferenceCopyLocalPaths Include="$(_MUXRoot)runtimes\win10-$(Native-Platform)\native\Microsoft.UI.Xaml.pri" />

View File

@@ -15,6 +15,7 @@
#include <conpty-universal.h>
#include "../../types/inc/Utils.hpp"
#include "../../types/inc/UTF8OutPipeReader.hpp"
using namespace ::Microsoft::Console;
@@ -37,23 +38,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
}
}
ConhostConnection::ConhostConnection(const uint64_t server,
const uint32_t initialRows,
const uint32_t initialCols,
const guid& initialGuid) :
_initialRows{ initialRows },
_initialCols{ initialCols },
_commandline{},
_startingDirectory{},
_guid{ initialGuid },
_hServer{ reinterpret_cast<HANDLE>(server) }
{
if (_guid == guid{})
{
_guid = Utils::CreateGuid();
}
}
winrt::guid ConhostConnection::Guid() const noexcept
{
return _guid;
@@ -88,12 +72,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
startingDirectory = _startingDirectory;
}
std::optional<HANDLE> server;
if (_hServer)
{
server = _hServer.get();
}
EnvironmentVariableMapW extraEnvVars;
{
// Convert connection Guid to string and ignore the enclosing '{}'.
@@ -111,7 +89,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
startingDirectory,
static_cast<short>(_initialCols),
static_cast<short>(_initialRows),
server,
&_inPipe,
&_outPipe,
&_signalPipe,
@@ -213,39 +190,37 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
DWORD ConhostConnection::_OutputThread()
{
const size_t bufferSize = 4096;
BYTE buffer[bufferSize];
DWORD dwRead;
UTF8OutPipeReader pipeReader{ _outPipe.get() };
std::string_view strView{};
// process the data of the output pipe in a loop
while (true)
{
dwRead = 0;
bool fSuccess = false;
fSuccess = !!ReadFile(_outPipe.get(), buffer, bufferSize, &dwRead, nullptr);
if (!fSuccess)
HRESULT result = pipeReader.Read(strView);
if (FAILED(result) || result == S_FALSE)
{
if (_closing.load())
{
// This is okay, break out to kill the thread
return 0;
}
else
{
_disconnectHandlers();
return (DWORD)-1;
}
_disconnectHandlers();
return (DWORD)-1;
}
if (dwRead == 0)
if (strView.empty())
{
continue;
return 0;
}
// Convert buffer to hstring
char* pchStr = (char*)(buffer);
std::string str{ pchStr, dwRead };
auto hstr = winrt::to_hstring(str);
auto hstr{ winrt::to_hstring(strView) };
// Pass the output to our registered event handlers
_outputHandlers(hstr);
}
return 0;
}
}

View File

@@ -10,7 +10,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
struct ConhostConnection : ConhostConnectionT<ConhostConnection>
{
ConhostConnection(const hstring& cmdline, const hstring& startingDirectory, const uint32_t rows, const uint32_t cols, const guid& guid);
ConhostConnection(const uint64_t server, 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;
@@ -42,7 +41,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
wil::unique_handle _hOutputThread;
wil::unique_process_information _piConhost;
wil::unique_handle _hJob;
wil::unique_handle _hServer;
static DWORD WINAPI StaticOutputThreadProc(LPVOID lpParameter);
DWORD _OutputThread();

View File

@@ -9,7 +9,6 @@ namespace Microsoft.Terminal.TerminalConnection
runtimeclass ConhostConnection : ITerminalConnection
{
ConhostConnection(String cmdline, String startingDirectory, UInt32 rows, UInt32 columns, Guid guid);
ConhostConnection(UInt64 server, UInt32 rows, UInt32 columns, Guid guid);
Guid Guid { get; };
};

View File

@@ -606,9 +606,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (bindings)
{
KeyChord chord(
WI_IsAnyFlagSet(modifiers, CTRL_PRESSED),
WI_IsAnyFlagSet(modifiers, ALT_PRESSED),
WI_IsFlagSet(modifiers, SHIFT_PRESSED),
modifiers.IsCtrlPressed(),
modifiers.IsAltPressed(),
modifiers.IsShiftPressed(),
vkey);
handled = bindings.TryKeyChord(chord);
}
@@ -1473,8 +1473,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// find out which modifiers (ctrl, alt, shift) are pressed in events that
// don't necessarily include that state.
// Return Value:
// - The combined ControlKeyState flags as a bitfield.
DWORD TermControl::_GetPressedModifierKeys() const
// - The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states.
ControlKeyStates TermControl::_GetPressedModifierKeys() const
{
CoreWindow window = CoreWindow::GetForCurrentThread();
// DONT USE
@@ -1488,24 +1488,28 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
struct KeyModifier
{
VirtualKey vkey;
DWORD flag;
ControlKeyStates flags;
};
constexpr std::array<KeyModifier, 5> modifiers{ {
{ VirtualKey::RightMenu, RIGHT_ALT_PRESSED },
{ VirtualKey::LeftMenu, LEFT_ALT_PRESSED },
{ VirtualKey::RightControl, RIGHT_CTRL_PRESSED },
{ VirtualKey::LeftControl, LEFT_CTRL_PRESSED },
{ VirtualKey::Shift, SHIFT_PRESSED },
{ VirtualKey::RightMenu, ControlKeyStates::RightAltPressed },
{ VirtualKey::LeftMenu, ControlKeyStates::LeftAltPressed },
{ VirtualKey::RightControl, ControlKeyStates::RightCtrlPressed },
{ VirtualKey::LeftControl, ControlKeyStates::LeftCtrlPressed },
{ VirtualKey::Shift, ControlKeyStates::ShiftPressed },
} };
DWORD flags = 0;
ControlKeyStates flags;
for (const auto& mod : modifiers)
{
const auto state = window.GetKeyState(mod.vkey);
const auto isDown = WI_IsFlagSet(state, CoreVirtualKeyStates::Down);
flags |= isDown ? mod.flag : 0;
if (isDown)
{
flags |= mod.flags;
}
}
return flags;

View File

@@ -122,7 +122,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _ApplyUISettings();
void _InitializeBackgroundBrush();
void _BackgroundColorChanged(const uint32_t color);
void _ApplyConnectionSettings();
void _InitializeTerminal();
void _UpdateFont();
void _KeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
@@ -153,7 +152,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _ScrollbarUpdater(Windows::UI::Xaml::Controls::Primitives::ScrollBar scrollbar, const int viewTop, const int viewHeight, const int bufferSize);
static Windows::UI::Xaml::Thickness _ParseThicknessFromPadding(const hstring padding);
DWORD _GetPressedModifierKeys() const;
::Microsoft::Terminal::Core::ControlKeyStates _GetPressedModifierKeys() const;
const COORD _GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition);
const unsigned int _NumberOfClicks(winrt::Windows::Foundation::Point clickPos, Timestamp clickTime);

View File

@@ -0,0 +1,112 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
namespace Microsoft::Terminal::Core
{
class ControlKeyStates;
}
constexpr Microsoft::Terminal::Core::ControlKeyStates operator|(Microsoft::Terminal::Core::ControlKeyStates lhs, Microsoft::Terminal::Core::ControlKeyStates rhs) noexcept;
// This class is functionally equivalent to PowerShell's System.Management.Automation.Host.ControlKeyStates enum:
// https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.host.controlkeystates
// It's flagging values are compatible to those used by the NT console subsystem (<um/wincon.h>),
// as these are being used throughout older parts of this project.
class Microsoft::Terminal::Core::ControlKeyStates
{
struct StaticValue
{
DWORD v;
};
public:
static constexpr StaticValue RightAltPressed{ RIGHT_ALT_PRESSED };
static constexpr StaticValue LeftAltPressed{ LEFT_ALT_PRESSED };
static constexpr StaticValue RightCtrlPressed{ RIGHT_CTRL_PRESSED };
static constexpr StaticValue LeftCtrlPressed{ LEFT_CTRL_PRESSED };
static constexpr StaticValue ShiftPressed{ SHIFT_PRESSED };
static constexpr StaticValue NumlockOn{ NUMLOCK_ON };
static constexpr StaticValue ScrolllockOn{ SCROLLLOCK_ON };
static constexpr StaticValue CapslockOn{ CAPSLOCK_ON };
static constexpr StaticValue EnhancedKey{ ENHANCED_KEY };
constexpr ControlKeyStates() noexcept :
_value(0) {}
constexpr ControlKeyStates(StaticValue value) noexcept :
_value(value.v) {}
explicit constexpr ControlKeyStates(DWORD value) noexcept :
_value(value) {}
ControlKeyStates& operator|=(ControlKeyStates rhs) noexcept
{
_value |= rhs.Value();
return *this;
}
constexpr DWORD Value() const noexcept
{
return _value;
}
constexpr bool IsShiftPressed() const noexcept
{
return IsAnyFlagSet(ShiftPressed);
}
constexpr bool IsAltPressed() const noexcept
{
return IsAnyFlagSet(RightAltPressed | LeftAltPressed);
}
constexpr bool IsCtrlPressed() const noexcept
{
return IsAnyFlagSet(RightCtrlPressed | LeftCtrlPressed);
}
constexpr bool IsAltGrPressed() const noexcept
{
return AreAllFlagsSet(RightAltPressed | LeftCtrlPressed);
}
constexpr bool IsModifierPressed() const noexcept
{
return IsAnyFlagSet(RightAltPressed | LeftAltPressed | RightCtrlPressed | LeftCtrlPressed | ShiftPressed);
}
private:
constexpr bool AreAllFlagsSet(ControlKeyStates mask) const noexcept
{
return (Value() & mask.Value()) == mask.Value();
}
constexpr bool IsAnyFlagSet(ControlKeyStates mask) const noexcept
{
return (Value() & mask.Value()) != 0;
}
DWORD _value;
};
constexpr Microsoft::Terminal::Core::ControlKeyStates operator|(Microsoft::Terminal::Core::ControlKeyStates lhs, Microsoft::Terminal::Core::ControlKeyStates rhs) noexcept
{
return Microsoft::Terminal::Core::ControlKeyStates{ lhs.Value() | rhs.Value() };
}
constexpr Microsoft::Terminal::Core::ControlKeyStates operator&(Microsoft::Terminal::Core::ControlKeyStates lhs, Microsoft::Terminal::Core::ControlKeyStates rhs) noexcept
{
return Microsoft::Terminal::Core::ControlKeyStates{ lhs.Value() & rhs.Value() };
}
constexpr bool operator==(Microsoft::Terminal::Core::ControlKeyStates lhs, Microsoft::Terminal::Core::ControlKeyStates rhs) noexcept
{
return lhs.Value() == rhs.Value();
}
constexpr bool operator!=(Microsoft::Terminal::Core::ControlKeyStates lhs, Microsoft::Terminal::Core::ControlKeyStates rhs) noexcept
{
return lhs.Value() != rhs.Value();
}

View File

@@ -2,6 +2,9 @@
// Licensed under the MIT license.
#pragma once
#include "ControlKeyStates.hpp"
namespace Microsoft::Terminal::Core
{
class ITerminalInput
@@ -9,7 +12,7 @@ namespace Microsoft::Terminal::Core
public:
virtual ~ITerminalInput() {}
virtual bool SendKeyEvent(const WORD vkey, const DWORD modifiers) = 0;
virtual bool SendKeyEvent(const WORD vkey, const ControlKeyStates states) = 0;
// void SendMouseEvent(uint row, uint col, KeyModifiers modifiers);
[[nodiscard]] virtual HRESULT UserResize(const COORD size) noexcept = 0;

View File

@@ -197,11 +197,11 @@ void Terminal::Write(std::wstring_view stringView)
// real character out of the event.
// Arguments:
// - vkey: The vkey of the key pressed.
// - modifiers: The current ControlKeyState flags.
// - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states.
// Return Value:
// - true if we translated the key event, and it should not be processed any further.
// - false if we did not translate the key, and it should be processed into a character.
bool Terminal::SendKeyEvent(const WORD vkey, const DWORD modifiers)
bool Terminal::SendKeyEvent(const WORD vkey, const ControlKeyStates states)
{
if (_snapOnInput && _scrollOffset != 0)
{
@@ -210,8 +210,6 @@ bool Terminal::SendKeyEvent(const WORD vkey, const DWORD modifiers)
_NotifyScrollEvent();
}
KeyEvent keyEv{ true, 0, vkey, 0, UNICODE_NULL, modifiers };
// AltGr key combinations don't always contain any meaningful,
// pretranslated unicode character during WM_KEYDOWN.
// E.g. on a German keyboard AltGr+Q should result in a "@" character,
@@ -219,50 +217,58 @@ bool Terminal::SendKeyEvent(const WORD vkey, const DWORD modifiers)
// By returning false though, we can abort handling this WM_KEYDOWN
// event and let the WM_CHAR handler kick in, which will be
// provided with an appropriate unicode character.
if (keyEv.IsAltGrPressed())
if (states.IsAltGrPressed())
{
return false;
}
const auto ctrlPressed = keyEv.IsCtrlPressed();
const auto altPressed = keyEv.IsAltPressed();
const auto shiftPressed = keyEv.IsShiftPressed();
// Alt key sequences _require_ the char to be in the keyevent. If alt is
// pressed, manually get the character that's being typed, and put it in the
// KeyEvent.
// DON'T manually handle Alt+Space - the system will use this to bring up
// the system menu for restore, min/maximimize, size, move, close
wchar_t ch = UNICODE_NULL;
if (altPressed && vkey != VK_SPACE)
if (states.IsAltPressed() && vkey != VK_SPACE)
{
ch = static_cast<wchar_t>(LOWORD(MapVirtualKey(vkey, MAPVK_VK_TO_CHAR)));
// MapVirtualKey will give us the capitalized version of the char.
// However, if shift isn't pressed, we want to send the lowercase version.
// (See GH#637)
if (!shiftPressed)
if (!states.IsShiftPressed())
{
ch = towlower(ch);
}
}
// Manually handle Ctrl+H. Ctrl+H should be handled as Backspace. To do this
// correctly, the keyEvents's char needs to be set to Backspace.
// 0x48 is the VKEY for 'H', which isn't named
if (ctrlPressed && vkey == 0x48)
if (states.IsCtrlPressed())
{
ch = UNICODE_BACKSPACE;
}
// Manually handle Ctrl+Space here. The terminalInput translator requires
// the char to be set to Space for space handling to work correctly.
if (ctrlPressed && vkey == VK_SPACE)
{
ch = UNICODE_SPACE;
switch (vkey)
{
case 0x48:
// Manually handle Ctrl+H. Ctrl+H should be handled as Backspace. To do this
// correctly, the keyEvents's char needs to be set to Backspace.
// 0x48 is the VKEY for 'H', which isn't named
ch = UNICODE_BACKSPACE;
break;
case VK_SPACE:
// Manually handle Ctrl+Space here. The terminalInput translator requires
// the char to be set to Space for space handling to work correctly.
ch = UNICODE_SPACE;
break;
}
}
keyEv.SetCharData(ch);
// Manually handle Escape here. If we let it fall through, it'll come
// back up through the character handler. It's registered as a translation
// in TerminalInput, so we'll let TerminalInput control it.
if (vkey == VK_ESCAPE)
{
ch = UNICODE_ESC;
}
const bool manuallyHandled = ch != UNICODE_NULL;
KeyEvent keyEv{ true, 0, vkey, 0, ch, states.Value() };
const bool translated = _terminalInput->HandleKey(&keyEv);
return translated && manuallyHandled;

View File

@@ -76,7 +76,7 @@ public:
#pragma region ITerminalInput
// These methods are defined in Terminal.cpp
bool SendKeyEvent(const WORD vkey, const DWORD modifiers) override;
bool SendKeyEvent(const WORD vkey, const Microsoft::Terminal::Core::ControlKeyStates states) override;
[[nodiscard]] HRESULT UserResize(const COORD viewportSize) noexcept override;
void UserScrollViewport(const int viewTop) override;
int GetScrollOffset() override;

View File

@@ -14,6 +14,7 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\ControlKeyStates.hpp" />
<ClInclude Include="..\TerminalDispatch.hpp" />
<ClInclude Include="..\ITerminalApi.hpp" />
<ClInclude Include="..\pch.h" />

View File

@@ -48,12 +48,12 @@ namespace TerminalCoreUnitTests
// Verify that Alt+a generates a lowercase a on the input
expectedinput = L"\x1b"
"a";
VERIFY_IS_TRUE(term.SendKeyEvent(L'A', LEFT_ALT_PRESSED));
VERIFY_IS_TRUE(term.SendKeyEvent(L'A', ControlKeyStates::LeftAltPressed));
// Verify that Alt+shift+a generates a uppercase a on the input
expectedinput = L"\x1b"
"A";
VERIFY_IS_TRUE(term.SendKeyEvent(L'A', LEFT_ALT_PRESSED | SHIFT_PRESSED));
VERIFY_IS_TRUE(term.SendKeyEvent(L'A', ControlKeyStates::LeftAltPressed | ControlKeyStates::ShiftPressed));
}
void InputTest::AltSpace()
@@ -61,6 +61,6 @@ namespace TerminalCoreUnitTests
// Make sure we don't handle Alt+Space. The system will use this to
// bring up the system menu for restore, min/maximimize, size, move,
// close
VERIFY_IS_FALSE(term.SendKeyEvent(L' ', LEFT_ALT_PRESSED));
VERIFY_IS_FALSE(term.SendKeyEvent(L' ', ControlKeyStates::LeftAltPressed));
}
}

View File

@@ -61,25 +61,27 @@ AppHost::~AppHost()
void AppHost::Initialize()
{
_window->Initialize();
const auto handle = _window->GetHandle();
_app.Create(reinterpret_cast<uint64_t>(handle));
if (_useNonClientArea)
{
// Register our callbar for when the app's non-client content changes.
// This has to be done _before_ App::Create, as the app might set the
// content in Create.
_app.SetTitleBarContent({ this, &AppHost::_UpdateTitleBarContent });
}
_app.RequestedThemeChanged({ this, &AppHost::_UpdateTheme });
_app.Create();
_app.TitleChanged({ this, &AppHost::AppTitleChanged });
_app.LastTabClosed({ this, &AppHost::LastTabClosed });
AppTitleChanged(_app.GetTitle());
_window->OnAppInitialized(_app);
}
void AppHost::IncomingConnectionByHandle(HANDLE handle)
{
_app.IncomingConnection(reinterpret_cast<uint64_t>(handle));
}
void AppHost::IncomingConnectionByLaunch(std::wstring_view cmdline, std::wstring_view workingDir)
{
_app.IncomingConnection(winrt::hstring(cmdline), winrt::hstring(workingDir));
// Set up the content of the application. If the app has a custom titlebar,
// set that content as well.
_window->SetContent(_app.GetRoot());
_window->OnAppInitialized();
}
// Method Description:
@@ -193,3 +195,32 @@ void AppHost::_HandleCreateWindow(const HWND hwnd, const RECT proposedRect)
// the originally proposed window size.
LOG_LAST_ERROR_IF(!succeeded);
}
// Method Description:
// - Called when the app wants to set its titlebar content. We'll take the
// UIElement and set the Content property of our Titlebar that element.
// Arguments:
// - sender: unused
// - arg: the UIElement to use as the new Titlebar content.
// Return Value:
// - <none>
void AppHost::_UpdateTitleBarContent(const winrt::TerminalApp::App&, const winrt::Windows::UI::Xaml::UIElement& arg)
{
if (_useNonClientArea)
{
(static_cast<NonClientIslandWindow*>(_window.get()))->SetTitlebarContent(arg);
}
}
// Method Description:
// - Called when the app wants to change its theme. We'll forward this to the
// IslandWindow, so it can update the root UI element of the entire XAML tree.
// Arguments:
// - sender: unused
// - arg: the ElementTheme to use as the new theme for the UI
// Return Value:
// - <none>
void AppHost::_UpdateTheme(const winrt::TerminalApp::App&, const winrt::Windows::UI::Xaml::ElementTheme& arg)
{
_window->UpdateTheme(arg);
}

View File

@@ -14,8 +14,6 @@ public:
AppHost() noexcept;
virtual ~AppHost();
void IncomingConnectionByHandle(HANDLE handle);
void IncomingConnectionByLaunch(std::wstring_view cmdline, std::wstring_view workingDir);
void AppTitleChanged(winrt::hstring newTitle);
void LastTabClosed();
void Initialize();
@@ -27,4 +25,8 @@ private:
winrt::TerminalApp::App _app;
void _HandleCreateWindow(const HWND hwnd, const RECT proposedRect);
void _UpdateTitleBarContent(const winrt::TerminalApp::App& sender,
const winrt::Windows::UI::Xaml::UIElement& arg);
void _UpdateTheme(const winrt::TerminalApp::App&,
const winrt::Windows::UI::Xaml::ElementTheme& arg);
};

View File

@@ -79,7 +79,7 @@ void IslandWindow::Close()
// Method Description:
// - Set a callback to be called when we process a WM_CREATE message. This gives
// the AppHost a chance to resize the window to the proper size.
// the AppHost a chance to resize the window to the propoer size.
// Arguments:
// - pfn: a function to be called during the handling of WM_CREATE. It takes two
// parameters:
@@ -196,22 +196,41 @@ void IslandWindow::OnResize(const UINT width, const UINT height)
// - Called when the window is minimized to the taskbar.
void IslandWindow::OnMinimize()
{
// TODO MSFT#21315817 Stop rendering island content when the app is minimized.
// TODO GH#1989 Stop rendering island content when the app is minimized.
}
// Method Description:
// - Called when the window is restored from having been minimized.
void IslandWindow::OnRestore()
{
// TODO MSFT#21315817 Stop rendering island content when the app is minimized.
// TODO GH#1989 Stop rendering island content when the app is minimized.
}
void IslandWindow::OnAppInitialized(winrt::TerminalApp::App app)
void IslandWindow::SetContent(winrt::Windows::UI::Xaml::UIElement content)
{
_rootGrid.Children().Clear();
_rootGrid.Children().Append(app.GetRoot());
_rootGrid.Children().Append(content);
}
void IslandWindow::OnAppInitialized()
{
// Do a quick resize to force the island to paint
const auto size = GetPhysicalSize();
OnSize(size.cx, size.cy);
}
// Method Description:
// - Called when the app wants to change its theme. We'll update the root UI
// element of the entire XAML tree, so that all UI elements get the theme
// applied.
// Arguments:
// - arg: the ElementTheme to use as the new theme for the UI
// Return Value:
// - <none>
void IslandWindow::UpdateTheme(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme)
{
_rootGrid.RequestedTheme(requestedTheme);
// Invalidate the window rect, so that we'll repaint any elements we're
// drawing ourselves to match the new theme
::InvalidateRect(_window.get(), nullptr, false);
}

View File

@@ -20,12 +20,15 @@ public:
void OnResize(const UINT width, const UINT height) override;
void OnMinimize() override;
void OnRestore() override;
virtual void OnAppInitialized(winrt::TerminalApp::App app);
virtual void OnAppInitialized();
virtual void SetContent(winrt::Windows::UI::Xaml::UIElement content);
void Initialize();
virtual void Initialize();
void SetCreateCallback(std::function<void(const HWND, const RECT)> pfn) noexcept;
void UpdateTheme(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme);
protected:
void ForceResize()
{

View File

@@ -42,17 +42,82 @@ NonClientIslandWindow::~NonClientIslandWindow()
// - <unused>
// Return Value:
// - <none>
void NonClientIslandWindow::OnDragBarSizeChanged(winrt::Windows::Foundation::IInspectable /*sender*/, winrt::Windows::UI::Xaml::SizeChangedEventArgs /*eventArgs*/)
void NonClientIslandWindow::OnDragBarSizeChanged(winrt::Windows::Foundation::IInspectable /*sender*/,
winrt::Windows::UI::Xaml::SizeChangedEventArgs /*eventArgs*/)
{
_UpdateDragRegion();
}
void NonClientIslandWindow::OnAppInitialized(winrt::TerminalApp::App app)
void NonClientIslandWindow::OnAppInitialized()
{
_dragBar = app.GetDragBar();
IslandWindow::OnAppInitialized();
}
void NonClientIslandWindow::Initialize()
{
IslandWindow::Initialize();
// Set up our grid of content. We'll use _rootGrid as our root element.
// There will be two children of this grid - the TitlebarControl, and the
// "client content"
_rootGrid.Children().Clear();
Controls::RowDefinition titlebarRow{};
Controls::RowDefinition contentRow{};
titlebarRow.Height(GridLengthHelper::Auto());
_rootGrid.RowDefinitions().Append(titlebarRow);
_rootGrid.RowDefinitions().Append(contentRow);
// Create our titlebar control
_titlebar = winrt::TerminalApp::TitlebarControl{ reinterpret_cast<uint64_t>(GetHandle()) };
_dragBar = _titlebar.DragBar();
_rootGrid.SizeChanged({ this, &NonClientIslandWindow::OnDragBarSizeChanged });
IslandWindow::OnAppInitialized(app);
_rootGrid.Children().Append(_titlebar);
Controls::Grid::SetRow(_titlebar, 0);
}
// Method Description:
// - Set the content of the "client area" of our window to the given content.
// Arguments:
// - content: the new UI element to use as the client content
// Return Value:
// - <none>
void NonClientIslandWindow::SetContent(winrt::Windows::UI::Xaml::UIElement content)
{
_clientContent = content;
_rootGrid.Children().Append(content);
// SetRow only works on FrameworkElement's, so cast it to a FWE before
// calling. We know that our content is a Grid, so we don't need to worry
// about this.
const auto fwe = content.try_as<winrt::Windows::UI::Xaml::FrameworkElement>();
if (fwe)
{
Controls::Grid::SetRow(fwe, 1);
}
}
// Method Description:
// - Set the content of the "titlebar area" of our window to the given content.
// Arguments:
// - content: the new UI element to use as the titlebar content
// Return Value:
// - <none>
void NonClientIslandWindow::SetTitlebarContent(winrt::Windows::UI::Xaml::UIElement content)
{
_titlebar.Content(content);
// When the size of the titlebar content changes, we want to make sure to
// update the size of the drag region as well.
const auto fwe = content.try_as<winrt::Windows::UI::Xaml::FrameworkElement>();
if (fwe)
{
fwe.SizeChanged({ this, &NonClientIslandWindow::OnDragBarSizeChanged });
}
}
RECT NonClientIslandWindow::GetDragAreaRect() const noexcept
@@ -61,7 +126,12 @@ RECT NonClientIslandWindow::GetDragAreaRect() const noexcept
{
const auto scale = GetCurrentDpiScale();
const auto transform = _dragBar.TransformToVisual(_rootGrid);
const auto logicalDragBarRect = winrt::Windows::Foundation::Rect{ 0.0f, 0.0f, static_cast<float>(_dragBar.ActualWidth()), static_cast<float>(_dragBar.ActualHeight()) };
const auto logicalDragBarRect = winrt::Windows::Foundation::Rect{
0.0f,
0.0f,
static_cast<float>(_dragBar.ActualWidth()),
static_cast<float>(_dragBar.ActualHeight())
};
const auto clientDragBarRect = transform.TransformBounds(logicalDragBarRect);
RECT dragBarRect = {
static_cast<LONG>(clientDragBarRect.X * scale),
@@ -118,8 +188,14 @@ void NonClientIslandWindow::OnSize(const UINT width, const UINT height)
_rootGrid.Arrange(finalRect);
}
// I'm not sure that HWND_BOTTOM is any different than HWND_TOP for us.
winrt::check_bool(SetWindowPos(_interopWindowHandle, HWND_BOTTOM, xPos, yPos, windowsWidth, windowsHeight, SWP_SHOWWINDOW));
// I'm not sure that HWND_BOTTOM does anything differnet than HWND_TOP for us.
winrt::check_bool(SetWindowPos(_interopWindowHandle,
HWND_BOTTOM,
xPos,
yPos,
windowsWidth,
windowsHeight,
SWP_SHOWWINDOW));
}
// Method Description:
@@ -143,6 +219,7 @@ void NonClientIslandWindow::_UpdateDragRegion()
const auto width = windowRect.right - windowRect.left;
const auto height = windowRect.bottom - windowRect.top;
const auto scale = GetCurrentDpiScale();
const auto dpi = ::GetDpiForWindow(_window.get());
const auto dragY = ::GetSystemMetricsForDpi(SM_CYDRAG, dpi);
@@ -318,11 +395,14 @@ RECT NonClientIslandWindow::GetMaxWindowRectInPixels(const RECT* const prcSugges
// First get the monitor pointer from either the active window or the default location (0,0,0,0)
HMONITOR hMonitor = nullptr;
// NOTE: We must use the nearest monitor because sometimes the system moves the window around into strange spots while performing snap and Win+D operations.
// Those operations won't work correctly if we use MONITOR_DEFAULTTOPRIMARY.
// NOTE: We must use the nearest monitor because sometimes the system moves
// the window around into strange spots while performing snap and Win+D
// operations. Those operations won't work correctly if we use
// MONITOR_DEFAULTTOPRIMARY.
if (!EqualRect(&rc, &rcZero))
{
// For invalid window handles or when we were passed a non-zero suggestion rectangle, get the monitor from the rect.
// For invalid window handles or when we were passed a non-zero
// suggestion rectangle, get the monitor from the rect.
hMonitor = MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST);
}
else
@@ -331,8 +411,9 @@ RECT NonClientIslandWindow::GetMaxWindowRectInPixels(const RECT* const prcSugges
hMonitor = MonitorFromWindow(_window.get(), MONITOR_DEFAULTTONEAREST);
}
// If for whatever reason there is no monitor, we're going to give back whatever we got since we can't figure anything out.
// We won't adjust the DPI either. That's OK. DPI doesn't make much sense with no display.
// If for whatever reason there is no monitor, we're going to give back
// whatever we got since we can't figure anything out. We won't adjust the
// DPI either. That's OK. DPI doesn't make much sense with no display.
if (nullptr == hMonitor)
{
return rc;
@@ -344,9 +425,11 @@ RECT NonClientIslandWindow::GetMaxWindowRectInPixels(const RECT* const prcSugges
GetMonitorInfoW(hMonitor, &MonitorInfo);
// We have to make a correction to the work area. If we actually consume the entire work area (by maximizing the window)
// The window manager will render the borders off-screen.
// We need to pad the work rectangle with the border dimensions to represent the actual max outer edges of the window rect.
// We have to make a correction to the work area. If we actually consume the
// entire work area (by maximizing the window). The window manager will
// render the borders off-screen. We need to pad the work rectangle with the
// border dimensions to represent the actual max outer edges of the window
// rect.
WINDOWINFO wi = { 0 };
wi.cbSize = sizeof(WINDOWINFO);
GetWindowInfo(_window.get(), &wi);
@@ -464,8 +547,8 @@ RECT NonClientIslandWindow::GetMaxWindowRectInPixels(const RECT* const prcSugges
const auto yPos = _isMaximized ? _maximizedMargins.cyTopHeight : dragY;
// Create brush for borders, titlebar color.
const auto backgroundBrush = _dragBar.Background();
const auto backgroundSolidBrush = backgroundBrush.as<winrt::Windows::UI::Xaml::Media::SolidColorBrush>();
const auto backgroundBrush = _titlebar.Background();
const auto backgroundSolidBrush = backgroundBrush.as<Media::SolidColorBrush>();
const auto backgroundColor = backgroundSolidBrush.Color();
const auto color = RGB(backgroundColor.R, backgroundColor.G, backgroundColor.B);
_backgroundBrush = wil::unique_hbrush(CreateSolidBrush(color));
@@ -475,12 +558,16 @@ RECT NonClientIslandWindow::GetMaxWindowRectInPixels(const RECT* const prcSugges
const auto cx = windowRect.right - windowRect.left;
const auto cy = windowRect.bottom - windowRect.top;
// Fill in the _entire_ titlebar area.
RECT dragBarRect = {};
dragBarRect.left = xPos;
dragBarRect.right = xPos + cx;
dragBarRect.top = yPos;
dragBarRect.bottom = yPos + cy;
// Fill in ONLY the titlebar area. If we paint the _entirety_ of the
// window rect here, the single pixel of the bottom border (set in
// _UpdateFrameMargins) will be drawn, and blend with whatever the
// border color is.
RECT dragBarRect = GetDragAreaRect();
const auto dragHeight = RECT_HEIGHT(&dragBarRect);
dragBarRect.left = 0;
dragBarRect.right = cx;
dragBarRect.top = 0;
dragBarRect.bottom = dragHeight + yPos;
::FillRect(hdc.get(), &dragBarRect, _backgroundBrush.get());
// Draw the top window border

View File

@@ -36,9 +36,16 @@ public:
MARGINS GetFrameMargins() const noexcept;
void OnAppInitialized(winrt::TerminalApp::App app) override;
void Initialize() override;
void OnAppInitialized() override;
void SetContent(winrt::Windows::UI::Xaml::UIElement content) override;
void SetTitlebarContent(winrt::Windows::UI::Xaml::UIElement content);
private:
winrt::TerminalApp::TitlebarControl _titlebar{ nullptr };
winrt::Windows::UI::Xaml::UIElement _clientContent{ nullptr };
wil::unique_hbrush _backgroundBrush;
wil::unique_hrgn _dragBarRegion;

View File

@@ -1,7 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0-preview6.2\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0-preview6.2\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" />
<PropertyGroup>
<ConfigurationType>Application</ConfigurationType>
<SubSystem>Windows</SubSystem>
@@ -72,46 +74,17 @@
</ItemGroup>
<!-- Dependencies -->
<ItemGroup>
<!-- Manually include the .pri files from the app project as content files. -->
<NativeReferenceFile Include="$(OpenConsoleDir)$(Platform)\$(Configuration)\TerminalAppLib\*.pri">
<DeploymentContent>true</DeploymentContent>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</NativeReferenceFile>
<!-- Manually include the xbf files from the app project as content files -->
<NativeReferenceFile Include="$(OpenConsoleDir)$(Platform)\$(Configuration)\TerminalAppLib\*.xbf">
<DeploymentContent>true</DeploymentContent>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</NativeReferenceFile>
<!--
the packaging project wont 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
-->
<!-- Even though we do have proper recursive dependencies, we want to keep some of these here
so that the AppX Manifest contains their activatable classes. -->
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalSettings\TerminalSettings.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\TerminalControl.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalConnection\TerminalConnection.vcxproj" />
<!-- Don't add a ProjectReference to TerminalApp here. If you do, the
packaging project will find the TerminalApp.pri from both the TerminalAppLib
and TerminalApp project, and we only want this lib project's pri file. We'll
manually add a reference to the TerminalApp winmd and dll below, because we
still need those. -->
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\TerminalApp.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj" />
</ItemGroup>
<PropertyGroup>
<!-- A small helper for paths to the compiled cppwinrt projects -->
<_BinRoot Condition="'$(Platform)' != 'Win32'">$(OpenConsoleDir)$(Platform)\$(Configuration)\</_BinRoot>
<_BinRoot Condition="'$(Platform)' == 'Win32'">$(OpenConsoleDir)$(Configuration)\</_BinRoot>
</PropertyGroup>
<ItemGroup>
<!-- Manually reference TerminalApp, since we can't use a ProjectReference. -->
<Reference Include="TerminalApp">
<HintPath>$(_BinRoot)\TerminalApp\TerminalApp.winmd</HintPath>
<IsWinMDFile>true</IsWinMDFile>
<Private>false</Private>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
</Reference>
<ReferenceCopyLocalPaths Include="$(_BinRoot)\TerminalApp\TerminalApp.dll" />
</ItemGroup>
<!--
This ItemGroup and the Globals PropertyGroup below it are required in order
to enable F5 debugging for the unpackaged application
@@ -127,6 +100,7 @@
<!-- DON'T REDIRECT OUR OUTPUT -->
<NoOutputRedirection>true</NoOutputRedirection>
</PropertyGroup>
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.2.190611001-prerelease\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.2.190611001-prerelease\build\native\Microsoft.UI.Xaml.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -138,9 +112,38 @@
<Error Condition="!Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0-preview6.2\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0-preview6.2\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.0-rc\build\native\Microsoft.VCRTForwarders.140.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.0-rc\build\native\Microsoft.VCRTForwarders.140.targets'))" />
</Target>
<Import Project="$(OpenConsoleDir)src\common.build.post.props" />
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<Import Project="..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.190521.3\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.190521.3\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0-preview6.2\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0-preview6.2\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
<Import Project="..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.0-rc\build\native\Microsoft.VCRTForwarders.140.targets" Condition="Exists('..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.0-rc\build\native\Microsoft.VCRTForwarders.140.targets')" />
</Project>
<!-- Override GetPackagingOutputs to roll up all our dependencies.
This ensures that when the WAP packaging project asks what files go into
the package, we tell it.
This is a heavily stripped version of the one in Microsoft.*.AppxPackage.targets.
-->
<PropertyGroup>
<_ContinueOnError Condition="'$(BuildingProject)' == 'true'">true</_ContinueOnError>
<_ContinueOnError Condition="'$(BuildingProject)' != 'true'">false</_ContinueOnError>
</PropertyGroup>
<Target Name="GetPackagingOutputs" Returns="@(PackagingOutputs)">
<MSBuild
Projects="@(ProjectReferenceWithConfiguration)"
Targets="GetPackagingOutputs"
BuildInParallel="$(BuildInParallel)"
Properties="%(ProjectReferenceWithConfiguration.SetConfiguration); %(ProjectReferenceWithConfiguration.SetPlatform)"
Condition="'@(ProjectReferenceWithConfiguration)' != ''
and '%(ProjectReferenceWithConfiguration.BuildReference)' == 'true'
and '%(ProjectReferenceWithConfiguration.ReferenceOutputAssembly)' == 'true'"
ContinueOnError="$(_ContinueOnError)">
<Output TaskParameter="TargetOutputs" ItemName="_PackagingOutputsFromOtherProjects"/>
</MSBuild>
<ItemGroup>
<PackagingOutputs Include="@(_PackagingOutputsFromOtherProjects)" />
</ItemGroup>
</Target>
</Project>

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
</ItemGroup>
<ItemGroup>
<Manifest Include="WindowsTerminal.manifest" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="AppHost.cpp" />
<ClCompile Include="IslandWindow.cpp" />
<ClCompile Include="NonClientIslandWindow.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="AppHost.h" />
<ClInclude Include="BaseWindow.h" />
<ClInclude Include="IslandWindow.h" />
<ClInclude Include="NonClientIslandWindow.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="WindowsTerminal.rc" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>

View File

@@ -3,7 +3,6 @@
#include "pch.h"
#include "AppHost.h"
#include "..\..\types\Manager.h"
#include "resource.h"
using namespace winrt;
@@ -108,16 +107,6 @@ int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
// provides an implementation of Windows.UI.Xaml.Application.
AppHost host;
// Create a manager object for IPC.
Manager manager;
// Create and register on connection callbacks from the manager into the application host.
std::function<void(HANDLE)> onHandleConnection = std::bind(&AppHost::IncomingConnectionByHandle, &host, std::placeholders::_1);
std::function<void(std::wstring_view, std::wstring_view)> onLaunchConnection = std::bind(&AppHost::IncomingConnectionByLaunch, &host, std::placeholders::_1, std::placeholders::_2);
Manager::s_RegisterOnConnection(onHandleConnection);
Manager::s_RegisterOnConnection(onLaunchConnection);
// !!! LOAD BEARING !!!
// This is _magic_. Do the initial loading of our settings *BEFORE* we
// initialize our COM apartment type. This is because the Windows.Storage
@@ -143,8 +132,5 @@ int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
TranslateMessage(&message);
DispatchMessage(&message);
}
manager.NotifyExit();
return 0;
}

View File

@@ -20,7 +20,6 @@ const std::wstring_view ConsoleArguments::HEIGHT_ARG = L"--height";
const std::wstring_view ConsoleArguments::INHERIT_CURSOR_ARG = L"--inheritcursor";
const std::wstring_view ConsoleArguments::FEATURE_ARG = L"--feature";
const std::wstring_view ConsoleArguments::FEATURE_PTY_ARG = L"pty";
const std::wstring_view ConsoleArguments::FORCE_MANAGER_ARG = L"--manager";
std::wstring EscapeArgument(std::wstring_view ac)
{
@@ -103,9 +102,7 @@ ConsoleArguments::ConsoleArguments(const std::wstring& commandline,
_vtOutHandle(hStdOut),
_recievedEarlySizeChange{ false },
_originalWidth{ -1 },
_originalHeight{ -1 },
_forceManager{ false },
_workingDirectory{ wil::GetCurrentDirectoryW().get() }
_originalHeight{ -1 }
{
_clientCommandline = L"";
_vtMode = L"";
@@ -288,7 +285,7 @@ void ConsoleArguments::s_ConsumeArg(_Inout_ std::vector<std::wstring>& args, _In
// - S_OK if we could successfully parse the given text and store it in the handle value location.
// - E_INVALIDARG if we couldn't parse the text as a valid hex-encoded handle number OR
// if the handle value was already filled.
[[nodiscard]] HRESULT ConsoleArguments::s_ParseHandleArg(const std::wstring& handleAsText, _Inout_ HANDLE& handleAsVal)
[[nodiscard]] HRESULT ConsoleArguments::s_ParseHandleArg(const std::wstring& handleAsText, _Inout_ DWORD& handleAsVal)
{
HRESULT hr = S_OK;
@@ -299,15 +296,13 @@ void ConsoleArguments::s_ConsumeArg(_Inout_ std::vector<std::wstring>& args, _In
}
else if (0 == handleAsVal)
{
const auto handleAsUlong = wcstoul(handleAsText.c_str(), nullptr /*endptr*/, 16 /*base*/);
handleAsVal = wcstoul(handleAsText.c_str(), nullptr /*endptr*/, 16 /*base*/);
// If the handle didn't parse into a reasonable handle ID, invalid.
if (handleAsUlong == 0)
if (handleAsVal == 0)
{
hr = E_INVALIDARG;
}
handleAsVal = UlongToHandle(handleAsUlong);
}
else
{
@@ -472,12 +467,6 @@ void ConsoleArguments::s_ConsumeArg(_Inout_ std::vector<std::wstring>& args, _In
{
hr = s_HandleFeatureValue(args, i);
}
else if (arg == FORCE_MANAGER_ARG)
{
_forceManager = true;
s_ConsumeArg(args, i);
hr = S_OK;
}
else if (arg == HEADLESS_ARG)
{
_headless = true;
@@ -563,72 +552,62 @@ bool ConsoleArguments::InConptyMode() const noexcept
return IsValidHandle(_vtInHandle) || IsValidHandle(_vtOutHandle) || HasSignalHandle();
}
bool ConsoleArguments::IsHeadless() const noexcept
bool ConsoleArguments::IsHeadless() const
{
return _headless;
}
bool ConsoleArguments::ShouldCreateServerHandle() const noexcept
bool ConsoleArguments::ShouldCreateServerHandle() const
{
return _createServerHandle;
}
bool ConsoleArguments::ShouldSendToManager() const noexcept
HANDLE ConsoleArguments::GetServerHandle() const
{
return _forceManager;
return ULongToHandle(_serverHandle);
}
HANDLE ConsoleArguments::GetServerHandle() const noexcept
HANDLE ConsoleArguments::GetSignalHandle() const
{
return _serverHandle;
return ULongToHandle(_signalHandle);
}
void ConsoleArguments::SetServerHandle(const HANDLE server) noexcept
{
_serverHandle = server;
}
HANDLE ConsoleArguments::GetSignalHandle() const noexcept
{
return _signalHandle;
}
HANDLE ConsoleArguments::GetVtInHandle() const noexcept
HANDLE ConsoleArguments::GetVtInHandle() const
{
return _vtInHandle;
}
HANDLE ConsoleArguments::GetVtOutHandle() const noexcept
HANDLE ConsoleArguments::GetVtOutHandle() const
{
return _vtOutHandle;
}
std::wstring_view ConsoleArguments::GetClientCommandline() const noexcept
std::wstring ConsoleArguments::GetClientCommandline() const
{
return _clientCommandline;
}
std::wstring ConsoleArguments::GetVtMode() const noexcept
std::wstring ConsoleArguments::GetVtMode() const
{
return _vtMode;
}
bool ConsoleArguments::GetForceV1() const noexcept
bool ConsoleArguments::GetForceV1() const
{
return _forceV1;
}
short ConsoleArguments::GetWidth() const noexcept
short ConsoleArguments::GetWidth() const
{
return _width;
}
short ConsoleArguments::GetHeight() const noexcept
short ConsoleArguments::GetHeight() const
{
return _height;
}
bool ConsoleArguments::GetInheritCursor() const noexcept
bool ConsoleArguments::GetInheritCursor() const
{
return _inheritCursor;
}

View File

@@ -33,26 +33,23 @@ public:
bool HasVtHandles() const;
bool InConptyMode() const noexcept;
bool IsHeadless() const noexcept;
bool ShouldCreateServerHandle() const noexcept;
bool ShouldSendToManager() const noexcept;
bool IsHeadless() const;
bool ShouldCreateServerHandle() const;
HANDLE GetServerHandle() const noexcept;
void SetServerHandle(const HANDLE server) noexcept;
HANDLE GetVtInHandle() const noexcept;
HANDLE GetVtOutHandle() const noexcept;
HANDLE GetServerHandle() const;
HANDLE GetVtInHandle() const;
HANDLE GetVtOutHandle() const;
bool HasSignalHandle() const;
HANDLE GetSignalHandle() const noexcept;
HANDLE GetSignalHandle() const;
std::wstring_view GetClientCommandline() const noexcept;
std::wstring GetVtMode() const noexcept;
bool GetForceV1() const noexcept;
std::wstring GetClientCommandline() const;
std::wstring GetVtMode() const;
bool GetForceV1() const;
short GetWidth() const noexcept;
short GetHeight() const noexcept;
bool GetInheritCursor() const noexcept;
short GetWidth() const;
short GetHeight() const;
bool GetInheritCursor() const;
void SetExpectedSize(COORD dimensions) noexcept;
@@ -69,7 +66,6 @@ public:
static const std::wstring_view INHERIT_CURSOR_ARG;
static const std::wstring_view FEATURE_ARG;
static const std::wstring_view FEATURE_PTY_ARG;
static const std::wstring_view FORCE_MANAGER_ARG;
private:
#ifdef UNIT_TESTING
@@ -111,8 +107,6 @@ private:
std::wstring _clientCommandline;
std::wstring _workingDirectory;
HANDLE _vtInHandle;
HANDLE _vtOutHandle;
@@ -125,10 +119,9 @@ private:
short _width;
short _height;
bool _forceManager;
bool _createServerHandle;
HANDLE _serverHandle;
HANDLE _signalHandle;
DWORD _serverHandle;
DWORD _signalHandle;
bool _inheritCursor;
bool _recievedEarlySizeChange;
@@ -151,7 +144,7 @@ private:
_Inout_ size_t& index);
[[nodiscard]] static HRESULT s_ParseHandleArg(const std::wstring& handleAsText,
_Inout_ HANDLE& handle);
_Inout_ DWORD& handleAsVal);
#ifdef UNIT_TESTING
friend class ConsoleArgumentsTests;

View File

@@ -77,7 +77,28 @@ VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe,
{
return S_FALSE;
}
_pInputStateMachine->ProcessString(pwsSequence.get(), cchSequence);
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (gci.GetVtIo()->IsInPassthroughMode())
{
try
{
std::deque<std::unique_ptr<IInputEvent>> inputEvents;
for (size_t i = 0; i < cchSequence; i++)
{
inputEvents.push_back(std::make_unique<KeyEvent>(true, 1ui16, 0ui16, 0ui16, pwsSequence[i], 0));
}
gci.GetActiveInputBuffer()->Write(inputEvents);
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
}
}
else
{
_pInputStateMachine->ProcessString(pwsSequence.get(), cchSequence);
}
}
CATCH_RETURN();

View File

@@ -70,14 +70,14 @@ VtIo::VtIo() :
return S_OK;
}
[[nodiscard]] HRESULT VtIo::Initialize(const ConsoleArguments args)
[[nodiscard]] HRESULT VtIo::Initialize(const ConsoleArguments* const pArgs)
{
_lookingForCursorPosition = args.GetInheritCursor();
_lookingForCursorPosition = pArgs->GetInheritCursor();
// If we were already given VT handles, set up the VT IO engine to use those.
if (args.InConptyMode())
if (pArgs->InConptyMode())
{
return _Initialize(args.GetVtInHandle(), args.GetVtOutHandle(), args.GetVtMode(), args.GetSignalHandle());
return _Initialize(pArgs->GetVtInHandle(), pArgs->GetVtOutHandle(), pArgs->GetVtMode(), pArgs->GetSignalHandle());
}
// Didn't need to initialize if we didn't have VT stuff. It's still OK, but report we did nothing.
else
@@ -397,3 +397,18 @@ void VtIo::_ShutdownIfNeeded()
ServiceLocator::RundownAndExit(ERROR_BROKEN_PIPE);
}
}
void VtIo::SetPassthroughMode(const bool enable)
{
_pVtRenderEngine->SetPassthroughMode(enable);
}
bool VtIo::IsInPassthroughMode()
{
return _pVtRenderEngine->IsInPassthroughMode();
}
void VtIo::PassthroughString(std::wstring_view view)
{
std::wstring wstr{ view };
// LOG_IF_FAILED(_pVtRenderEngine->WriteTerminalW(wstr));
_pVtRenderEngine->PassthroughString(wstr);
}

View File

@@ -19,7 +19,7 @@ namespace Microsoft::Console::VirtualTerminal
VtIo();
virtual ~VtIo() override = default;
[[nodiscard]] HRESULT Initialize(const ConsoleArguments args);
[[nodiscard]] HRESULT Initialize(const ConsoleArguments* const pArgs);
[[nodiscard]] HRESULT CreateAndStartSignalThread() noexcept;
[[nodiscard]] HRESULT CreateIoHandlers() noexcept;
@@ -36,6 +36,10 @@ namespace Microsoft::Console::VirtualTerminal
void CloseInput() override;
void CloseOutput() override;
void SetPassthroughMode(const bool enable);
void PassthroughString(std::wstring_view view);
bool IsInPassthroughMode();
private:
// After CreateIoHandlers is called, these will be invalid.
wil::unique_hfile _hInput;

View File

@@ -941,6 +941,17 @@ using Microsoft::Console::VirtualTerminal::StateMachine;
const DWORD dwFlags,
_Inout_opt_ PSHORT const psScrollY)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
if (gci.IsInVtIoMode() && gci.GetVtIo()->IsInPassthroughMode())
// if (WI_IsFlagSet(screenInfo.OutputMode, ENABLE_PASSTHROUGH_MODE))
{
const size_t BufferSize = *pcb;
const size_t cch = BufferSize / sizeof(WCHAR);
gci.GetVtIo()->PassthroughString({ pwchRealUnicode, cch });
// *pcb += BufferSize;
// return STATUS_SUCCESS;
}
if (!WI_IsFlagSet(screenInfo.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING) ||
!WI_IsFlagSet(screenInfo.OutputMode, ENABLE_PROCESSED_OUTPUT))
{

View File

@@ -7,7 +7,6 @@
#include "srvinit.h"
#include "..\server\Entrypoints.h"
#include "..\interactivity\inc\ServiceLocator.hpp"
#include "..\types\Manager.h"
// Define TraceLogging provider
TRACELOGGING_DEFINE_PROVIDER(
@@ -196,7 +195,7 @@ int CALLBACK wWinMain(
{
if (args.ShouldCreateServerHandle())
{
hr = Entrypoints::StartConsoleForCmdLine(args);
hr = Entrypoints::StartConsoleForCmdLine(args.GetClientCommandline().c_str(), &args);
}
else
{
@@ -204,7 +203,7 @@ int CALLBACK wWinMain(
if (SUCCEEDED(hr))
{
hr = Entrypoints::StartConsoleForServerHandle(args);
hr = Entrypoints::StartConsoleForServerHandle(args.GetServerHandle(), &args);
}
}
}

View File

@@ -26,7 +26,7 @@
#define VALID_TEXT_ATTRIBUTES (FG_ATTRS | BG_ATTRS | META_ATTRS)
#define INPUT_MODES (ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT | ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT)
#define OUTPUT_MODES (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN | ENABLE_LVB_GRID_WORLDWIDE)
#define OUTPUT_MODES (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN | ENABLE_LVB_GRID_WORLDWIDE | ENABLE_PASSTHROUGH_MODE)
#define PRIVATE_MODES (ENABLE_INSERT_MODE | ENABLE_QUICK_EDIT_MODE | ENABLE_AUTO_POSITION | ENABLE_EXTENDED_FLAGS)
using namespace Microsoft::Console::Types;
@@ -394,7 +394,14 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept
SCREEN_INFORMATION& screenInfo = context.GetActiveBuffer();
const DWORD dwOldMode = screenInfo.OutputMode;
const DWORD dwNewMode = mode;
DWORD preprocessNewMode = mode;
if (!gci.IsInVtIoMode() &&
(WI_IsFlagSet(preprocessNewMode, ENABLE_PASSTHROUGH_MODE)))
{
WI_ClearFlag(preprocessNewMode, ENABLE_PASSTHROUGH_MODE);
}
const DWORD dwNewMode = preprocessNewMode;
screenInfo.OutputMode = dwNewMode;
@@ -413,6 +420,31 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept
screenInfo.SetDefaultVtTabStops();
}
if (gci.IsInVtIoMode())
{
// if we're moving from passthrough on->off
if (WI_IsFlagClear(dwNewMode, ENABLE_PASSTHROUGH_MODE) &&
WI_IsFlagSet(dwOldMode, ENABLE_PASSTHROUGH_MODE))
{
gci.GetVtIo()->SetPassthroughMode(false);
// Trigger a redaw all, to sync us back up.
ServiceLocator::LocateGlobals().pRender->TriggerRedrawAll();
}
// if we're moving from passthrough off->on
else if (WI_IsFlagSet(dwNewMode, ENABLE_PASSTHROUGH_MODE) &&
WI_IsFlagClear(dwOldMode, ENABLE_PASSTHROUGH_MODE))
{
// DebugBreak();
// Trigger a frame NOW, to flush any state since the last frame
RECT dummy{ 0 };
// ServiceLocator::LocateGlobals().pRender->TriggerSystemRedraw(&dummy);
// ServiceLocator::LocateGlobals().pRender->WaitForPaintCompletion(1000);
LOG_IF_FAILED(ServiceLocator::LocateGlobals().pRender->PaintFrame());
gci.GetVtIo()->SetPassthroughMode(true);
ServiceLocator::LocateGlobals().pRender->WaitForPaintCompletion(1000);
}
}
gci.SetVirtTermLevel(WI_IsFlagSet(dwNewMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING) ? 1 : 0);
gci.SetAutomaticReturnOnNewline(WI_IsFlagSet(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN) ? false : true);
gci.SetGridRenderingAllowedWorldwide(WI_IsFlagSet(screenInfo.OutputMode, ENABLE_LVB_GRID_WORLDWIDE));

View File

@@ -71,6 +71,9 @@ Revision History:
#define CONSOLE_SUSPENDED (CONSOLE_OUTPUT_SUSPENDED)
// clang-format on
// ENABLE_LVB_GRID_WORLDWIDE is 0x0010
#define ENABLE_PASSTHROUGH_MODE 0x0020
class COOKED_READ_DATA;
class CommandHistory;

View File

@@ -11,7 +11,6 @@
#include "renderFontDefaults.hpp"
#include "ApiRoutines.h"
#include "ConsoleArguments.hpp"
#include "../types/inc/GlyphWidth.hpp"
@@ -32,15 +31,15 @@ using namespace Microsoft::Console::Render;
const UINT CONSOLE_EVENT_FAILURE_ID = 21790;
const UINT CONSOLE_LPC_PORT_FAILURE_ID = 21791;
[[nodiscard]] HRESULT ConsoleServerInitialization(const ConsoleArguments& args)
[[nodiscard]] HRESULT ConsoleServerInitialization(_In_ HANDLE Server, const ConsoleArguments* const args)
{
Globals& Globals = ServiceLocator::LocateGlobals();
try
{
Globals.pDeviceComm = new DeviceComm(args.GetServerHandle());
Globals.pDeviceComm = new DeviceComm(Server);
Globals.launchArgs = args;
Globals.launchArgs = *args;
Globals.uiOEMCP = GetOEMCP();
Globals.uiWindowsCP = GetACP();
@@ -246,10 +245,10 @@ void ConsoleCheckDebug()
#endif
}
[[nodiscard]] HRESULT ConsoleCreateIoThreadLegacy(const ConsoleArguments& args)
[[nodiscard]] HRESULT ConsoleCreateIoThreadLegacy(_In_ HANDLE Server, const ConsoleArguments* const args)
{
auto& g = ServiceLocator::LocateGlobals();
RETURN_IF_FAILED(ConsoleServerInitialization(args));
RETURN_IF_FAILED(ConsoleServerInitialization(Server, args));
RETURN_IF_FAILED(g.hConsoleInputInitEvent.create(wil::EventOptions::None));
// Set up and tell the driver about the input available event.

View File

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

View File

@@ -201,7 +201,6 @@ bool SignalResizeWindow(const HANDLE hSignal,
// - startingDirectory: The directory to start the process in
// - w: The initial width of the pty, in characters
// - h: The initial height of the pty, in characters
// - hServer: An optional handle to a server connection handle already established for this PTY.
// - hInput: A handle to the pipe for writing input to the pty.
// - hOutput: A handle to the pipe for reading the output of the pty.
// - hSignal: A handle to the pipe for writing signal messages to the pty.
@@ -217,7 +216,6 @@ bool SignalResizeWindow(const HANDLE hSignal,
std::optional<std::wstring> startingDirectory,
const unsigned short w,
const unsigned short h,
std::optional<HANDLE> const hServer,
HANDLE* const hInput,
HANDLE* const hOutput,
HANDLE* const hSignal,
@@ -256,11 +254,6 @@ bool SignalResizeWindow(const HANDLE hSignal,
SetHandleInformation(outPipeConhostSide, HANDLE_FLAG_INHERIT, 1);
SetHandleInformation(signalPipeConhostSide, HANDLE_FLAG_INHERIT, 1);
if (hServer.has_value())
{
SetHandleInformation(hServer.value(), HANDLE_FLAG_INHERIT, 1);
}
std::wstring conhostCmdline = L"conhost.exe";
conhostCmdline += L" --headless";
std::wstringstream ss;
@@ -271,19 +264,9 @@ bool SignalResizeWindow(const HANDLE hSignal,
}
ss << L" --signal 0x" << std::hex << HandleToUlong(signalPipeConhostSide);
if (hServer.has_value())
{
ss << L" --server 0x" << std::hex << HandleToUlong(hServer.value());
}
conhostCmdline += ss.str();
if (!hServer.has_value())
{
conhostCmdline += L" -- ";
conhostCmdline += cmdline;
}
conhostCmdline += L" -- ";
conhostCmdline += cmdline;
STARTUPINFO si = { 0 };
si.cb = sizeof(STARTUPINFOW);

View File

@@ -27,6 +27,7 @@ Author(s):
// UNICODE_NULL is a Windows macro definition
const wchar_t UNICODE_BACKSPACE = 0x8;
const wchar_t UNICODE_ESC = 0x1b;
const wchar_t UNICODE_DEL = 0x7f;
// NOTE: This isn't actually a backspace. It's a graphical block. But
// I believe it's emitted by one of our ANSI/OEM --> Unicode conversions.

View File

@@ -507,6 +507,11 @@ void Renderer::WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs)
_pThread->WaitForPaintCompletionAndDisable(dwTimeoutMs);
}
void Renderer::WaitForPaintCompletion(const DWORD dwTimeoutMs)
{
_pThread->WaitForPaintCompletion(dwTimeoutMs);
}
// Routine Description:
// - Paint helper to fill in the background color of the invalid area within the frame.
// Arguments:

View File

@@ -73,6 +73,7 @@ namespace Microsoft::Console::Render
void EnablePainting() override;
void WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs) override;
void WaitForPaintCompletion(const DWORD dwTimeoutMs) override;
void AddRenderEngine(_In_ IRenderEngine* const pEngine) override;

View File

@@ -242,3 +242,8 @@ void RenderThread::WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs)
ResetEvent(_hPaintEnabledEvent);
WaitForSingleObject(_hPaintCompletedEvent, dwTimeoutMs);
}
void RenderThread::WaitForPaintCompletion(const DWORD dwTimeoutMs)
{
WaitForSingleObject(_hPaintCompletedEvent, dwTimeoutMs);
}

View File

@@ -31,6 +31,7 @@ namespace Microsoft::Console::Render
void EnablePainting() override;
void WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs) override;
void WaitForPaintCompletion(const DWORD dwTimeoutMs) override;
private:
static DWORD WINAPI s_ThreadProc(_In_ LPVOID lpParameter);

View File

@@ -22,6 +22,7 @@ namespace Microsoft::Console::Render
virtual void NotifyPaint() = 0;
virtual void EnablePainting() = 0;
virtual void WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs) = 0;
virtual void WaitForPaintCompletion(const DWORD dwTimeoutMs) = 0;
};
inline Microsoft::Console::Render::IRenderThread::~IRenderThread(){};

View File

@@ -54,6 +54,7 @@ namespace Microsoft::Console::Render
virtual void EnablePainting() = 0;
virtual void WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs) = 0;
virtual void WaitForPaintCompletion(const DWORD dwTimeoutMs) = 0;
virtual void AddRenderEngine(_In_ IRenderEngine* const pEngine) = 0;
};

View File

@@ -40,7 +40,11 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
// the pipe.
[[nodiscard]] HRESULT XtermEngine::StartPaint() noexcept
{
RETURN_IF_FAILED(VtEngine::StartPaint());
const auto hr = VtEngine::StartPaint();
if (hr == S_FALSE)
{
return hr;
}
_trace.TraceLastText(_lastText);

View File

@@ -24,6 +24,10 @@ using namespace Microsoft::Console::Types;
{
return S_FALSE;
}
if (_passthroughMode)
{
return S_FALSE;
}
// If there's nothing to do, quick return
bool somethingToDo = _fInvalidRectUsed ||

View File

@@ -417,3 +417,19 @@ HRESULT VtEngine::RequestCursor() noexcept
RETURN_IF_FAILED(_Flush());
return S_OK;
}
void VtEngine::SetPassthroughMode(const bool enable)
{
_passthroughMode = enable;
}
void VtEngine::PassthroughString(const std::wstring& wstr)
{
LOG_IF_FAILED(WriteTerminalW(wstr));
LOG_IF_FAILED(_Flush());
}
bool VtEngine::IsInPassthroughMode()
{
return _passthroughMode;
}

View File

@@ -95,6 +95,10 @@ namespace Microsoft::Console::Render
void SetTerminalOwner(Microsoft::Console::ITerminalOwner* const terminalOwner);
void SetPassthroughMode(const bool enable);
void PassthroughString(const std::wstring& wstr);
bool IsInPassthroughMode();
protected:
wil::unique_hfile _hFile;
std::string _buffer;
@@ -133,6 +137,8 @@ namespace Microsoft::Console::Render
Microsoft::Console::VirtualTerminal::RenderTracing _trace;
bool _passthroughMode{ false };
[[nodiscard]] HRESULT _Write(std::string_view const str) noexcept;
[[nodiscard]] HRESULT _WriteFormattedString(const std::string* const pFormat, ...) noexcept;
[[nodiscard]] HRESULT _Flush() noexcept;

View File

@@ -9,21 +9,10 @@
#include "winbasep.h"
#include "..\types\Manager.h"
#include "..\host\ConsoleArguments.hpp"
[[nodiscard]] HRESULT Entrypoints::StartConsoleForServerHandle(const ConsoleArguments& args)
[[nodiscard]] HRESULT Entrypoints::StartConsoleForServerHandle(const HANDLE ServerHandle,
const ConsoleArguments* const args)
{
if (args.ShouldSendToManager())
{
if (Manager::s_TrySendToManager(args.GetServerHandle()))
{
SleepEx(INFINITE, FALSE);
return S_OK;
}
}
return ConsoleCreateIoThreadLegacy(args);
return ConsoleCreateIoThreadLegacy(ServerHandle, args);
}
// this function has unreachable code due to its unusual lifetime. We
@@ -31,17 +20,9 @@
#pragma warning(push)
#pragma warning(disable : 4702)
[[nodiscard]] HRESULT Entrypoints::StartConsoleForCmdLine(ConsoleArguments& args)
[[nodiscard]] HRESULT Entrypoints::StartConsoleForCmdLine(_In_ PCWSTR pwszCmdLine,
const ConsoleArguments* const args)
{
if (args.ShouldSendToManager())
{
if (Manager::s_TrySendToManager(args.GetClientCommandline(), L"C:\\windows\\system32"))
{
SleepEx(INFINITE, FALSE);
return S_OK;
}
}
// Create a scope because we're going to exit thread if everything goes well.
// This scope will ensure all C++ objects and smart pointers get a chance to destruct before ExitThread is called.
{
@@ -58,8 +39,7 @@
L"\\Reference",
FALSE));
args.SetServerHandle(ServerHandle.get());
RETURN_IF_NTSTATUS_FAILED(Entrypoints::StartConsoleForServerHandle(args));
RETURN_IF_NTSTATUS_FAILED(Entrypoints::StartConsoleForServerHandle(ServerHandle.get(), args));
// If we get to here, we have transferred ownership of the server handle to the console, so release it.
// Keep a copy of the value so we can open the client handles even though we're no longer the owner.
@@ -163,8 +143,6 @@
NULL));
// We have to copy the command line string we're given because CreateProcessW has to be called with mutable data.
PCWSTR pwszCmdLine = args.GetClientCommandline().data();
if (wcslen(pwszCmdLine) == 0)
{
// If they didn't give us one, just launch cmd.exe.

View File

@@ -20,6 +20,6 @@ class ConsoleArguments;
namespace Entrypoints
{
[[nodiscard]] HRESULT StartConsoleForServerHandle(const ConsoleArguments& args);
[[nodiscard]] HRESULT StartConsoleForCmdLine(ConsoleArguments& args);
[[nodiscard]] HRESULT StartConsoleForServerHandle(const HANDLE ServerHandle, const ConsoleArguments* const args);
[[nodiscard]] HRESULT StartConsoleForCmdLine(_In_ PCWSTR pwszCmdLine, const ConsoleArguments* const args);
};

View File

@@ -5,4 +5,4 @@
class ConsoleArguments;
[[nodiscard]] HRESULT ConsoleCreateIoThreadLegacy(const ConsoleArguments& args);
[[nodiscard]] HRESULT ConsoleCreateIoThreadLegacy(_In_ HANDLE Server, const ConsoleArguments* const args);

View File

@@ -2,9 +2,178 @@
// Licensed under the MIT license.
#include <windows.h>
#include <wil\Common.h>
#include <wil\result.h>
#include <wil\resource.h>
#include <wil\wistd_functional.h>
#include <wil\wistd_memory.h>
#include <stdlib.h> /* srand, rand */
#include <time.h> /* time */
#include <conio.h>
#include <deque>
#include <memory>
#include <vector>
#include <string>
#include <sstream>
#include <assert.h>
#define ENABLE_PASSTHROUGH_MODE 0x0020
std::string csi(std::string seq)
{
std::string fullSeq = "\x1b[";
fullSeq += seq;
return fullSeq;
}
std::string osc(std::string seq)
{
std::string fullSeq = "\x1b]";
fullSeq += seq;
fullSeq += "\x7";
return fullSeq;
}
void testOutput()
{
wprintf(L"Attempting to start passthrough mode...\n");
auto hOut = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD dwMode = 0;
THROW_LAST_ERROR_IF(!GetConsoleMode(hOut, &dwMode));
wprintf(L"Original Mode: 0x%x\n", dwMode);
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
dwMode |= DISABLE_NEWLINE_AUTO_RETURN;
dwMode |= ENABLE_PASSTHROUGH_MODE;
wprintf(L"Requested Mode: 0x%x\n", dwMode);
THROW_LAST_ERROR_IF(!SetConsoleMode(hOut, dwMode));
DWORD roundtripMode = 0;
THROW_LAST_ERROR_IF(!GetConsoleMode(hOut, &roundtripMode));
wprintf(L"Rountripped Mode: 0x%x\n", dwMode);
if (roundtripMode != dwMode)
{
wprintf(L"Mode did not rountrip\n");
}
else
{
wprintf(L"Mode rountripped successfully\n");
}
wprintf(L"Press a key to continue\n");
_getch();
wprintf(L"We're going to write some VT straight to the terminal\n");
printf(csi("31m").c_str());
printf(osc("0;Title:foo").c_str());
wprintf(L"Press a key to continue\n");
_getch();
printf(csi("0m").c_str());
wprintf(L"Time for something more complicated...\n");
Sleep(500);
printf(csi("2;1H").c_str());
printf(csi("44m").c_str());
printf(csi("K").c_str());
Sleep(500);
printf(csi("9;1H").c_str());
printf(csi("46m").c_str());
printf(csi("K").c_str());
Sleep(500);
printf(csi("3;8r").c_str());
printf(csi("3;1H").c_str());
printf(csi("0m").c_str());
Sleep(500);
for (int i = 0; i < 10; i++)
{
wprintf(L"Print in the margins %d\n", i);
Sleep(500);
}
printf(csi("r").c_str());
wprintf(L"Press a key to continue\n");
_getch();
}
void launchChild(int argc, WCHAR* argv[])
{
auto hOut = GetStdHandle(STD_OUTPUT_HANDLE);
auto hIn = GetStdHandle(STD_INPUT_HANDLE);
DWORD dwMode = 0;
THROW_LAST_ERROR_IF(!GetConsoleMode(hOut, &dwMode));
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
dwMode |= DISABLE_NEWLINE_AUTO_RETURN;
dwMode |= ENABLE_PASSTHROUGH_MODE;
THROW_LAST_ERROR_IF(!SetConsoleMode(hOut, dwMode));
DWORD dwInMode = 0;
GetConsoleMode(hIn, &dwInMode);
dwInMode = ENABLE_VIRTUAL_TERMINAL_INPUT;
SetConsoleMode(hIn, dwInMode);
std::wstring commandline = L"";
for (int i = 0; i < argc; i++)
{
commandline += (argv[i]);
commandline += (L" ");
}
std::unique_ptr<wchar_t[]> mutableCommandline = std::make_unique<wchar_t[]>(commandline.length() + 1);
THROW_IF_NULL_ALLOC(mutableCommandline);
HRESULT hr = StringCchCopy(mutableCommandline.get(), commandline.length() + 1, commandline.c_str());
THROW_IF_FAILED(hr);
STARTUPINFO si = { 0 };
si.cb = sizeof(STARTUPINFOW);
PROCESS_INFORMATION _piClient;
bool fSuccess = !!CreateProcessW(
nullptr,
mutableCommandline.get(),
nullptr, // lpProcessAttributes
nullptr, // lpThreadAttributes
true, // bInheritHandles
false, // dwCreationFlags
nullptr, // lpEnvironment
nullptr, // lpCurrentDirectory
&si, // lpStartupInfo
&_piClient // lpProcessInformation
);
THROW_LAST_ERROR_IF(!fSuccess);
// Sleep(10000);
WaitForSingleObject(_piClient.hProcess, INFINITE);
}
// This wmain exists for help in writing scratch programs while debugging.
int __cdecl wmain(int /*argc*/, WCHAR* /*argv[]*/)
int __cdecl wmain(int argc, WCHAR* argv[])
{
for (int i = 0; i < argc; i++)
{
wprintf(argv[i]);
wprintf(L" ");
}
if (argc > 1)
{
std::wstring arg1 = argv[1];
if (arg1 == L"--test")
{
testOutput();
}
else if (arg1 == L"--")
{
launchChild(argc - 2, &argv[2]);
}
}
return 0;
}

View File

@@ -1,357 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "Manager.h"
static constexpr std::wstring_view MUTEX_NAME = L"Local\\WindowsTerminalManager";
static constexpr std::wstring_view PIPE_NAME = L"\\\\.\\pipe\\WindowsTerminalManagerPipe";
static constexpr DWORD PIPE_BUFFER_SIZE = 4096;
Manager::Manager() :
_mutex(),
_pipe(),
_exit(),
_theServer(false)
{
// Create exit event.
_exit.create();
// Try to open the mutex.
if (!_mutex.try_open(MUTEX_NAME.data()))
{
// If we can't open it, create one because no one else did.
_mutex.create(MUTEX_NAME.data());
_theServer = true;
}
else
{
// If it could just be opened, we're not the server.
_theServer = false;
}
// If we're the server, establish communication listener and thread
if (_theServer)
{
_becomeServer();
}
// If we're not the server, find out who it is so we can get notified when they leave and become the server.
else
{
ManagerMessageQuery query;
query.size = sizeof(query);
query.type = ManagerMessageTypes::GetManagerPid;
const auto reply = _ask(query);
_waitToBecomeServer = std::async(std::launch::async, [reply, this] {
wil::unique_handle managerProcess(OpenProcess(SYNCHRONIZE, FALSE, reply.reply.getPid.id));
THROW_LAST_ERROR_IF_NULL(managerProcess.get());
WaitForSingleObject(managerProcess.get(), INFINITE);
_becomeServer();
});
}
}
Manager::~Manager()
{
}
void Manager::NotifyExit()
{
_exit.SetEvent();
}
bool Manager::s_TrySendToManager(const HANDLE server)
{
try
{
// Get PID from remote process.
ManagerMessageQuery query;
query.size = sizeof(query);
query.type = ManagerMessageTypes::GetManagerPid;
auto reply = _ask(query);
const auto processId = reply.reply.getPid.id;
// Open for handle duping.
wil::unique_handle otherProcess(OpenProcess(PROCESS_DUP_HANDLE, FALSE, processId));
THROW_LAST_ERROR_IF_NULL(otherProcess.get());
// Send handle into that process.
HANDLE targetHandle;
THROW_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(), server,
otherProcess.get(), &targetHandle,
0,
FALSE,
DUPLICATE_SAME_ACCESS));
// Tell remote process about new handle ID
query.size = sizeof(query);
query.type = ManagerMessageTypes::SendConnection;
query.query.sendConn.handle = targetHandle;
reply = _ask(query);
return reply.reply.sendConn.ok;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return false;
}
}
bool Manager::s_TrySendToManager(const std::wstring_view cmdline,
const std::wstring_view workingDir)
{
try
{
// Figure out how much data we actually need to send with the variable length strings
size_t size = sizeof(ManagerMessageQuery) + 1;
size += 2*(cmdline.size() + 1);
size += 2*(workingDir.size() + 1);
// Make a big buffer to hold it all contiguously
const auto buffer = std::make_unique<byte[]>(size);
// Make pointers to fill up each piece of data
const auto query = reinterpret_cast<ManagerMessageQuery*>(buffer.get());
const auto cmdPayload = reinterpret_cast<PWCHAR>(buffer.get() + sizeof(ManagerMessageQuery) + 1);
const auto workingPayload = reinterpret_cast<PWCHAR>(buffer.get() + sizeof(ManagerMessageQuery) + 1 + (cmdline.size() + 1) * 2);
// Tell the remote process the command line and working directory
query->size = gsl::narrow<DWORD>(size);
query->type = ManagerMessageTypes::SendCmdAndWorking;
query->query.sendCmdAndWorking.cmd = gsl::narrow<DWORD>(cmdline.size());
query->query.sendCmdAndWorking.working = gsl::narrow<DWORD>(workingDir.size());
THROW_IF_FAILED(StringCchCopyW(cmdPayload, cmdline.size() + 1, cmdline.data()));
THROW_IF_FAILED(StringCchCopyW(workingPayload, workingDir.size() + 1, workingDir.data()));
const auto reply = _ask(*query);
return reply.reply.sendConn.ok;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return false;
}
}
void Manager::_becomeServer()
{
// lock?
// test if pipe exists?
_theServer = true;
// enter loop to make server connections
_serverWork = std::async(std::launch::async, [this] {
_serverLoop();
});
}
void Manager::_serverLoop()
{
wil::unique_event newClient;
newClient.create(wil::EventOptions::ManualReset);
bool keepGoing = true;
while (keepGoing)
{
OVERLAPPED overlap = { 0 };
overlap.hEvent = newClient.get();
wil::unique_handle pipe(CreateNamedPipeW(PIPE_NAME.data(),
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT | PIPE_ACCEPT_REMOTE_CLIENTS,
PIPE_UNLIMITED_INSTANCES,
PIPE_BUFFER_SIZE,
PIPE_BUFFER_SIZE,
0,
nullptr)); // replace with actual security descriptor?
THROW_LAST_ERROR_IF(pipe.get() == INVALID_HANDLE_VALUE);
if (!ConnectNamedPipe(pipe.get(), &overlap))
{
// IO pending and Pipe Connected are OK error codes. Go into the wait.
const auto gle = GetLastError();
if (gle != ERROR_IO_PENDING && gle != ERROR_PIPE_CONNECTED)
{
THROW_LAST_ERROR();
}
}
std::array<HANDLE, 2> waitOn;
waitOn.at(0) = _exit.get();
waitOn.at(1) = newClient.get();
const auto ret = WaitForMultipleObjects(gsl::narrow<DWORD>(waitOn.size()), waitOn.data(), FALSE, INFINITE);
if (ret == WAIT_OBJECT_0)
{
keepGoing = false;
continue;
}
else if (ret == WAIT_OBJECT_0 + 1)
{
const auto loosePipeHandle = pipe.release();
auto future = std::async(std::launch::async, [this, loosePipeHandle] {
_perClientLoop(wil::unique_handle(loosePipeHandle));
});
_perClientWork.push_back(std::move(future));
}
else if (ret == WAIT_FAILED)
{
THROW_LAST_ERROR();
}
else
{
THROW_WIN32(ret);
}
}
}
void Manager::_perClientLoop(wil::unique_handle pipe)
{
/*bool keepGoing = true;
while (keepGoing)*/
{
ManagerMessageQuery query;
DWORD bytesRead = 0;
SetLastError(S_OK);
const auto result = ReadFile(pipe.get(),
&query,
sizeof(query),
&bytesRead,
nullptr);
// False is OK if it's ERROR_MORE_DATA.
const auto gle = GetLastError();
THROW_LAST_ERROR_IF(!result && gle != ERROR_MORE_DATA);
std::unique_ptr<byte[]> buffer;
if (gle == ERROR_MORE_DATA)
{
const auto remainingBytes = query.size - gsl::narrow<DWORD>(sizeof(query));
buffer = std::make_unique<byte[]>(remainingBytes);
bytesRead = 0;
THROW_IF_WIN32_BOOL_FALSE(ReadFile(pipe.get(),
buffer.get(),
remainingBytes,
&bytesRead,
nullptr));
}
ManagerMessageReply reply;
switch (query.type)
{
case ManagerMessageTypes::GetManagerPid:
reply = _getPid(query);
break;
case ManagerMessageTypes::SendConnection:
reply = _sendConnection(query);
break;
case ManagerMessageTypes::SendCmdAndWorking:
reply = _sendCmdAndWorking(query, buffer);
break;
default:
THROW_HR(E_NOTIMPL);
}
DWORD bytesWritten = 0;
THROW_IF_WIN32_BOOL_FALSE(WriteFile(pipe.get(),
&reply,
sizeof(reply),
&bytesWritten,
nullptr));
}
}
Manager::ManagerMessageReply Manager::_ask(Manager::ManagerMessageQuery& query)
{
ManagerMessageReply reply;
DWORD bytesRead = 0;
THROW_IF_WIN32_BOOL_FALSE(CallNamedPipeW(PIPE_NAME.data(),
&query,
query.size,
&reply,
sizeof(reply),
&bytesRead,
NMPWAIT_WAIT_FOREVER));
return reply;
}
Manager::ManagerMessageReply Manager::_getPid(ManagerMessageQuery /*query*/)
{
ManagerMessageReply reply;
reply.type = ManagerMessageTypes::GetManagerPid;
reply.reply.getPid.id = GetCurrentProcessId();
return reply;
}
static std::vector<std::function<void(HANDLE)>> s_onHandleConnection;
void Manager::s_RegisterOnConnection(std::function<void(HANDLE)> func)
{
s_onHandleConnection.push_back(func);
}
Manager::ManagerMessageReply Manager::_sendConnection(ManagerMessageQuery query)
{
const auto serverHandle = query.query.sendConn.handle;
// create new conhost connection...
for (const auto& func : s_onHandleConnection)
{
func(serverHandle);
}
ManagerMessageReply reply;
reply.type = ManagerMessageTypes::SendConnection;
reply.reply.sendConn.ok = true;
return reply;
}
static std::vector<std::function<void(std::wstring_view, std::wstring_view)>> s_onLaunchConnection;
void Manager::s_RegisterOnConnection(std::function<void(std::wstring_view, std::wstring_view)> func)
{
s_onLaunchConnection.push_back(func);
}
Manager::ManagerMessageReply Manager::_sendCmdAndWorking(ManagerMessageQuery query, std::unique_ptr<BYTE[]>& buffer)
{
const auto cmd = query.query.sendCmdAndWorking.cmd;
const auto work = query.query.sendCmdAndWorking.working;
std::wstring_view cmdline(reinterpret_cast<wchar_t*>(buffer.get() + 1), cmd);
std::wstring_view workingDir(reinterpret_cast<wchar_t*>(buffer.get() + 1 + (cmd + 1) * 2), work);
// create new conhost connection...
for (const auto& func : s_onLaunchConnection)
{
func(cmdline, workingDir);
}
ManagerMessageReply reply;
reply.type = ManagerMessageTypes::SendCmdAndWorking;
reply.reply.sendCmdAndWorking.ok = true;
return reply;
}

View File

@@ -1,84 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
class Manager final
{
public:
Manager();
virtual ~Manager();
void NotifyExit();
static void s_RegisterOnConnection(std::function<void(HANDLE)> func);
static void s_RegisterOnConnection(std::function<void(std::wstring_view, std::wstring_view)> func);
static bool s_TrySendToManager(const HANDLE server);
static bool s_TrySendToManager(const std::wstring_view cmdline,
const std::wstring_view workingDir);
Manager(const Manager&) = delete;
Manager(Manager&&) = delete;
Manager& operator=(const Manager&) = delete;
Manager& operator=(Manager&&) = delete;
private:
wil::unique_mutex _mutex;
wil::unique_event _exit;
wil::unique_handle _pipe;
bool _theServer;
std::future<void> _waitToBecomeServer;
std::future<void> _serverWork;
std::vector<std::future<void>> _perClientWork;
enum class ManagerMessageTypes
{
GetManagerPid,
SendConnection,
SendCmdAndWorking
};
struct ManagerMessageQuery
{
DWORD size;
ManagerMessageTypes type;
union
{
struct SendConnection
{
HANDLE handle;
} sendConn;
struct SendCmdAndWorking
{
DWORD cmd;
DWORD working;
} sendCmdAndWorking;
} query;
};
struct ManagerMessageReply
{
ManagerMessageTypes type;
union
{
struct GetManagerPid
{
DWORD id;
} getPid;
struct SendConnection
{
bool ok;
} sendConn;
struct SendCmdAndWorking
{
bool ok;
} sendCmdAndWorking;
} reply;
};
static ManagerMessageReply _ask(ManagerMessageQuery& query);
static ManagerMessageReply _getPid(ManagerMessageQuery query);
static ManagerMessageReply _sendConnection(ManagerMessageQuery query);
static ManagerMessageReply _sendCmdAndWorking(ManagerMessageQuery query, std::unique_ptr<byte[]>& buffer);
void _becomeServer();
void _serverLoop();
void _perClientLoop(wil::unique_handle pipe);
};

View File

@@ -0,0 +1,98 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "inc/Utf8OutPipeReader.hpp"
#include <type_traits>
#include <utility>
UTF8OutPipeReader::UTF8OutPipeReader(HANDLE outPipe) :
_outPipe{ outPipe }
{
}
// Method Description:
// Populates a string_view with *complete* UTF-8 codepoints read from the pipe.
// If it receives an incomplete codepoint, it will cache it until it can be completed.
// Note: This method trusts that the other end will, in fact, send complete codepoints.
// Arguments:
// - strView: on return, populated with successfully-read codepoints.
// Return Value:
// An HRESULT indicating whether the read was successful. For the purposes of this
// method, a closed pipe is considered a successful (but false!) read. All other errors
// are translated into an appropriate status code.
// S_OK for a successful read
// S_FALSE for a read on a closed pipe
// E_* (anything) for a failed read
[[nodiscard]] HRESULT UTF8OutPipeReader::Read(_Out_ std::string_view& strView)
{
DWORD dwRead{};
bool fSuccess{};
// in case of early escaping
*_buffer = 0;
strView = std::string_view{ reinterpret_cast<char*>(_buffer), 0 };
// copy UTF-8 code units that were remaining from the previously read chunk (if any)
if (_dwPartialsLen != 0)
{
std::move(_utf8Partials, _utf8Partials + _dwPartialsLen, _buffer);
}
// try to read data
fSuccess = !!ReadFile(_outPipe, &_buffer[_dwPartialsLen], std::extent<decltype(_buffer)>::value - _dwPartialsLen, &dwRead, nullptr);
dwRead += _dwPartialsLen;
_dwPartialsLen = 0;
if (!fSuccess) // reading failed (we must check this first, because dwRead will also be 0.)
{
auto lastError = GetLastError();
if (lastError == ERROR_BROKEN_PIPE)
{
// This is a successful, but detectable, exit.
// There is a chance that we put some partials into the buffer. Since
// the pipe has closed, they're just invalid now. They're not worth
// reporting.
return S_FALSE;
}
return HRESULT_FROM_WIN32(lastError);
}
if (dwRead == 0) // quit if no data has been read and no cached data was left over
{
return S_OK;
}
const BYTE* const endPtr{ _buffer + dwRead };
const BYTE* backIter{ endPtr - 1 };
// If the last byte in the buffer was a byte belonging to a UTF-8 multi-byte character
if ((*backIter & _Utf8BitMasks::MaskAsciiByte) > _Utf8BitMasks::IsAsciiByte)
{
// Check only up to 3 last bytes, if no Lead Byte was found then the byte before must be the Lead Byte and no partials are in the buffer
for (DWORD dwSequenceLen{ 1UL }, stop{ dwRead < 4UL ? dwRead : 4UL }; dwSequenceLen < stop; ++dwSequenceLen, --backIter)
{
// If Lead Byte found
if ((*backIter & _Utf8BitMasks::MaskContinuationByte) > _Utf8BitMasks::IsContinuationByte)
{
// If the Lead Byte indicates that the last bytes in the buffer is a partial UTF-8 code point then cache them:
// Use the bitmask at index `dwSequenceLen`. Compare the result with the operand having the same index. If they
// are not equal then the sequence has to be cached because it is a partial code point. Otherwise the
// sequence is a complete UTF-8 code point and the whole buffer is ready for the conversion to hstring.
if ((*backIter & _cmpMasks[dwSequenceLen]) != _cmpOperands[dwSequenceLen])
{
std::move(backIter, endPtr, _utf8Partials);
dwRead -= dwSequenceLen;
_dwPartialsLen = dwSequenceLen;
}
break;
}
}
}
// give back a view of the part of the buffer that contains complete code points only
strView = std::string_view{ reinterpret_cast<char*>(_buffer), dwRead };
return S_OK;
}

View File

@@ -0,0 +1,68 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- UTF8OutPipeReader.hpp
Abstract:
- This reads a UTF-8 stream and gives back a buffer that contains complete code points only
- Partial UTF-8 code points at the end of the buffer read are cached and prepended to the next chunk read
Author(s):
- Steffen Illhardt (german-one) 12-July-2019
--*/
#pragma once
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <wil\common.h>
#include <wil\resource.h>
#include <string_view>
class UTF8OutPipeReader final
{
public:
UTF8OutPipeReader(HANDLE outPipe);
[[nodiscard]] HRESULT Read(_Out_ std::string_view& strView);
private:
enum _Utf8BitMasks : BYTE
{
IsAsciiByte = 0b0'0000000, // Any byte representing an ASCII character has the MSB set to 0
MaskAsciiByte = 0b1'0000000, // Bit mask to be used in a bitwise AND operation to find out whether or not a byte match the IsAsciiByte pattern
IsContinuationByte = 0b10'000000, // Continuation bytes of any UTF-8 non-ASCII character have the MSB set to 1 and the adjacent bit set to 0
MaskContinuationByte = 0b11'000000, // Bit mask to be used in a bitwise AND operation to find out whether or not a byte match the IsContinuationByte pattern
IsLeadByteTwoByteSequence = 0b110'00000, // A lead byte that indicates a UTF-8 non-ASCII character consisting of two bytes has the two highest bits set to 1 and the adjacent bit set to 0
MaskLeadByteTwoByteSequence = 0b111'00000, // Bit mask to be used in a bitwise AND operation to find out whether or not a lead byte match the IsLeadByteTwoByteSequence pattern
IsLeadByteThreeByteSequence = 0b1110'0000, // A lead byte that indicates a UTF-8 non-ASCII character consisting of three bytes has the three highest bits set to 1 and the adjacent bit set to 0
MaskLeadByteThreeByteSequence = 0b1111'0000, // Bit mask to be used in a bitwise AND operation to find out whether or not a lead byte match the IsLeadByteThreeByteSequence pattern
IsLeadByteFourByteSequence = 0b11110'000, // A lead byte that indicates a UTF-8 non-ASCII character consisting of four bytes has the four highest bits set to 1 and the adjacent bit set to 0
MaskLeadByteFourByteSequence = 0b11111'000 // Bit mask to be used in a bitwise AND operation to find out whether or not a lead byte match the IsLeadByteFourByteSequence pattern
};
// array of bitmasks
constexpr const static BYTE _cmpMasks[]{
0, // unused
_Utf8BitMasks::MaskContinuationByte,
_Utf8BitMasks::MaskLeadByteTwoByteSequence,
_Utf8BitMasks::MaskLeadByteThreeByteSequence,
};
// array of values for the comparisons
constexpr const static BYTE _cmpOperands[]{
0, // unused
_Utf8BitMasks::IsAsciiByte, // intentionally conflicts with MaskContinuationByte
_Utf8BitMasks::IsLeadByteTwoByteSequence,
_Utf8BitMasks::IsLeadByteThreeByteSequence,
};
HANDLE _outPipe; // non-owning reference to a pipe.
BYTE _buffer[4096]{ 0 }; // buffer for the chunk read
BYTE _utf8Partials[4]{ 0 }; // buffer for code units of a partial UTF-8 code point that have to be cached
DWORD _dwPartialsLen{}; // number of cached UTF-8 code units
};

View File

@@ -5,7 +5,6 @@
<ClCompile Include="..\CodepointWidthDetector.cpp" />
<ClCompile Include="..\convert.cpp" />
<ClCompile Include="..\GlyphWidth.cpp" />
<ClCompile Include="..\Manager.cpp" />
<ClCompile Include="..\MouseEvent.cpp" />
<ClCompile Include="..\FocusEvent.cpp" />
<ClCompile Include="..\IInputEvent.cpp" />
@@ -13,6 +12,7 @@
<ClCompile Include="..\MenuEvent.cpp" />
<ClCompile Include="..\ModifierKeyState.cpp" />
<ClCompile Include="..\Utf16Parser.cpp" />
<ClCompile Include="..\UTF8OutPipeReader.cpp" />
<ClCompile Include="..\Viewport.cpp" />
<ClCompile Include="..\WindowBufferSizeEvent.cpp" />
<ClCompile Include="..\precomp.cpp">
@@ -25,9 +25,9 @@
<ClInclude Include="..\inc\convert.hpp" />
<ClInclude Include="..\inc\GlyphWidth.hpp" />
<ClInclude Include="..\inc\IInputEvent.hpp" />
<ClInclude Include="..\inc\UTF8OutPipeReader.hpp" />
<ClInclude Include="..\inc\Viewport.hpp" />
<ClInclude Include="..\inc\Utf16Parser.hpp" />
<ClInclude Include="..\Manager.h" />
<ClInclude Include="..\precomp.h" />
<ClInclude Include="..\utils.hpp" />
</ItemGroup>
@@ -41,4 +41,4 @@
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(SolutionDir)src\common.build.lib.props" />
<Import Project="$(SolutionDir)src\common.build.post.props" />
</Project>
</Project>

View File

@@ -57,7 +57,7 @@
<ClCompile Include="..\utils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\Manager.cpp">
<ClCompile Include="..\UTF8OutPipeReader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
@@ -86,7 +86,7 @@
<ClInclude Include="..\utils.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\Manager.h">
<ClInclude Include="..\inc\UTF8OutPipeReader.hpp">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>

View File

@@ -33,7 +33,6 @@ SOURCES= \
..\FocusEvent.cpp \
..\GlyphWidth.cpp \
..\KeyEvent.cpp \
..\Manager.cpp \
..\MenuEvent.cpp \
..\ModifierKeyState.cpp \
..\MouseEvent.cpp \

View File

@@ -2,6 +2,7 @@
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<ItemGroup>
<ClCompile Include="UTF8OutPipeReaderTests.cpp" />
<ClCompile Include="UtilsTests.cpp" />
<ClCompile Include="UuidTests.cpp" />
<ClCompile Include="..\precomp.cpp">

View File

@@ -0,0 +1,155 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "WexTestClass.h"
#include "..\..\inc\consoletaeftemplates.hpp"
#include "..\inc\UTF8OutPipeReader.hpp"
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
class UTF8OutPipeReaderTests
{
TEST_CLASS(UTF8OutPipeReaderTests);
TEST_METHOD(TestUtf8MergePartials)
{
// The test uses the character 'GOTHIC LETTER HWAIR' (U+10348) as an example
// Its UTF-8 representation consists of four bytes:
// 1 2 3 4
// 0xF0 0x90 0x8D 0x88
//
// For the test a std::string is filled with 4104 '.' characters to make sure it exceeds the
// buffer size of 4096 bytes in UTF8OutPipeReader.
//
// This figure shows how the string is getting changed for the 7 sub-tests. The digits 1 to 4
// represent the four bytes of the 'Hwair' letter. The vertical bar represents the buffer boundary.
// Test 1: [more points] . . S 1 2 3 4 T|U V W X Y Z . .
// Test 2: [more points] . . S T 1 2 3 4|U V W X Y Z . .
// Test 3: [more points] . . S T U 1 2 3|4 V W X Y Z . .
// Test 4: [more points] . . S T U V 1 2|3 4 W X Y Z . .
// Test 5: [more points] . . S T U V W 1|2 3 4 X Y Z . .
// Test 6: [more points] . . S T U V W X|1 2 3 4 Y Z . .
// Test 7: [more points] . . S T U V W X|Y 1 2 3 4 Z . .
//
// Tests 1, 6, and 7 prove proper ASCII handling.
// Test 2 leaves all four bytes of 'Hwair' in the first chunk.
// Test 3, 4, and 5 move the partials from the end of the first chunk to the begin of the
// second chunk.
//
// At the beginning of a test the whole string is converted into a winrt::hstring for reference.
// During the test a second hstring is concatenated out of the chunks that we get from
// UTF8OutPipeReader::Read. Each chunk is separately converted to hstring in order to make
// sure it would be corrupted if we get UTF-8 partials.
// The test is positive if both hstrings are equal.
const size_t bufferSize{ 4096 }; // NOTE: This has to match the buffer size in UTF8OutPipeReader!
std::string utf8TestString(bufferSize + 8, '.'); // create a test string with the required size
// Test 1:
// ||
utf8TestString.replace(bufferSize - 6, 12, "S\xF0\x90\x8D\x88TUVWXYZ");
VERIFY_SUCCEEDED(RunTest(utf8TestString));
// Test 2:
// | |
utf8TestString.replace(bufferSize - 6, 12, "ST\xF0\x90\x8D\x88UVWXYZ");
VERIFY_SUCCEEDED(RunTest(utf8TestString));
// Test 3:
// | |
utf8TestString.replace(bufferSize - 6, 12, "STU\xF0\x90\x8D\x88VWXYZ");
VERIFY_SUCCEEDED(RunTest(utf8TestString));
// Test 4:
// | |
utf8TestString.replace(bufferSize - 6, 12, "STUV\xF0\x90\x8D\x88WXYZ");
VERIFY_SUCCEEDED(RunTest(utf8TestString));
// Test 5:
// | |
utf8TestString.replace(bufferSize - 6, 12, "STUVW\xF0\x90\x8D\x88XYZ");
VERIFY_SUCCEEDED(RunTest(utf8TestString));
// Test 6:
// | |
utf8TestString.replace(bufferSize - 6, 12, "STUVWX\xF0\x90\x8D\x88YZ");
VERIFY_SUCCEEDED(RunTest(utf8TestString));
// Test 7:
// ||
utf8TestString.replace(bufferSize - 6, 12, "STUVWXY\xF0\x90\x8D\x88Z");
VERIFY_SUCCEEDED(RunTest(utf8TestString));
}
struct ThreadData
{
wil::unique_hfile& inPipe;
std::string& utf8TestString;
};
// Thread function which writes the UTF-8 data to the pipe.
static DWORD WINAPI WritePipeThread(LPVOID threadArg)
{
ThreadData* pThreadData{ reinterpret_cast<ThreadData*>(threadArg) };
DWORD length{};
WriteFile(pThreadData->inPipe.get(), pThreadData->utf8TestString.c_str(), static_cast<DWORD>(pThreadData->utf8TestString.size()), &length, nullptr);
pThreadData->inPipe.reset();
return 0;
}
// Performs the sub-tests.
HRESULT RunTest(std::string& utf8TestString)
{
std::string_view strView{}; // contains the chunk that we get from UTF8OutPipeReader::Read
const winrt::hstring utf16Expected{ winrt::to_hstring(utf8TestString) }; // contains the whole string converted to UTF-16
winrt::hstring utf16Actual{}; // will be concatenated from the converted chunks
wil::unique_hfile outPipe{};
wil::unique_hfile inPipe{};
SECURITY_ATTRIBUTES sa{ sizeof(SECURITY_ATTRIBUTES) };
CreatePipe(&outPipe, &inPipe, &sa, 0); // create the pipe handles
UTF8OutPipeReader reader{ outPipe.get() };
ThreadData data{ inPipe, utf8TestString };
wil::unique_handle threadHandle{ CreateThread(nullptr, 0, WritePipeThread, &data, 0, nullptr) }; // create a thread that writes to the pipe
RETURN_HR_IF_NULL(E_FAIL, threadHandle.get());
// process the chunks that we get from UTF8OutPipeReader::Read
while (true)
{
// get a chunk of UTF-8 data
THROW_IF_FAILED(reader.Read(strView));
if (strView.empty())
{
// this is okay, no data left in the pipe
break;
}
// convert the chunk to hstring and append it to the resulting hstring
utf16Actual = utf16Actual + winrt::to_hstring(strView);
}
WaitForSingleObject(threadHandle.get(), 2000);
// the test passed if both hstrings are equal
if (utf16Actual == utf16Expected)
{
return S_OK;
}
return E_FAIL;
}
};

View File

@@ -187,7 +187,7 @@ function Invoke-OpenConsoleTests()
}
$OpenConsolePath = "$env:OpenConsoleroot\bin\$OpenConsolePlatform\$Configuration\OpenConsole.exe"
$RunTePath = "$env:OpenConsoleRoot\tools\runte.cmd"
$TaefExePath = "$env:OpenConsoleRoot\packages\Taef.Redist.Wlk.10.30.180808002\build\binaries\$Platform\te.exe"
$TaefExePath = "$env:OpenConsoleRoot\packages\Taef.Redist.Wlk.10.38.190610001-uapadmin\build\Binaries\$Platform\te.exe"
$BinDir = "$env:OpenConsoleRoot\bin\$OpenConsolePlatform\$Configuration"
[xml]$TestConfig = Get-Content "$env:OpenConsoleRoot\tools\tests.xml"

5
tools/bzcon.cmd Normal file
View File

@@ -0,0 +1,5 @@
@echo off
"%msbuild%" Openconsole.sln /t:Conhost\Host_EXE /m /p:Configuration=Debug /p:Platform=%ARCH%
:eof

View File

@@ -104,7 +104,7 @@ shift
goto :ARGS_LOOP
:POST_ARGS_LOOP
set TAEF=%OPENCON%\packages\Taef.Redist.Wlk.10.30.180808002\build\binaries\%ARCH%\TE.exe
set TAEF=%OPENCON%\packages\Taef.Redist.Wlk.10.38.190610001-uapadmin\build\Binaries\%ARCH%\TE.exe
rem Set this envvar so setup won't repeat itself
set OpenConBuild=true

24
tools/scratch.cmd Normal file
View File

@@ -0,0 +1,24 @@
@echo off
rem openvt - launch the vtterm binary
rem Runs the VtPipeTerm.exe binary generated by the build in the debug directory.
rem Passes any args along.
setlocal
set _last_build=%OPENCON%\bin\%ARCH%\%_LAST_BUILD_CONF%
if not exist %_last_build%\scratch.exe (
echo Could not locate the scratch.exe in %_last_build%. Double check that it has been built and try again.
goto :eof
)
set _r=%random%
set copy_dir=OpenConsole\%_r%
rem Generate a unique name, so that we can debug multiple revisions of the binary at the same time if needed.
(xcopy /Y %_last_build%\OpenConsole.exe %TEMP%\%copy_dir%\conhost.exe*) > nul
(xcopy /Y %_last_build%\console.dll %TEMP%\%copy_dir%\console.dll*) > nul
(xcopy /Y %_last_build%\scratch.exe %TEMP%\%copy_dir%\scratch.exe*) > nul
echo Launching %TEMP%\%copy_dir%\scratch.exe...
%TEMP%\%copy_dir%\scratch.exe %*