Migrate OSS up to f9a844dbd

# Conflicts:
#	src/inc/til/u8u16convert.h
This commit is contained in:
Dustin Howett 2021-09-21 16:22:57 -05:00
commit dc865529b5
356 changed files with 16039 additions and 4070 deletions

View file

@ -1,10 +1,13 @@
Apc
apc apc
calt
ccmp
Apc
clickable clickable
clig
copyable copyable
dalet dalet
Dcs
dcs dcs
Dcs
dialytika dialytika
dje dje
downside downside
@ -12,8 +15,10 @@ downsides
dze dze
dzhe dzhe
Enum'd Enum'd
Fitt
formattings formattings
ftp ftp
fvar
geeksforgeeks geeksforgeeks
ghe ghe
gje gje
@ -27,7 +32,10 @@ It'd
kje kje
liga liga
lje lje
locl
lorem
maxed maxed
mkmk
mru mru
nje nje
ogonek ogonek
@ -37,10 +45,12 @@ postmodern
ptys ptys
qof qof
qps qps
rclt
reimplementation reimplementation
reserialization reserialization
reserialize reserialize
reserializes reserializes
rlig
runtimes runtimes
shcha shcha
slnt slnt
@ -50,8 +60,10 @@ TLDR
tokenizes tokenizes
tonos tonos
tshe tshe
uiatextrange
UIs UIs
und und
unregister
versioned versioned
We'd We'd
wildcards wildcards

View file

@ -2,11 +2,13 @@ ACCEPTFILES
ACCESSDENIED ACCESSDENIED
alignas alignas
alignof alignof
APPLYTOSUBMENUS
bitfield bitfield
bitfields bitfields
BUILDBRANCH BUILDBRANCH
BUILDMSG BUILDMSG
BUILDNUMBER BUILDNUMBER
BYPOSITION
charconv charconv
CLASSNOTAVAILABLE CLASSNOTAVAILABLE
cmdletbinding cmdletbinding
@ -63,6 +65,7 @@ IObject
iosfwd iosfwd
IPackage IPackage
IPeasant IPeasant
isspace
IStorage IStorage
istream istream
IStringable IStringable
@ -77,7 +80,11 @@ llu
localtime localtime
lround lround
LSHIFT LSHIFT
MENUCOMMAND
MENUDATA
MENUINFO
memicmp memicmp
mptt
mov mov
msappx msappx
MULTIPLEUSE MULTIPLEUSE
@ -92,6 +99,7 @@ NOCHANGEDIR
NOPROGRESS NOPROGRESS
NOREDIRECTIONBITMAP NOREDIRECTIONBITMAP
NOREPEAT NOREPEAT
NOTIFYBYPOS
NOTIFYICON NOTIFYICON
NOTIFYICONDATA NOTIFYICONDATA
ntprivapi ntprivapi
@ -132,11 +140,14 @@ SRWLOCK
STDCPP STDCPP
STDMETHOD STDMETHOD
strchr strchr
strcpy
streambuf streambuf
strtoul
Stubless Stubless
Subheader Subheader
Subpage Subpage
syscall syscall
TASKBARCREATED
TBPF TBPF
THEMECHANGED THEMECHANGED
tlg tlg
@ -151,6 +162,7 @@ userenv
wcsstr wcsstr
wcstoui wcstoui
winmain winmain
wmemcmp
wpc wpc
wsregex wsregex
wwinmain wwinmain

View file

@ -25,6 +25,7 @@ DWINRT
enablewttlogging enablewttlogging
Intelli Intelli
LKG LKG
Lxss
mfcribbon mfcribbon
microsoft microsoft
microsoftonline microsoftonline

View file

@ -31,6 +31,7 @@ Kourosh
kowalczyk kowalczyk
leonmsft leonmsft
Lepilleur Lepilleur
lhecker
lukesampson lukesampson
Manandhar Manandhar
mbadolato mbadolato
@ -66,6 +67,7 @@ sonpham
stakx stakx
thereses thereses
Walisch Walisch
Wellons
Wirt Wirt
Wojciech Wojciech
zadjii zadjii

View file

@ -105,6 +105,7 @@ autoscrolling
Autowrap Autowrap
AVerify AVerify
AVI AVI
AVX
awch awch
azuredevopspodcast azuredevopspodcast
azzle azzle
@ -168,6 +169,7 @@ brandings
BRK BRK
Browsable Browsable
bsearch bsearch
Bspace
bstr bstr
BTNFACE BTNFACE
buf buf
@ -190,6 +192,7 @@ CARETBLINKINGENABLED
CARRIAGERETURN CARRIAGERETURN
cascadia cascadia
cassert cassert
castsi
catid catid
cazamor cazamor
CBash CBash
@ -268,9 +271,12 @@ cmder
CMDEXT CMDEXT
Cmdlet Cmdlet
cmdline cmdline
cmh
CMOUSEBUTTONS CMOUSEBUTTONS
cmp cmp
cmpeq
cmt cmt
cmw
cmyk cmyk
CNL CNL
cnt cnt
@ -403,11 +409,13 @@ csbiex
csharp csharp
CSHORT CSHORT
CSIDL CSIDL
Cspace
csproj csproj
Csr Csr
csrmsg csrmsg
CSRSS CSRSS
csrutil csrutil
css
cstdarg cstdarg
cstddef cstddef
cstdio cstdio
@ -506,6 +514,8 @@ DECAWM
DECCKM DECCKM
DECCOLM DECCOLM
DECDHL DECDHL
decdld
DECDLD
DECDWL DECDWL
DECEKBD DECEKBD
DECID DECID
@ -778,6 +788,7 @@ flyout
fmodern fmodern
fmtarg fmtarg
fmtid fmtid
FNV
FOLDERID FOLDERID
FONTCHANGE FONTCHANGE
fontdlg fontdlg
@ -786,6 +797,7 @@ FONTENUMPROC
FONTFACE FONTFACE
FONTFAMILY FONTFAMILY
FONTHEIGHT FONTHEIGHT
FONTINFO
fontlist fontlist
FONTOK FONTOK
FONTSIZE FONTSIZE
@ -899,6 +911,7 @@ github
gitlab gitlab
gle gle
globals globals
GLYPHENTRY
gmail gmail
GMEM GMEM
GNUC GNUC
@ -947,6 +960,7 @@ hdrstop
HEIGHTSCROLL HEIGHTSCROLL
hfile hfile
hfont hfont
hfontresource
hglobal hglobal
hhh hhh
HHmm HHmm
@ -1021,6 +1035,7 @@ IAction
IApi IApi
IApplication IApplication
IBase IBase
ICache
icacls icacls
iccex iccex
icch icch
@ -1260,6 +1275,7 @@ lnkd
lnkfile lnkfile
LNM LNM
LOADONCALL LOADONCALL
loadu
LOBYTE LOBYTE
localappdata localappdata
localhost localhost
@ -1267,6 +1283,7 @@ locsrc
locstudio locstudio
Loewen Loewen
LOGFONT LOGFONT
LOGFONTA
LOGFONTW LOGFONTW
logissue logissue
lowercased lowercased
@ -1420,6 +1437,7 @@ MOUSEFIRST
MOUSEHWHEEL MOUSEHWHEEL
MOUSEMOVE MOUSEMOVE
mousewheel mousewheel
movemask
MOVESTART MOVESTART
msb msb
msbuild msbuild
@ -1452,6 +1470,7 @@ Mul
multiline multiline
munged munged
munges munges
murmurhash
mutex mutex
mutexes mutexes
muxes muxes
@ -1502,6 +1521,7 @@ nfe
nlength nlength
Nls Nls
NLSMODE NLSMODE
nnn
NOACTIVATE NOACTIVATE
NOAPPLYNOW NOAPPLYNOW
NOCLIP NOCLIP
@ -1582,6 +1602,7 @@ NTVDM
ntverp ntverp
NTWIN NTWIN
nuget nuget
nullability
nullness nullness
nullonfailure nullonfailure
nullopt nullopt
@ -1927,6 +1948,7 @@ realloc
reamapping reamapping
rects rects
redef redef
redefinable
Redir Redir
redirector redirector
redist redist
@ -1972,6 +1994,7 @@ rfc
rftp rftp
rgb rgb
rgba rgba
RGBCOLOR
rgbi rgbi
rgci rgci
rgfae rgfae
@ -2141,6 +2164,7 @@ SIGDN
SINGLEFLAG SINGLEFLAG
SINGLETHREADED SINGLETHREADED
siup siup
sixel
SIZEBOX SIZEBOX
sizeof sizeof
SIZESCROLL SIZESCROLL
@ -2245,6 +2269,7 @@ SWMR
SWP SWP
swprintf swprintf
SYMED SYMED
symlink
SYNCPAINT SYNCPAINT
sys sys
syscalls syscalls
@ -2528,6 +2553,7 @@ vcvarsall
vcxitems vcxitems
vcxproj vcxproj
vec vec
vectorized
VERCTRL VERCTRL
versioning versioning
VERTBAR VERTBAR
@ -2745,6 +2771,7 @@ WTo
wtof wtof
wtoi wtoi
WTs WTs
WTSOFTFONT
wtw wtw
wtypes wtypes
Wubi Wubi
@ -2774,6 +2801,7 @@ xes
xff xff
XFile XFile
XFORM XFORM
xIcon
XManifest XManifest
XMath XMath
XMFLOAT XMFLOAT
@ -2806,6 +2834,7 @@ YCast
YCENTER YCENTER
YCount YCount
YDPI YDPI
yIcon
yml yml
YOffset YOffset
YPosition YPosition

View file

@ -73,6 +73,7 @@ EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Host.unittest", "src\host\ut_lib\host.unittest.vcxproj", "{06EC74CB-9A12-429C-B551-8562EC954747}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Host.unittest", "src\host\ut_lib\host.unittest.vcxproj", "{06EC74CB-9A12-429C-B551-8562EC954747}"
ProjectSection(ProjectDependencies) = postProject ProjectSection(ProjectDependencies) = postProject
{18D09A24-8240-42D6-8CB6-236EEE820263} = {18D09A24-8240-42D6-8CB6-236EEE820263} {18D09A24-8240-42D6-8CB6-236EEE820263} = {18D09A24-8240-42D6-8CB6-236EEE820263}
{71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF} = {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}
EndProjectSection EndProjectSection
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Host.Tests.Unit", "src\host\ut_host\Host.UnitTests.vcxproj", "{531C23E7-4B76-4C08-8AAD-04164CB628C9}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Host.Tests.Unit", "src\host\ut_host\Host.UnitTests.vcxproj", "{531C23E7-4B76-4C08-8AAD-04164CB628C9}"

View file

@ -1,48 +1,487 @@
# This build should never run as CI or against a pull request.
trigger: none trigger: none
pr: none pr: none
pool:
name: WinDevPool-L
demands: ImageOverride -equals WinDevVS16-latest
parameters:
- name: branding
displayName: "Branding (Build Type)"
type: string
default: Release
values:
- Release
- Preview
- name: buildTerminal
displayName: "Build Windows Terminal MSIX"
type: boolean
default: true
- name: buildTerminalVPack
displayName: "Build Windows Terminal VPack"
type: boolean
default: false
- name: buildWPF
displayName: "Build Terminal WPF Control"
type: boolean
default: false
- name: pgoBuildMode
displayName: "PGO Build Mode"
type: string
default: Optimize
values:
- Optimize
- Instrument
- None
- name: buildConfigurations
type: object
default:
- Release
- name: buildPlatforms
type: object
default:
- x64
- x86
- arm64
variables: variables:
baseYearForVersioning: 2019 # Used by build-console-int TerminalInternalPackageVersion: "0.0.7"
versionMajor: 0
versionMinor: 1
# When we move off PackageES for Versioning, we'll need to switch name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
# name to this format. For now, though, we need to use DayOfYear.Rev resources:
# to unique our builds, as mandated by PackageES's Setup task. repositories:
# name: '$(versionMajor).$(versionMinor).$(DayOfYear)$(Rev:r).0' - repository: self
# type: git
# Build name/version number above must end with .0 to make the ref: main
# store publication machinery happy.
name: 'Terminal_$(date:yyMM).$(date:dd)$(rev:rrr)'
# Build Arguments:
# WindowsTerminalOfficialBuild=[true,false]
# true - this is running on our build agent
# false - running locally
# WindowsTerminalBranding=[Dev,Preview,Release]
# <none> - Development build resources (default)
# Preview - Preview build resources
# Release - regular build resources
jobs: jobs:
- template: ./templates/build-console-audit-job.yml - job: Build
parameters: strategy:
platform: x64 matrix:
${{ each config in parameters.buildConfigurations }}:
${{ each platform in parameters.buildPlatforms }}:
${{ config }}_${{ platform }}:
BuildConfiguration: ${{ config }}
BuildPlatform: ${{ platform }}
displayName: Build
cancelTimeoutInMinutes: 1
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: True
- task: PkgESSetupBuild@12
displayName: Package ES - Setup Build
inputs:
disableOutputRedirect: true
- task: PowerShell@2
displayName: Rationalize Build Platform
inputs:
targetType: inline
script: >-
$Arch = "$(BuildPlatform)"
- template: ./templates/build-console-int.yml If ($Arch -Eq "x86") { $Arch = "Win32" }
parameters:
platform: x64
additionalBuildArguments: /p:WindowsTerminalOfficialBuild=true;WindowsTerminalBranding=Preview
- template: ./templates/build-console-int.yml Write-Host "##vso[task.setvariable variable=RationalizedBuildPlatform]${Arch}"
parameters: - task: NuGetToolInstaller@1
platform: x86 displayName: Use NuGet 5.10
additionalBuildArguments: /p:WindowsTerminalOfficialBuild=true;WindowsTerminalBranding=Preview inputs:
versionSpec: 5.10
- task: NuGetCommand@2
displayName: NuGet custom
inputs:
command: custom
selectOrConfig: config
nugetConfigPath: NuGet.Config
arguments: restore OpenConsole.sln -SolutionDirectory $(Build.SourcesDirectory)
- task: UniversalPackages@0
displayName: Download terminal-internal Universal Package
inputs:
feedListDownload: 2b3f8893-a6e8-411f-b197-a9e05576da48
packageListDownload: e82d490c-af86-4733-9dc4-07b772033204
versionListDownload: $(TerminalInternalPackageVersion)
- task: TouchdownBuildTask@1
displayName: Download Localization Files
inputs:
teamId: 7105
authId: $(TouchdownAppId)
authKey: $(TouchdownAppKey)
resourceFilePath: >-
src\cascadia\TerminalApp\Resources\en-US\Resources.resw
- template: ./templates/build-console-int.yml src\cascadia\TerminalControl\Resources\en-US\Resources.resw
parameters:
platform: arm64
additionalBuildArguments: /p:WindowsTerminalOfficialBuild=true;WindowsTerminalBranding=Preview
- template: ./templates/check-formatting.yml src\cascadia\TerminalConnection\Resources\en-US\Resources.resw
- template: ./templates/release-sign-and-bundle.yml src\cascadia\TerminalSettingsModel\Resources\en-US\Resources.resw
src\cascadia\TerminalSettingsEditor\Resources\en-US\Resources.resw
src\cascadia\WindowsTerminalUniversal\Resources\en-US\Resources.resw
src\cascadia\CascadiaPackage\Resources\en-US\Resources.resw
appendRelativeDir: true
localizationTarget: false
pseudoSetting: Included
- task: PowerShell@2
displayName: Move Loc files one level up
inputs:
targetType: inline
script: >-
$Files = Get-ChildItem . -R -Filter 'Resources.resw' | ? FullName -Like '*en-US\*\Resources.resw'
$Files | % { Move-Item -Verbose $_.Directory $_.Directory.Parent.Parent -EA:Ignore }
pwsh: true
- task: PowerShell@2
displayName: Generate NOTICE.html from NOTICE.md
inputs:
filePath: .\build\scripts\Generate-ThirdPartyNotices.ps1
arguments: -MarkdownNoticePath .\NOTICE.md -OutputPath .\src\cascadia\CascadiaPackage\NOTICE.html
pwsh: true
- ${{ if eq(parameters.pgoBuildMode, 'Optimize') }}:
- task: PowerShell@2
displayName: Restore PGO Database
inputs:
filePath: tools/PGODatabase/restore-pgodb.ps1
workingDirectory: $(Build.SourcesDirectory)\tools\PGODatabase
- ${{ if eq(parameters.buildTerminal, true) }}:
- task: VSBuild@1
displayName: Build solution **\OpenConsole.sln
inputs:
solution: '**\OpenConsole.sln'
vsVersion: 16.0
msbuildArgs: /p:WindowsTerminalOfficialBuild=true /p:WindowsTerminalBranding=${{ parameters.branding }} /t:Terminal\CascadiaPackage;Terminal\WindowsTerminalUniversal /p:WindowsTerminalReleaseBuild=true /bl:$(Build.SourcesDirectory)\msbuild.binlog
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
clean: true
maximumCpuCount: true
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: binlog'
condition: failed()
continueOnError: True
inputs:
PathtoPublish: $(Build.SourcesDirectory)\msbuild.binlog
ArtifactName: binlog-$(BuildPlatform)
- ${{ if eq(parameters.pgoBuildMode, 'Optimize') }}:
- task: PowerShell@2
displayName: Validate binaries are optimized
condition: and(succeeded(), eq(variables['BuildPlatform'], 'x64'))
inputs:
targetType: inline
script: >-
$Binaries = 'OpenConsole.exe', 'WindowsTerminal.exe', 'TerminalApp.dll', 'TerminalConnection.dll', 'Microsoft.Terminal.Control.dll', 'Microsoft.Terminal.Remoting.dll', 'Microsoft.Terminal.Settings.Editor.dll', 'Microsoft.Terminal.Settings.Model.dll'
foreach ($BinFile in $Binaries) {
& "$(Build.SourcesDirectory)\tools\PGODatabase\verify-pgo.ps1" "$(Build.SourcesDirectory)/src/cascadia/CascadiaPackage/bin/$(BuildPlatform)/$(BuildConfiguration)/$BinFile"
}
- 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
pwsh: true
- ${{ if eq(parameters.buildWPF, true) }}:
- task: VSBuild@1
displayName: Build solution **\OpenConsole.sln for PublicTerminalCore
condition: and(succeeded(), ne(variables['BuildPlatform'], 'arm64'))
inputs:
solution: '**\OpenConsole.sln'
vsVersion: 16.0
msbuildArgs: /p:WindowsTerminalOfficialBuild=true /p:WindowsTerminalBranding=${{ parameters.branding }} /p:WindowsTerminalReleaseBuild=true /t:Terminal\wpf\PublicTerminalCore
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
- task: PowerShell@2
displayName: Source Index PDBs
inputs:
filePath: build\scripts\Index-Pdbs.ps1
arguments: -SearchDir '$(Build.SourcesDirectory)' -SourceRoot '$(Build.SourcesDirectory)' -recursive -Verbose -CommitId $(Build.SourceVersion)
errorActionPreference: silentlyContinue
- task: ComponentGovernanceComponentDetection@0
displayName: Component Detection
- task: PowerShell@2
displayName: Run Unit Tests
condition: and(succeeded(), or(eq(variables['BuildPlatform'], 'x64'), eq(variables['BuildPlatform'], 'x86')))
enabled: False
inputs:
filePath: build\scripts\Run-Tests.ps1
arguments: -MatchPattern '*unit.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)'
- task: PowerShell@2
displayName: Run Feature Tests
condition: and(succeeded(), eq(variables['BuildPlatform'], 'x64'))
enabled: False
inputs:
filePath: build\scripts\Run-Tests.ps1
arguments: -MatchPattern '*feature.test*.dll' -Platform '$(RationalizedBuildPlatform)' -Configuration '$(BuildConfiguration)'
- ${{ if eq(parameters.buildTerminal, true) }}:
- task: CopyFiles@2
displayName: Copy *.appx/*.msix to Artifacts
inputs:
Contents: >-
**/*.appx
**/*.msix
**/*.appxsym
!**/Microsoft.VCLibs*.appx
TargetFolder: $(Build.ArtifactStagingDirectory)/appx
OverWrite: true
flattenFolders: true
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (appx)
inputs:
PathtoPublish: $(Build.ArtifactStagingDirectory)/appx
ArtifactName: appx-$(BuildPlatform)-$(BuildConfiguration)
- ${{ if eq(parameters.buildWPF, true) }}:
- task: CopyFiles@2
displayName: Copy PublicTerminalCore.dll to Artifacts
condition: and(succeeded(), ne(variables['BuildPlatform'], 'arm64'))
inputs:
Contents: >-
**/PublicTerminalCore.dll
**/api-ms-win-core-synch-l1-2-0.dll
TargetFolder: $(Build.ArtifactStagingDirectory)/wpf
OverWrite: true
flattenFolders: true
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (PublicTerminalCore)
condition: and(succeeded(), ne(variables['BuildPlatform'], 'arm64'))
inputs:
PathtoPublish: $(Build.ArtifactStagingDirectory)/wpf
ArtifactName: wpf-dll-$(BuildPlatform)-$(BuildConfiguration)
- task: PublishSymbols@2
displayName: Publish symbols path
continueOnError: True
inputs:
SearchPattern: '**/*.pdb'
IndexSources: false
SymbolServerType: TeamServices
- ${{ if eq(parameters.buildTerminal, true) }}:
- job: BundleAndSign
displayName: Create and sign AppX/MSIX bundles
dependsOn: Build
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: True
- task: PkgESSetupBuild@12
displayName: Package ES - Setup Build
inputs:
disableOutputRedirect: true
- task: DownloadBuildArtifacts@0
displayName: Download Artifacts (*.appx, *.msix)
inputs:
downloadType: specific
itemPattern: >-
**/*.msix
**/*.appx
extractTars: false
- task: PowerShell@2
displayName: Create WindowsTerminal*.msixbundle
inputs:
filePath: build\scripts\Create-AppxBundle.ps1
arguments: -InputPath "$(System.ArtifactsDirectory)" -ProjectName CascadiaPackage -BundleVersion 0.0.0.0 -OutputPath "$(System.ArtifactsDirectory)\Microsoft.WindowsTerminal_$(XES_APPXMANIFESTVERSION)_8wekyb3d8bbwe.msixbundle"
- task: PowerShell@2
displayName: Create WindowsTerminalUniversal*.msixbundle
inputs:
filePath: build\scripts\Create-AppxBundle.ps1
arguments: -InputPath "$(System.ArtifactsDirectory)" -ProjectName WindowsTerminalUniversal -BundleVersion $(XES_APPXMANIFESTVERSION) -OutputPath "$(System.ArtifactsDirectory)\Microsoft.WindowsTerminalUniversal_$(XES_APPXMANIFESTVERSION)_8wekyb3d8bbwe.msixbundle"
- task: EsrpCodeSigning@1
displayName: Submit *.msixbundle to ESRP for code signing
inputs:
ConnectedServiceName: 9d6d2960-0793-4d59-943e-78dcb434840a
FolderPath: $(System.ArtifactsDirectory)
Pattern: Microsoft.WindowsTerminal*.msixbundle
UseMinimatch: true
signConfigType: inlineSignParams
inlineOperation: >-
[
{
"KeyCode": "Dynamic",
"CertTemplateName": "WINMSAPP1ST",
"CertSubjectName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US",
"OperationCode": "SigntoolSign",
"Parameters": {
"OpusName": "Microsoft",
"OpusInfo": "http://www.microsoft.com",
"FileDigest": "/fd \"SHA256\"",
"TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
},
"ToolName": "sign",
"ToolVersion": "1.0"
},
{
"KeyCode": "Dynamic",
"CertTemplateName": "WINMSAPP1ST",
"CertSubjectName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US",
"OperationCode": "SigntoolVerify",
"Parameters": {},
"ToolName": "sign",
"ToolVersion": "1.0"
}
]
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: appxbundle-signed'
inputs:
PathtoPublish: $(System.ArtifactsDirectory)
ArtifactName: appxbundle-signed
- ${{ if eq(parameters.buildWPF, true) }}:
- job: PackageAndSignWPF
strategy:
matrix:
${{ each config in parameters.buildConfigurations }}:
${{ config }}:
BuildConfiguration: ${{ config }}
displayName: Create NuGet Package (WPF Terminal Control)
dependsOn: Build
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: True
- task: PkgESSetupBuild@12
displayName: Package ES - Setup Build
inputs:
disableOutputRedirect: true
- task: DownloadBuildArtifacts@0
displayName: Download x86 PublicTerminalCore
inputs:
artifactName: wpf-dll-x86-$(BuildConfiguration)
itemPattern: '**/*.dll'
downloadPath: bin\Win32\$(BuildConfiguration)\
extractTars: false
- task: DownloadBuildArtifacts@0
displayName: Download x64 PublicTerminalCore
inputs:
artifactName: wpf-dll-x64-$(BuildConfiguration)
itemPattern: '**/*.dll'
downloadPath: bin\x64\$(BuildConfiguration)\
extractTars: false
- task: PowerShell@2
displayName: Move downloaded artifacts up a level
inputs:
targetType: inline
# Find all artifact files and move them up a directory. Ugh.
script: >-
Get-ChildItem bin -Recurse -Directory -Filter wpf-dll-* | % {
$_ | Get-ChildItem -Recurse -File | % {
Move-Item -Verbose $_.FullName $_.Directory.Parent.FullName
}
}
- task: NuGetToolInstaller@1
displayName: Use NuGet 5.10.0
inputs:
versionSpec: 5.10.0
- task: NuGetCommand@2
displayName: NuGet restore copy
inputs:
selectOrConfig: config
nugetConfigPath: NuGet.Config
- task: VSBuild@1
displayName: Build solution **\OpenConsole.sln for WPF Control
inputs:
solution: '**\OpenConsole.sln'
vsVersion: 16.0
msbuildArgs: /p:WindowsTerminalReleaseBuild=$(UseReleaseBranding);Version=$(XES_PACKAGEVERSIONNUMBER) /t:Pack
platform: Any CPU
configuration: $(BuildConfiguration)
maximumCpuCount: true
- task: PublishSymbols@2
displayName: Publish symbols path
continueOnError: True
inputs:
SearchPattern: '**/*.pdb'
IndexSources: false
SymbolServerType: TeamServices
SymbolsArtifactName: Symbols_WPF_$(BuildConfiguration)
- task: CopyFiles@2
displayName: Copy *.nupkg to Artifacts
inputs:
Contents: '**/*Wpf*.nupkg'
TargetFolder: $(Build.ArtifactStagingDirectory)/nupkg
OverWrite: true
flattenFolders: true
- task: EsrpCodeSigning@1
displayName: Submit *.nupkg to ESRP for code signing
inputs:
ConnectedServiceName: 9d6d2960-0793-4d59-943e-78dcb434840a
FolderPath: $(Build.ArtifactStagingDirectory)/nupkg
Pattern: '*.nupkg'
UseMinimatch: true
signConfigType: inlineSignParams
inlineOperation: >-
[
{
"KeyCode": "CP-401405",
"OperationCode": "NuGetSign",
"Parameters": {},
"ToolName": "sign",
"ToolVersion": "1.0"
},
{
"KeyCode": "CP-401405",
"OperationCode": "NuGetVerify",
"Parameters": {},
"ToolName": "sign",
"ToolVersion": "1.0"
}
]
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (nupkg)
inputs:
PathtoPublish: $(Build.ArtifactStagingDirectory)\nupkg
ArtifactName: wpf-nupkg-$(BuildConfiguration)
- ${{ if eq(parameters.buildTerminalVPack, true) }}:
- job: VPack
displayName: Create Windows vPack
dependsOn: BundleAndSign
steps:
- checkout: self
clean: true
submodules: true
- task: PkgESSetupBuild@12
displayName: Package ES - Setup Build
- task: DownloadBuildArtifacts@0
displayName: Download Build Artifacts
inputs:
artifactName: appxbundle-signed
extractTars: false
- task: PowerShell@2
displayName: Rename and stage packages for vpack
inputs:
targetType: inline
script: >-
# Rename to known/fixed name for Windows build system
Get-ChildItem Microsoft.WindowsTerminal_*.msixbundle | Rename-Item -NewName { 'Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle' }
# Create vpack directory and place item inside
mkdir WindowsTerminal.app
mv Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle .\WindowsTerminal.app\
workingDirectory: $(System.ArtifactsDirectory)\appxbundle-signed
- task: PkgESVPack@12
displayName: 'Package ES - VPack'
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
inputs:
sourceDirectory: $(System.ArtifactsDirectory)\appxbundle-signed\WindowsTerminal.app
description: Windows Terminal pre-install application
pushPkgName: WindowsTerminal.app
owner: condev
...

View file

@ -8,9 +8,12 @@ jobs:
variables: variables:
BuildConfiguration: AuditMode BuildConfiguration: AuditMode
BuildPlatform: ${{ parameters.platform }} BuildPlatform: ${{ parameters.platform }}
pool: "windevbuildagents" pool:
# The public pool is also an option! ${{ if eq(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
# pool: { vmImage: windows-2019 } name: WinDevPoolOSS-L
${{ if ne(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
name: WinDevPool-L
demands: ImageOverride -equals WinDevVS16-latest
steps: steps:
- checkout: self - checkout: self

View file

@ -11,9 +11,12 @@ jobs:
variables: variables:
BuildConfiguration: ${{ parameters.configuration }} BuildConfiguration: ${{ parameters.configuration }}
BuildPlatform: ${{ parameters.platform }} BuildPlatform: ${{ parameters.platform }}
pool: "windevbuildagents" pool:
# The public pool is also an option! ${{ if eq(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
# pool: { vmImage: windows-2019 } name: WinDevPoolOSS-L
${{ if ne(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
name: WinDevPool-L
demands: ImageOverride -equals WinDevVS16-latest
steps: steps:
- template: build-console-steps.yml - template: build-console-steps.yml

View file

@ -1,31 +0,0 @@
parameters:
configuration: 'Release'
platform: ''
additionalBuildArguments: ''
jobs:
- job: Build${{ parameters.platform }}${{ parameters.configuration }}
displayName: Build ${{ parameters.platform }} ${{ parameters.configuration }}
variables:
BuildConfiguration: ${{ parameters.configuration }}
BuildPlatform: ${{ parameters.platform }}
PGOBuildMode: 'Optimize'
pool:
name: Package ES Lab E
demands:
- msbuild
- visualstudio
- vstest
steps:
- task: PkgESSetupBuild@10
displayName: 'Package ES - Setup Build'
inputs:
useDfs: false
productName: WindowsTerminal
disableOutputRedirect: true
- template: build-console-steps.yml
parameters:
additionalBuildArguments: "/p:XesUseOneStoreVersioning=true;XesBaseYearForStoreVersion=$(baseYearForVersioning) ${{ parameters.additionalBuildArguments }}"

View file

@ -12,9 +12,12 @@ jobs:
BuildConfiguration: ${{ parameters.configuration }} BuildConfiguration: ${{ parameters.configuration }}
BuildPlatform: ${{ parameters.platform }} BuildPlatform: ${{ parameters.platform }}
PGOBuildMode: 'Instrument' PGOBuildMode: 'Instrument'
pool: "windevbuildagents" pool:
# The public pool is also an option! ${{ if eq(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
# pool: { vmImage: windows-2019 } name: WinDevPoolOSS-L
${{ if ne(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
name: WinDevPool-L
demands: ImageOverride -equals WinDevVS16-latest
steps: steps:
- template: build-console-steps.yml - template: build-console-steps.yml

View file

@ -1,74 +0,0 @@
parameters:
configuration: 'Release'
jobs:
- job: SignDeploy${{ parameters.configuration }}
displayName: Sign and Deploy for ${{ parameters.configuration }}
dependsOn:
- Buildx64AuditMode
- Buildx64Release
- Buildx86Release
- Buildarm64Release
- CodeFormatCheck
condition: |
and
(
in(dependencies.Buildx64AuditMode.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
in(dependencies.Buildx64Release.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
in(dependencies.Buildx86Release.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
in(dependencies.Buildarm64Release.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'),
in(dependencies.CodeFormatCheck.result, 'Succeeded', 'SucceededWithIssues', 'Skipped')
)
variables:
BuildConfiguration: ${{ parameters.configuration }}
AppxProjectName: CascadiaPackage
AppxBundleName: Microsoft.WindowsTerminal_8wekyb3d8bbwe.msixbundle
pool:
name: Package ES Lab E
steps:
- checkout: self
clean: true
- task: PkgESSetupBuild@10
displayName: 'Package ES - Setup Build'
inputs:
useDfs: false
productName: WindowsTerminal
disableOutputRedirect: true
- task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0
displayName: 'Component Detection'
- task: DownloadBuildArtifacts@0
displayName: Download AppX artifacts
inputs:
artifactName: 'appx-$(BuildConfiguration)'
itemPattern: |
**/*.appx
**/*.msix
downloadPath: '$(Build.ArtifactStagingDirectory)\appx'
- task: PowerShell@2
displayName: 'Create $(AppxBundleName)'
inputs:
targetType: filePath
filePath: '.\build\scripts\Create-AppxBundle.ps1'
arguments: |
-InputPath "$(Build.ArtifactStagingDirectory)\appx" -ProjectName $(AppxProjectName) -BundleVersion 0.0.0.0 -OutputPath "$(Build.ArtifactStagingDirectory)\$(AppxBundleName)"
- task: PkgESCodeSign@10
displayName: 'Package ES - SignConfig.WindowsTerminal.xml'
inputs:
signConfigXml: 'build\config\SignConfig.WindowsTerminal.xml'
inPathRoot: '$(Build.ArtifactStagingDirectory)'
outPathRoot: '$(Build.ArtifactStagingDirectory)\signed'
- task: PublishBuildArtifacts@1
displayName: 'Publish Signed AppX'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)\signed'
ArtifactName: 'appxbundle-signed-$(BuildConfiguration)'

View file

@ -4,13 +4,13 @@
"title": "Microsoft's Windows Terminal Settings Profile Schema", "title": "Microsoft's Windows Terminal Settings Profile Schema",
"definitions": { "definitions": {
"KeyChordSegment": { "KeyChordSegment": {
"pattern": "^(?<modifier>(?<mod1>ctrl|alt|shift|win)(?:\\+(?<mod2>ctrl|alt|shift|win)(?<!\\k<mod1>))?(?:\\+(?<mod3>ctrl|alt|shift|win)(?<!\\k<mod1>|\\k<mod2>))?(?:\\+(?<mod4>ctrl|alt|shift|win)(?<!\\k<mod1>|\\k<mod2>|\\k<mod3>))?\\+)?(?<key>[^\\s+]|app|menu|backspace|tab|enter|esc|escape|space|pgup|pageup|pgdn|pagedown|end|home|left|up|right|down|insert|delete|(?<!shift.+)(?:numpad_?[0-9]|numpad_(?:period|decimal))|numpad_(?:multiply|plus|add|minus|subtract|divide)|f[1-9]|f1[0-9]|f2[0-4]|plus)$", "pattern": "^(?:(?:ctrl|alt|shift|win)\\+)*(?:app|backspace|browser_(?:back|forward|refresh|stop|search|favorites|home)|comma|delete|down|end|enter|esc|escape|home|insert|left|menu|minus|pagedown|pageup|period|pgdn|pgup|plus|right|space|tab|up|f(?:1\\d?|2[0-4]?|[3-9])|numpad\\d|numpad_(?:\\d|add|decimal|divide|minus|multiply|period|plus|subtract)|(?:vk|sc)\\((?:[1-9]|1?\\d{2}|2[0-4]\\d|25[0-5])\\)|[^\\s+])(?:\\+(?:ctrl|alt|shift|win))*$",
"type": "string", "type": "string",
"description": "The string should fit the format \"[ctrl+][alt+][shift+][win+]<keyName>\", where each modifier is optional, separated by + symbols, and keyName is either one of the names listed in the table below, or any single key character. The string should be written in full lowercase.\napp, menu\tMENU key\nbackspace\tBACKSPACE key\ntab\tTAB key\nenter\tENTER key\nesc, escape\tESC key\nspace\tSPACEBAR\npgup, pageup\tPAGE UP key\npgdn, pagedown\tPAGE DOWN key\nend\tEND key\nhome\tHOME key\nleft\tLEFT ARROW key\nup\tUP ARROW key\nright\tRIGHT ARROW key\ndown\tDOWN ARROW key\ninsert\tINS key\ndelete\tDEL key\nnumpad_0-numpad_9, numpad0-numpad9\tNumeric keypad keys 0 to 9. Can't be combined with the shift modifier.\nnumpad_multiply\tNumeric keypad MULTIPLY key (*)\nnumpad_plus, numpad_add\tNumeric keypad ADD key (+)\nnumpad_minus, numpad_subtract\tNumeric keypad SUBTRACT key (-)\nnumpad_period, numpad_decimal\tNumeric keypad DECIMAL key (.). Can't be combined with the shift modifier.\nnumpad_divide\tNumeric keypad DIVIDE key (/)\nf1-f24\tF1 to F24 function keys\nplus\tADD key (+)" "description": "The string should fit the format \"[ctrl+][alt+][shift+][win+]<KeyName>\", where each modifier is optional. KeyName is either any single key character, an explicit virtual key or scan code in the form vk(nnn) and sc(nnn) respectively, or one of the special names listed at https://docs.microsoft.com/en-us/windows/terminal/customize-settings/actions#accepted-modifiers-and-keys"
}, },
"Color": { "Color": {
"default": "#", "default": "#",
"pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", "pattern": "^#[A-Fa-f0-9]{3}(?:[A-Fa-f0-9]{3})?$",
"type": "string", "type": "string",
"format": "color" "format": "color"
}, },
@ -154,6 +154,17 @@
"description": "Sets how the background image aligns to the boundaries of the window when unfocused. Possible values: \"center\", \"left\", \"top\", \"right\", \"bottom\", \"topLeft\", \"topRight\", \"bottomLeft\", \"bottomRight\"", "description": "Sets how the background image aligns to the boundaries of the window when unfocused. Possible values: \"center\", \"left\", \"top\", \"right\", \"bottom\", \"topLeft\", \"topRight\", \"bottomLeft\", \"bottomRight\"",
"type": "string" "type": "string"
}, },
"intenseTextStyle": {
"default": "bright",
"description": "Controls how 'intense' text is rendered. Values are \"bold\", \"bright\", \"all\" and \"none\"",
"enum": [
"none",
"bold",
"bright",
"all"
],
"type": "string"
},
"experimental.retroTerminalEffect": { "experimental.retroTerminalEffect": {
"description": "When set to true, enable retro terminal effects when unfocused. This is an experimental feature, and its continued existence is not guaranteed.", "description": "When set to true, enable retro terminal effects when unfocused. This is an experimental feature, and its continued existence is not guaranteed.",
"type": "boolean" "type": "boolean"
@ -238,6 +249,8 @@
"identifyWindow", "identifyWindow",
"identifyWindows", "identifyWindows",
"moveFocus", "moveFocus",
"movePane",
"swapPane",
"moveTab", "moveTab",
"newTab", "newTab",
"newWindow", "newWindow",
@ -270,6 +283,7 @@
"toggleFocusMode", "toggleFocusMode",
"toggleFullscreen", "toggleFullscreen",
"togglePaneZoom", "togglePaneZoom",
"toggleSplitOrientation",
"toggleReadOnlyMode", "toggleReadOnlyMode",
"toggleShaderEffects", "toggleShaderEffects",
"wt", "wt",
@ -283,7 +297,9 @@
"right", "right",
"up", "up",
"down", "down",
"previous" "previous",
"nextInOrder",
"previousInOrder"
], ],
"type": "string" "type": "string"
}, },
@ -491,6 +507,23 @@
"type": "integer", "type": "integer",
"default": 0, "default": 0,
"description": "Which tab to switch to, with the first being 0" "description": "Which tab to switch to, with the first being 0"
}
}
}
],
"required": [ "index" ]
},
"MovePaneAction": {
"description": "Arguments corresponding to a Move Pane Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type": "string", "pattern": "movePane" },
"index": {
"type": "integer",
"default": 0,
"description": "Which tab to move the pane to, with the first being 0"
} }
} }
} }
@ -507,7 +540,24 @@
"direction": { "direction": {
"$ref": "#/definitions/FocusDirection", "$ref": "#/definitions/FocusDirection",
"default": "left", "default": "left",
"description": "The direction to move focus in, between panes. Direction can be 'previous' to move to the most recently used pane." "description": "The direction to move focus in, between panes. Direction can be 'previous' to move to the most recently used pane, or 'nextInOrder' or 'previousInOrder' to move to the next or previous pane."
}
}
}
],
"required": [ "direction" ]
},
"SwapPaneAction": {
"description": "Arguments corresponding to a Swap Pane Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type": "string", "pattern": "swapPane" },
"direction": {
"$ref": "#/definitions/FocusDirection",
"default": "left",
"description": "The direction to move the focus pane in, swapping panes. Direction can be 'previous' to swap with the most recently used pane, or 'nextInOrder' or 'previousInOrder' to move to the next or previous pane."
} }
} }
} }
@ -596,7 +646,7 @@
"defaultsFile", "defaultsFile",
"allFiles", "allFiles",
"settingsUI" "settingsUI"
] ]
} }
} }
@ -952,6 +1002,8 @@
{ "$ref": "#/definitions/NewTabAction" }, { "$ref": "#/definitions/NewTabAction" },
{ "$ref": "#/definitions/SwitchToTabAction" }, { "$ref": "#/definitions/SwitchToTabAction" },
{ "$ref": "#/definitions/MoveFocusAction" }, { "$ref": "#/definitions/MoveFocusAction" },
{ "$ref": "#/definitions/MovePaneAction" },
{ "$ref": "#/definitions/SwapPaneAction" },
{ "$ref": "#/definitions/ResizePaneAction" }, { "$ref": "#/definitions/ResizePaneAction" },
{ "$ref": "#/definitions/SendInputAction" }, { "$ref": "#/definitions/SendInputAction" },
{ "$ref": "#/definitions/SplitPaneAction" }, { "$ref": "#/definitions/SplitPaneAction" },
@ -1116,6 +1168,10 @@
"description": "When set to true, we will use the software renderer (a.k.a. WARP) instead of the hardware one.", "description": "When set to true, we will use the software renderer (a.k.a. WARP) instead of the hardware one.",
"type": "boolean" "type": "boolean"
}, },
"experimental.input.forceVT": {
"description": "Force the terminal to use the legacy input encoding. Certain keys in some applications may stop working when enabling this setting.",
"type": "boolean"
},
"initialCols": { "initialCols": {
"default": 120, "default": 120,
"description": "The number of columns displayed in the window upon first load. If \"launchMode\" is set to \"maximized\" (or \"maximizedFocus\"), this property is ignored.", "description": "The number of columns displayed in the window upon first load. If \"launchMode\" is set to \"maximized\" (or \"maximizedFocus\"), this property is ignored.",
@ -1158,6 +1214,16 @@
"minimum": 0, "minimum": 0,
"type": [ "integer", "string" ], "type": [ "integer", "string" ],
"deprecated": true "deprecated": true
},
"minimizeToTray": {
"default": "false",
"description": "When set to true, minimizing a Terminal window will no longer appear in the taskbar. Instead, a Terminal icon will appear in the system tray through which the user can access their windows.",
"type": "boolean"
},
"alwaysShowTrayIcon": {
"default": "false",
"description": "When set to true, the Terminal's tray icon will always be shown in the system tray.",
"type": "boolean"
}, },
"actions": { "actions": {
"description": "Properties are specific to each custom action.", "description": "Properties are specific to each custom action.",

View file

@ -140,12 +140,12 @@
<!-- **END VC LIBS HACK** --> <!-- **END VC LIBS HACK** -->
<!-- This is required to get the package dependency in the AppXManifest. --> <!-- This is required to get the package dependency in the AppXManifest. -->
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" /> <Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <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> <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> </PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets'))" /> <Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets'))" />
</Target> </Target>

View file

@ -5,7 +5,7 @@
#include "MyPage.h" #include "MyPage.h"
#include <LibraryResources.h> #include <LibraryResources.h>
#include "MyPage.g.cpp" #include "MyPage.g.cpp"
#include "..\..\..\src\cascadia\UnitTests_Control\MockControlSettings.h" #include "MySettings.h"
using namespace std::chrono_literals; using namespace std::chrono_literals;
using namespace winrt::Microsoft::Terminal; using namespace winrt::Microsoft::Terminal;
@ -26,17 +26,24 @@ namespace winrt::SampleApp::implementation
void MyPage::Create() void MyPage::Create()
{ {
TerminalConnection::EchoConnection conn{}; auto settings = winrt::make_self<implementation::MySettings>();
auto settings = winrt::make_self<ControlUnitTests::MockControlSettings>();
auto connectionSettings{ TerminalConnection::ConptyConnection::CreateSettings(L"cmd.exe /k echo This TermControl is hosted in-proc...",
winrt::hstring{},
L"",
nullptr,
32,
80,
winrt::guid()) };
// "Microsoft.Terminal.TerminalConnection.ConptyConnection"
winrt::hstring myClass{ winrt::name_of<TerminalConnection::ConptyConnection>() };
TerminalConnection::ConnectionInformation connectInfo{ myClass, connectionSettings };
TerminalConnection::ITerminalConnection conn{ TerminalConnection::ConnectionInformation::CreateConnection(connectInfo) };
Control::TermControl control{ *settings, conn }; Control::TermControl control{ *settings, conn };
InProcContent().Children().Append(control); InProcContent().Children().Append(control);
// Once the control loads (and not before that), write some text for debugging:
control.Initialized([conn](auto&&, auto&&) {
conn.WriteInput(L"This TermControl is hosted in-proc...");
});
} }
// Method Description: // Method Description:

View file

@ -19,10 +19,15 @@
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Button x:Name="TabRow" <StackPanel Orientation="Horizontal">
Grid.Row="0"> <TextBox x:Name="GuidInput"
&quot;Tabs&quot; Width="400"
</Button> PlaceholderText="{}{guid here}" />
<Button Grid.Row="0">
Create
</Button>
</StackPanel>
<Grid x:Name="TabContent" <Grid x:Name="TabContent"
Grid.Row="1" Grid.Row="1"

View file

@ -4,6 +4,8 @@ Licensed under the MIT license.
--*/ --*/
#pragma once #pragma once
#include "../../inc/cppwinrt_utils.h" #include "../../inc/cppwinrt_utils.h"
#include "../types/inc/colorTable.hpp"
#include <DefaultSettings.h> #include <DefaultSettings.h>
#include <conattrs.hpp> #include <conattrs.hpp>
#include "MySettings.g.h" #include "MySettings.g.h"
@ -12,9 +14,6 @@ namespace winrt::SampleApp::implementation
{ {
struct MySettings : MySettingsT<MySettings> struct MySettings : MySettingsT<MySettings>
{ {
public:
MySettings() = default;
// --------------------------- Core Settings --------------------------- // --------------------------- Core Settings ---------------------------
// All of these settings are defined in ICoreSettings. // All of these settings are defined in ICoreSettings.
@ -88,6 +87,14 @@ namespace winrt::SampleApp::implementation
winrt::Microsoft::Terminal::Core::Color GetColorTableEntry(int32_t index) noexcept { return _ColorTable.at(index); } winrt::Microsoft::Terminal::Core::Color GetColorTableEntry(int32_t index) noexcept { return _ColorTable.at(index); }
std::array<winrt::Microsoft::Terminal::Core::Color, 16> ColorTable() { return _ColorTable; } std::array<winrt::Microsoft::Terminal::Core::Color, 16> ColorTable() { return _ColorTable; }
void ColorTable(std::array<winrt::Microsoft::Terminal::Core::Color, 16> /*colors*/) {} void ColorTable(std::array<winrt::Microsoft::Terminal::Core::Color, 16> /*colors*/) {}
MySettings()
{
const auto campbellSpan = ::Microsoft::Console::Utils::CampbellColorTable();
std::transform(campbellSpan.begin(), campbellSpan.end(), _ColorTable.begin(), [](auto&& color) {
return static_cast<winrt::Microsoft::Terminal::Core::Color>(til::color{ color });
});
}
}; };
} }

View file

@ -147,13 +147,13 @@
<!-- ========================= Globals ======================== --> <!-- ========================= Globals ======================== -->
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" /> <Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" /> <Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" /> <Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <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> <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> </PropertyGroup>
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets'))" /> <Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" /> <Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
</Target> </Target>

View file

@ -48,6 +48,10 @@
<Private>true</Private> <Private>true</Private>
<CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies> <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
</ProjectReference> </ProjectReference>
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj">
<Project>{18D09A24-8240-42D6-8CB6-236EEE820263}</Project>
</ProjectReference>
</ItemGroup> </ItemGroup>
@ -76,13 +80,13 @@
</ItemGroup> </ItemGroup>
<Import Project="$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" /> <Import Project="$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="$(OpenConsoleDir)packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('$(OpenConsoleDir)packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" /> <Import Project="$(OpenConsoleDir)packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('$(OpenConsoleDir)packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <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> <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> </PropertyGroup>
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets'))" /> <Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" /> <Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
</Target> </Target>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.1.3" targetFramework="native" /> <package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.1.3" targetFramework="native" />
<package id="Microsoft.UI.Xaml" version="2.5.0-prerelease.201202003" targetFramework="native" /> <package id="Microsoft.UI.Xaml" version="2.6.2-prerelease.210818003" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.210309.3" targetFramework="native" /> <package id="Microsoft.Windows.CppWinRT" version="2.0.210309.3" targetFramework="native" />
</packages> </packages>

View file

@ -120,14 +120,14 @@
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" /> <Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" /> <Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" /> <Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
<Import Project="..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.4\build\native\Microsoft.VCRTForwarders.140.targets" Condition="Exists('..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.4\build\native\Microsoft.VCRTForwarders.140.targets')" /> <Import Project="..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.4\build\native\Microsoft.VCRTForwarders.140.targets" Condition="Exists('..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.4\build\native\Microsoft.VCRTForwarders.140.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <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> <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> </PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets'))" /> <Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props'))" /> <Error Condition="!Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" /> <Error Condition="!Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.4\build\native\Microsoft.VCRTForwarders.140.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.4\build\native\Microsoft.VCRTForwarders.140.targets'))" /> <Error Condition="!Exists('..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.4\build\native\Microsoft.VCRTForwarders.140.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.4\build\native\Microsoft.VCRTForwarders.140.targets'))" />

View file

@ -2,6 +2,6 @@
<packages> <packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.210309.3" targetFramework="native" /> <package id="Microsoft.Windows.CppWinRT" version="2.0.210309.3" targetFramework="native" />
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.1.3" targetFramework="native" /> <package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.1.3" targetFramework="native" />
<package id="Microsoft.UI.Xaml" version="2.5.0-prerelease.201202003" targetFramework="native" /> <package id="Microsoft.UI.Xaml" version="2.6.2-prerelease.210818003" targetFramework="native" />
<package id="Microsoft.VCRTForwarders.140" version="1.0.4" targetFramework="native" /> <package id="Microsoft.VCRTForwarders.140" version="1.0.4" targetFramework="native" />
</packages> </packages>

View file

@ -6,7 +6,7 @@ Module Name:
- OutputCellView.hpp - OutputCellView.hpp
Abstract: Abstract:
- Read-only view into a single cell of data that someone is attempting to write into the output buffer. - Read view into a single cell of data that someone is attempting to write into the output buffer.
- This is done for performance reasons (avoid heap allocs and copies). - This is done for performance reasons (avoid heap allocs and copies).
Author: Author:
@ -36,6 +36,21 @@ public:
TextAttribute TextAttr() const noexcept; TextAttribute TextAttr() const noexcept;
TextAttributeBehavior TextAttrBehavior() const noexcept; TextAttributeBehavior TextAttrBehavior() const noexcept;
void UpdateText(const std::wstring_view& view) noexcept
{
_view = view;
};
void UpdateDbcsAttribute(const DbcsAttribute& dbcsAttr) noexcept
{
_dbcsAttr = dbcsAttr;
}
void UpdateTextAttribute(const TextAttribute& textAttr) noexcept
{
_textAttr = textAttr;
}
bool operator==(const OutputCellView& view) const noexcept; bool operator==(const OutputCellView& view) const noexcept;
bool operator!=(const OutputCellView& view) const noexcept; bool operator!=(const OutputCellView& view) const noexcept;

View file

@ -95,16 +95,18 @@ bool TextAttribute::IsLegacy() const noexcept
// - defaultFgColor: the default foreground color rgb value. // - defaultFgColor: the default foreground color rgb value.
// - defaultBgColor: the default background color rgb value. // - defaultBgColor: the default background color rgb value.
// - reverseScreenMode: true if the screen mode is reversed. // - reverseScreenMode: true if the screen mode is reversed.
// - blinkingIsFaint: true if blinking should be interpreted as faint. // - blinkingIsFaint: true if blinking should be interpreted as faint. (defaults to false)
// - boldIsBright: true if "bold" should be interpreted as bright. (defaults to true)
// Return Value: // Return Value:
// - the foreground and background colors that should be displayed. // - the foreground and background colors that should be displayed.
std::pair<COLORREF, COLORREF> TextAttribute::CalculateRgbColors(const gsl::span<const COLORREF> colorTable, std::pair<COLORREF, COLORREF> TextAttribute::CalculateRgbColors(const std::array<COLORREF, 256>& colorTable,
const COLORREF defaultFgColor, const COLORREF defaultFgColor,
const COLORREF defaultBgColor, const COLORREF defaultBgColor,
const bool reverseScreenMode, const bool reverseScreenMode,
const bool blinkingIsFaint) const noexcept const bool blinkingIsFaint,
const bool boldIsBright) const noexcept
{ {
auto fg = _foreground.GetColor(colorTable, defaultFgColor, IsBold()); auto fg = _foreground.GetColor(colorTable, defaultFgColor, boldIsBright && IsBold());
auto bg = _background.GetColor(colorTable, defaultBgColor); auto bg = _background.GetColor(colorTable, defaultBgColor);
if (IsFaint() || (IsBlinking() && blinkingIsFaint)) if (IsFaint() || (IsBlinking() && blinkingIsFaint))
{ {

View file

@ -64,11 +64,12 @@ public:
static TextAttribute StripErroneousVT16VersionsOfLegacyDefaults(const TextAttribute& attribute) noexcept; static TextAttribute StripErroneousVT16VersionsOfLegacyDefaults(const TextAttribute& attribute) noexcept;
WORD GetLegacyAttributes() const noexcept; WORD GetLegacyAttributes() const noexcept;
std::pair<COLORREF, COLORREF> CalculateRgbColors(const gsl::span<const COLORREF> colorTable, std::pair<COLORREF, COLORREF> CalculateRgbColors(const std::array<COLORREF, 256>& colorTable,
const COLORREF defaultFgColor, const COLORREF defaultFgColor,
const COLORREF defaultBgColor, const COLORREF defaultBgColor,
const bool reverseScreenMode = false, const bool reverseScreenMode = false,
const bool blinkingIsFaint = false) const noexcept; const bool blinkingIsFaint = false,
const bool boldIsBright = true) const noexcept;
bool IsLeadingByte() const noexcept; bool IsLeadingByte() const noexcept;
bool IsTrailingByte() const noexcept; bool IsTrailingByte() const noexcept;

View file

@ -50,6 +50,9 @@ constexpr std::array<BYTE, 256> Index256ToIndex16 = {
// clang-format on // clang-format on
// We should only need 4B for TextColor. Any more than that is just waste.
static_assert(sizeof(TextColor) == 4);
bool TextColor::CanBeBrightened() const noexcept bool TextColor::CanBeBrightened() const noexcept
{ {
return IsIndex16() || IsDefault(); return IsIndex16() || IsDefault();
@ -138,15 +141,12 @@ void TextColor::SetDefault() noexcept
// - brighten: if true, we'll brighten a dark color table index. // - brighten: if true, we'll brighten a dark color table index.
// Return Value: // Return Value:
// - a COLORREF containing the real value of this TextColor. // - a COLORREF containing the real value of this TextColor.
COLORREF TextColor::GetColor(gsl::span<const COLORREF> colorTable, COLORREF TextColor::GetColor(const std::array<COLORREF, 256>& colorTable, const COLORREF defaultColor, bool brighten) const noexcept
const COLORREF defaultColor,
bool brighten) const noexcept
{ {
if (IsDefault()) if (IsDefault())
{ {
if (brighten) if (brighten)
{ {
FAIL_FAST_IF(colorTable.size() < 16);
// See MSFT:20266024 for context on this fix. // See MSFT:20266024 for context on this fix.
// Additionally todo MSFT:20271956 to fix this better for 19H2+ // Additionally todo MSFT:20271956 to fix this better for 19H2+
// If we're a default color, check to see if the defaultColor exists // If we're a default color, check to see if the defaultColor exists
@ -156,6 +156,61 @@ COLORREF TextColor::GetColor(gsl::span<const COLORREF> colorTable,
// (Settings::_DefaultForeground==INVALID_COLOR, and the index // (Settings::_DefaultForeground==INVALID_COLOR, and the index
// from _wFillAttribute is being used instead.) // from _wFillAttribute is being used instead.)
// If we find a match, return instead the bright version of this color // If we find a match, return instead the bright version of this color
static_assert(sizeof(COLORREF) * 8 == 32, "The vectorized code broke. If you can't fix COLORREF, just remove the vectorized code.");
#pragma warning(push)
#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
#pragma warning(disable : 26490) // Don't use reinterpret_cast (type.1).
#ifdef __AVX2__
// I wrote this vectorized code one day, because the sun was shining so nicely.
// There's no other reason for this to exist here, except for being pretty.
// This code implements the exact same for loop you can find below, but is ~3x faster.
//
// A brief explanation for people unfamiliar with vectorized instructions:
// Vectorized instructions, like "SSE" or "AVX", allow you to run
// common operations like additions, multiplications, comparisons,
// or bitwise operations concurrently on multiple values at once.
//
// We want to find the given defaultColor in the first 8 values of colorTable.
// Coincidentally a COLORREF is a DWORD and 8 of them are exactly 256 bits.
// -- The size of a single AVX register.
//
// Thus, the code works like this:
// 1. Load all 8 DWORDs at once into one register
// 2. Set the same defaultColor 8 times in another register
// 3. Compare all 8 values at once
// The result is either 0xffffffff or 0x00000000.
// 4. Extract the most significant bit of each DWORD
// Assuming that no duplicate colors exist in colorTable,
// the result will be something like 0b00100000.
// 5. Use BitScanForward (bsf) to find the index of the most significant 1 bit.
const auto haystack = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(colorTable.data())); // 1.
const auto needle = _mm256_set1_epi32(__builtin_bit_cast(int, defaultColor)); // 2.
const auto result = _mm256_cmpeq_epi32(haystack, needle); // 3.
const auto mask = _mm256_movemask_ps(_mm256_castsi256_ps(result)); // 4.
unsigned long index;
return _BitScanForward(&index, mask) ? til::at(colorTable, static_cast<size_t>(index) + 8) : defaultColor; // 5.
#elif _M_AMD64
// If you look closely this SSE2 algorithm is the same as the AVX one.
// The two differences are that we need to:
// * do everything twice, because SSE is limited to 128 bits and not 256.
// * use _mm_packs_epi32 to merge two 128 bits vectors into one in step 3.5.
// _mm_packs_epi32 takes two SSE registers and truncates all 8 DWORDs into 8 WORDs,
// the latter of which fits into a single register (which is then used in the identical step 4).
// * since the result are now 8 WORDs, we need to use _mm_movemask_epi8 (there's no 16-bit variant),
// which unlike AVX's step 4 results in in something like 0b0000110000000000.
// --> the index returned by _BitScanForward must be divided by 2.
const auto haystack1 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(colorTable.data() + 0));
const auto haystack2 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(colorTable.data() + 4));
const auto needle = _mm_set1_epi32(__builtin_bit_cast(int, defaultColor));
const auto result1 = _mm_cmpeq_epi32(haystack1, needle);
const auto result2 = _mm_cmpeq_epi32(haystack2, needle);
const auto result = _mm_packs_epi32(result1, result2); // 3.5
const auto mask = _mm_movemask_epi8(result);
unsigned long index;
return _BitScanForward(&index, mask) ? til::at(colorTable, static_cast<size_t>(index / 2) + 8) : defaultColor;
#else
for (size_t i = 0; i < 8; i++) for (size_t i = 0; i < 8; i++)
{ {
if (til::at(colorTable, i) == defaultColor) if (til::at(colorTable, i) == defaultColor)
@ -163,6 +218,8 @@ COLORREF TextColor::GetColor(gsl::span<const COLORREF> colorTable,
return til::at(colorTable, i + 8); return til::at(colorTable, i + 8);
} }
} }
#endif
#pragma warning(pop)
} }
return defaultColor; return defaultColor;
@ -199,7 +256,7 @@ BYTE TextColor::GetLegacyIndex(const BYTE defaultIndex) const noexcept
} }
else if (IsIndex256()) else if (IsIndex256())
{ {
return Index256ToIndex16.at(GetIndex()); return til::at(Index256ToIndex16, GetIndex());
} }
else else
{ {
@ -208,7 +265,7 @@ BYTE TextColor::GetLegacyIndex(const BYTE defaultIndex) const noexcept
const BYTE compressedRgb = (_red & 0b11100000) + const BYTE compressedRgb = (_red & 0b11100000) +
((_green >> 3) & 0b00011100) + ((_green >> 3) & 0b00011100) +
((_blue >> 6) & 0b00000011); ((_blue >> 6) & 0b00000011);
return CompressedRgbToIndex16.at(compressedRgb); return til::at(CompressedRgbToIndex16, compressedRgb);
} }
} }

View file

@ -86,10 +86,7 @@ public:
void SetIndex(const BYTE index, const bool isIndex256) noexcept; void SetIndex(const BYTE index, const bool isIndex256) noexcept;
void SetDefault() noexcept; void SetDefault() noexcept;
COLORREF GetColor(gsl::span<const COLORREF> colorTable, COLORREF GetColor(const std::array<COLORREF, 256>& colorTable, const COLORREF defaultColor, bool brighten = false) const noexcept;
const COLORREF defaultColor,
const bool brighten = false) const noexcept;
BYTE GetLegacyIndex(const BYTE defaultIndex) const noexcept; BYTE GetLegacyIndex(const BYTE defaultIndex) const noexcept;
constexpr BYTE GetIndex() const noexcept constexpr BYTE GetIndex() const noexcept
@ -157,5 +154,3 @@ namespace WEX
} }
} }
#endif #endif
static_assert(sizeof(TextColor) <= 4 * sizeof(BYTE), "We should only need 4B for an entire TextColor. Any more than that is just waste");

View file

@ -315,6 +315,11 @@ void Cursor::StartDeferDrawing() noexcept
_fDeferCursorRedraw = true; _fDeferCursorRedraw = true;
} }
bool Cursor::IsDeferDrawing() noexcept
{
return _fDeferCursorRedraw;
}
void Cursor::EndDeferDrawing() noexcept void Cursor::EndDeferDrawing() noexcept
{ {
if (_fHaveDeferredCursorRedraw) if (_fHaveDeferredCursorRedraw)

View file

@ -55,6 +55,7 @@ public:
const COLORREF GetColor() const noexcept; const COLORREF GetColor() const noexcept;
void StartDeferDrawing() noexcept; void StartDeferDrawing() noexcept;
bool IsDeferDrawing() noexcept;
void EndDeferDrawing() noexcept; void EndDeferDrawing() noexcept;
void SetHasMoved(const bool fHasMoved) noexcept; void SetHasMoved(const bool fHasMoved) noexcept;

View file

@ -44,7 +44,7 @@
<ClInclude Include="..\Row.hpp" /> <ClInclude Include="..\Row.hpp" />
<ClInclude Include="..\search.h" /> <ClInclude Include="..\search.h" />
<ClInclude Include="..\TextColor.h" /> <ClInclude Include="..\TextColor.h" />
<ClInclude Include="..\TextAttribute.h" /> <ClInclude Include="..\TextAttribute.hpp" />
<ClInclude Include="..\textBuffer.hpp" /> <ClInclude Include="..\textBuffer.hpp" />
<ClInclude Include="..\textBufferCellIterator.hpp" /> <ClInclude Include="..\textBufferCellIterator.hpp" />
<ClInclude Include="..\textBufferTextIterator.hpp" /> <ClInclude Include="..\textBufferTextIterator.hpp" />

View file

@ -418,7 +418,6 @@ bool TextBuffer::InsertCharacter(const std::wstring_view chars,
// Store character and double byte data // Store character and double byte data
CharRow& charRow = Row.GetCharRow(); CharRow& charRow = Row.GetCharRow();
short const cBufferWidth = GetSize().Width();
try try
{ {
@ -1650,13 +1649,14 @@ const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF,
if (!cell.DbcsAttr().IsTrailing()) if (!cell.DbcsAttr().IsTrailing())
{ {
selectionText.append(cell.Chars()); const auto chars = cell.Chars();
selectionText.append(chars);
if (copyTextColor) if (copyTextColor)
{ {
const auto cellData = cell.TextAttr(); const auto cellData = cell.TextAttr();
const auto [CellFgAttr, CellBkAttr] = GetAttributeColors(cellData); const auto [CellFgAttr, CellBkAttr] = GetAttributeColors(cellData);
for (const wchar_t wch : cell.Chars()) for (size_t j = 0; j < chars.size(); ++j)
{ {
selectionFgAttr.push_back(CellFgAttr); selectionFgAttr.push_back(CellFgAttr);
selectionBkAttr.push_back(CellBkAttr); selectionBkAttr.push_back(CellBkAttr);

View file

@ -94,20 +94,93 @@ bool TextBufferCellIterator::operator!=(const TextBufferCellIterator& it) const
// - Reference to self after movement. // - Reference to self after movement.
TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& movement) TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& movement)
{ {
// Note that this method is called intensively when the terminal is under heavy load.
// We need to aggressively optimize it, comparing to the -= operator.
ptrdiff_t move = movement; ptrdiff_t move = movement;
auto newPos = _pos; if (move < 0)
while (move > 0 && !_exceeded)
{ {
_exceeded = !_bounds.IncrementInBounds(newPos); // Early branching to leave the rare case to -= operator.
// This helps reducing the instruction count within this method, which is good for instruction cache.
return *this -= -move;
}
// The remaining code in this function is functionally equivalent to:
// auto newPos = _pos;
// while (move > 0 && !_exceeded)
// {
// _exceeded = !_bounds.IncrementInBounds(newPos);
// move--;
// }
// _SetPos(newPos);
//
// _SetPos() necessitates calling _GenerateView() and thus the construction
// of a new OutputCellView(). This has a high performance impact (ICache spill?).
// The code below inlines _bounds.IncrementInBounds as well as SetPos.
// In the hot path (_pos.Y doesn't change) we modify the _view directly.
// Hoist these integers which will be used frequently later.
const auto boundsRightInclusive = _bounds.RightInclusive();
const auto boundsLeft = _bounds.Left();
const auto boundsBottomInclusive = _bounds.BottomInclusive();
const auto boundsTop = _bounds.Top();
const auto oldX = _pos.X;
const auto oldY = _pos.Y;
// Under MSVC writing the individual members of a COORD generates worse assembly
// compared to having them be local variables. This causes a performance impact.
auto newX = oldX;
auto newY = oldY;
while (move > 0)
{
if (newX == boundsRightInclusive)
{
newX = boundsLeft;
newY++;
if (newY > boundsBottomInclusive)
{
newY = boundsTop;
_exceeded = true;
break;
}
}
else
{
newX++;
_exceeded = false;
}
move--; move--;
} }
while (move < 0 && !_exceeded)
if (_exceeded)
{ {
_exceeded = !_bounds.DecrementInBounds(newPos); // Early return because nothing needs to be done here.
move++; return *this;
} }
_SetPos(newPos);
return (*this); if (newY == oldY)
{
// hot path
const auto diff = gsl::narrow_cast<ptrdiff_t>(newX) - gsl::narrow_cast<ptrdiff_t>(oldX);
_attrIter += diff;
_view.UpdateTextAttribute(*_attrIter);
const CharRow& charRow = _pRow->GetCharRow();
_view.UpdateText(charRow.GlyphAt(newX));
_view.UpdateDbcsAttribute(charRow.DbcsAttrAt(newX));
_pos.X = newX;
}
else
{
// cold path (_GenerateView is slow)
_pRow = s_GetRow(_buffer, { newX, newY });
_attrIter = _pRow->GetAttrRow().cbegin() + newX;
_pos.X = newX;
_pos.Y = newY;
_GenerateView();
}
return *this;
} }
// Routine Description: // Routine Description:
@ -118,7 +191,22 @@ TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& move
// - Reference to self after movement. // - Reference to self after movement.
TextBufferCellIterator& TextBufferCellIterator::operator-=(const ptrdiff_t& movement) TextBufferCellIterator& TextBufferCellIterator::operator-=(const ptrdiff_t& movement)
{ {
return this->operator+=(-movement); ptrdiff_t move = movement;
if (move < 0)
{
return (*this) += (-move);
}
auto newPos = _pos;
while (move > 0 && !_exceeded)
{
_exceeded = !_bounds.DecrementInBounds(newPos);
move--;
}
_SetPos(newPos);
_GenerateView();
return (*this);
} }
// Routine Description: // Routine Description:

View file

@ -22,12 +22,11 @@ class TextAttributeTests
TEST_METHOD(TestTextAttributeColorGetters); TEST_METHOD(TestTextAttributeColorGetters);
TEST_METHOD(TestReverseDefaultColors); TEST_METHOD(TestReverseDefaultColors);
TEST_METHOD(TestRoundtripDefaultColors); TEST_METHOD(TestRoundtripDefaultColors);
TEST_METHOD(TestBoldAsBright);
static const int COLOR_TABLE_SIZE = 16; std::array<COLORREF, 256> _colorTable;
COLORREF _colorTable[COLOR_TABLE_SIZE];
COLORREF _defaultFg = RGB(1, 2, 3); COLORREF _defaultFg = RGB(1, 2, 3);
COLORREF _defaultBg = RGB(4, 5, 6); COLORREF _defaultBg = RGB(4, 5, 6);
gsl::span<const COLORREF> _GetTableView();
}; };
bool TextAttributeTests::ClassSetup() bool TextAttributeTests::ClassSetup()
@ -51,11 +50,6 @@ bool TextAttributeTests::ClassSetup()
return true; return true;
} }
gsl::span<const COLORREF> TextAttributeTests::_GetTableView()
{
return gsl::span<const COLORREF>(&_colorTable[0], COLOR_TABLE_SIZE);
}
void TextAttributeTests::TestRoundtripLegacy() void TextAttributeTests::TestRoundtripLegacy()
{ {
WORD expectedLegacy = FOREGROUND_BLUE | BACKGROUND_RED; WORD expectedLegacy = FOREGROUND_BLUE | BACKGROUND_RED;
@ -133,23 +127,22 @@ void TextAttributeTests::TestTextAttributeColorGetters()
const COLORREF faintRed = RGB(127, 0, 0); const COLORREF faintRed = RGB(127, 0, 0);
const COLORREF green = RGB(0, 255, 0); const COLORREF green = RGB(0, 255, 0);
TextAttribute attr(red, green); TextAttribute attr(red, green);
auto view = _GetTableView();
// verify that calculated foreground/background are the same as the direct // verify that calculated foreground/background are the same as the direct
// values when reverse video is not set // values when reverse video is not set
VERIFY_IS_FALSE(attr.IsReverseVideo()); VERIFY_IS_FALSE(attr.IsReverseVideo());
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg)); VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg)); VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(red, green), attr.CalculateRgbColors(view, _defaultFg, _defaultBg)); VERIFY_ARE_EQUAL(std::make_pair(red, green), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg));
// with reverse video set, calculated foreground/background values should be // with reverse video set, calculated foreground/background values should be
// switched while getters stay the same // switched while getters stay the same
attr.SetReverseVideo(true); attr.SetReverseVideo(true);
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg)); VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg)); VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(green, red), attr.CalculateRgbColors(view, _defaultFg, _defaultBg)); VERIFY_ARE_EQUAL(std::make_pair(green, red), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg));
// reset the reverse video // reset the reverse video
attr.SetReverseVideo(false); attr.SetReverseVideo(false);
@ -158,17 +151,17 @@ void TextAttributeTests::TestTextAttributeColorGetters()
// while the background and getters stay the same // while the background and getters stay the same
attr.SetFaint(true); attr.SetFaint(true);
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg)); VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg)); VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(faintRed, green), attr.CalculateRgbColors(view, _defaultFg, _defaultBg)); VERIFY_ARE_EQUAL(std::make_pair(faintRed, green), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg));
// with reverse video set, calculated foreground/background values should be // with reverse video set, calculated foreground/background values should be
// switched, and the background fainter, while getters stay the same // switched, and the background fainter, while getters stay the same
attr.SetReverseVideo(true); attr.SetReverseVideo(true);
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg)); VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg)); VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(green, faintRed), attr.CalculateRgbColors(view, _defaultFg, _defaultBg)); VERIFY_ARE_EQUAL(std::make_pair(green, faintRed), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg));
// reset the reverse video and faint attributes // reset the reverse video and faint attributes
attr.SetReverseVideo(false); attr.SetReverseVideo(false);
@ -178,17 +171,17 @@ void TextAttributeTests::TestTextAttributeColorGetters()
// background, while getters stay the same // background, while getters stay the same
attr.SetInvisible(true); attr.SetInvisible(true);
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg)); VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg)); VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(green, green), attr.CalculateRgbColors(view, _defaultFg, _defaultBg)); VERIFY_ARE_EQUAL(std::make_pair(green, green), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg));
// with reverse video set, the calculated background value should match // with reverse video set, the calculated background value should match
// the foreground, while getters stay the same // the foreground, while getters stay the same
attr.SetReverseVideo(true); attr.SetReverseVideo(true);
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg)); VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg)); VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(red, red), attr.CalculateRgbColors(view, _defaultFg, _defaultBg)); VERIFY_ARE_EQUAL(std::make_pair(red, red), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg));
} }
void TextAttributeTests::TestReverseDefaultColors() void TextAttributeTests::TestReverseDefaultColors()
@ -196,40 +189,39 @@ void TextAttributeTests::TestReverseDefaultColors()
const COLORREF red = RGB(255, 0, 0); const COLORREF red = RGB(255, 0, 0);
const COLORREF green = RGB(0, 255, 0); const COLORREF green = RGB(0, 255, 0);
TextAttribute attr{}; TextAttribute attr{};
auto view = _GetTableView();
// verify that calculated foreground/background are the same as the direct // verify that calculated foreground/background are the same as the direct
// values when reverse video is not set // values when reverse video is not set
VERIFY_IS_FALSE(attr.IsReverseVideo()); VERIFY_IS_FALSE(attr.IsReverseVideo());
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(view, _defaultFg)); VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(view, _defaultBg)); VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(view, _defaultFg, _defaultBg)); VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg));
// with reverse video set, calculated foreground/background values should be // with reverse video set, calculated foreground/background values should be
// switched while getters stay the same // switched while getters stay the same
attr.SetReverseVideo(true); attr.SetReverseVideo(true);
VERIFY_IS_TRUE(attr.IsReverseVideo()); VERIFY_IS_TRUE(attr.IsReverseVideo());
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(view, _defaultFg)); VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(view, _defaultBg)); VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(_defaultBg, _defaultFg), attr.CalculateRgbColors(view, _defaultFg, _defaultBg)); VERIFY_ARE_EQUAL(std::make_pair(_defaultBg, _defaultFg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg));
attr.SetForeground(red); attr.SetForeground(red);
VERIFY_IS_TRUE(attr.IsReverseVideo()); VERIFY_IS_TRUE(attr.IsReverseVideo());
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg)); VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(view, _defaultBg)); VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(_defaultBg, red), attr.CalculateRgbColors(view, _defaultFg, _defaultBg)); VERIFY_ARE_EQUAL(std::make_pair(_defaultBg, red), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg));
attr.Invert(); attr.Invert();
VERIFY_IS_FALSE(attr.IsReverseVideo()); VERIFY_IS_FALSE(attr.IsReverseVideo());
attr.SetDefaultForeground(); attr.SetDefaultForeground();
attr.SetBackground(green); attr.SetBackground(green);
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(view, _defaultFg)); VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg)); VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, green), attr.CalculateRgbColors(view, _defaultFg, _defaultBg)); VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, green), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg));
} }
void TextAttributeTests::TestRoundtripDefaultColors() void TextAttributeTests::TestRoundtripDefaultColors()
@ -272,3 +264,56 @@ void TextAttributeTests::TestRoundtripDefaultColors()
// Reset the legacy default colors to white on black. // Reset the legacy default colors to white on black.
TextAttribute::SetLegacyDefaultAttributes(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); TextAttribute::SetLegacyDefaultAttributes(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
} }
void TextAttributeTests::TestBoldAsBright()
{
const COLORREF darkBlack = til::at(_colorTable, 0);
const COLORREF brightBlack = til::at(_colorTable, 8);
const COLORREF darkGreen = til::at(_colorTable, 2);
TextAttribute attr{};
// verify that calculated foreground/background are the same as the direct
// values when not bold
VERIFY_IS_FALSE(attr.IsBold());
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(_colorTable, _defaultFg));
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(_colorTable, _defaultBg));
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
// with bold set, calculated foreground/background values shouldn't change for the default colors.
attr.SetBold(true);
VERIFY_IS_TRUE(attr.IsBold());
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
attr.SetIndexedForeground(0);
VERIFY_IS_TRUE(attr.IsBold());
Log::Comment(L"Foreground should be bright black when bold is bright is enabled");
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
Log::Comment(L"Foreground should be dark black when bold is bright is disabled");
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
attr.SetIndexedBackground(2);
VERIFY_IS_TRUE(attr.IsBold());
Log::Comment(L"background should be unaffected by 'bold is bright'");
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
attr.SetBold(false);
VERIFY_IS_FALSE(attr.IsBold());
Log::Comment(L"when not bold, 'bold is bright' changes nothing");
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
VERIFY_ARE_EQUAL(std::make_pair(darkBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
Log::Comment(L"When set to a bright color, and bold, 'bold is bright' changes nothing");
attr.SetBold(true);
attr.SetIndexedForeground(8);
VERIFY_IS_TRUE(attr.IsBold());
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true));
VERIFY_ARE_EQUAL(std::make_pair(brightBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false));
}

View file

@ -23,11 +23,9 @@ class TextColorTests
TEST_METHOD(TestRgbColor); TEST_METHOD(TestRgbColor);
TEST_METHOD(TestChangeColor); TEST_METHOD(TestChangeColor);
static const int COLOR_TABLE_SIZE = 16; std::array<COLORREF, 256> _colorTable;
COLORREF _colorTable[COLOR_TABLE_SIZE];
COLORREF _defaultFg = RGB(1, 2, 3); COLORREF _defaultFg = RGB(1, 2, 3);
COLORREF _defaultBg = RGB(4, 5, 6); COLORREF _defaultBg = RGB(4, 5, 6);
gsl::span<const COLORREF> _GetTableView();
}; };
bool TextColorTests::ClassSetup() bool TextColorTests::ClassSetup()
@ -51,11 +49,6 @@ bool TextColorTests::ClassSetup()
return true; return true;
} }
gsl::span<const COLORREF> TextColorTests::_GetTableView()
{
return gsl::span<const COLORREF>(&_colorTable[0], COLOR_TABLE_SIZE);
}
void TextColorTests::TestDefaultColor() void TextColorTests::TestDefaultColor()
{ {
TextColor defaultColor; TextColor defaultColor;
@ -64,18 +57,16 @@ void TextColorTests::TestDefaultColor()
VERIFY_IS_FALSE(defaultColor.IsLegacy()); VERIFY_IS_FALSE(defaultColor.IsLegacy());
VERIFY_IS_FALSE(defaultColor.IsRgb()); VERIFY_IS_FALSE(defaultColor.IsRgb());
auto view = _GetTableView(); auto color = defaultColor.GetColor(_colorTable, _defaultFg, false);
auto color = defaultColor.GetColor(view, _defaultFg, false);
VERIFY_ARE_EQUAL(_defaultFg, color); VERIFY_ARE_EQUAL(_defaultFg, color);
color = defaultColor.GetColor(view, _defaultFg, true); color = defaultColor.GetColor(_colorTable, _defaultFg, true);
VERIFY_ARE_EQUAL(_defaultFg, color); VERIFY_ARE_EQUAL(_defaultFg, color);
color = defaultColor.GetColor(view, _defaultBg, false); color = defaultColor.GetColor(_colorTable, _defaultBg, false);
VERIFY_ARE_EQUAL(_defaultBg, color); VERIFY_ARE_EQUAL(_defaultBg, color);
color = defaultColor.GetColor(view, _defaultBg, true); color = defaultColor.GetColor(_colorTable, _defaultBg, true);
VERIFY_ARE_EQUAL(_defaultBg, color); VERIFY_ARE_EQUAL(_defaultBg, color);
} }
@ -87,18 +78,16 @@ void TextColorTests::TestDarkIndexColor()
VERIFY_IS_TRUE(indexColor.IsLegacy()); VERIFY_IS_TRUE(indexColor.IsLegacy());
VERIFY_IS_FALSE(indexColor.IsRgb()); VERIFY_IS_FALSE(indexColor.IsRgb());
auto view = _GetTableView(); auto color = indexColor.GetColor(_colorTable, _defaultFg, false);
auto color = indexColor.GetColor(view, _defaultFg, false);
VERIFY_ARE_EQUAL(_colorTable[7], color); VERIFY_ARE_EQUAL(_colorTable[7], color);
color = indexColor.GetColor(view, _defaultFg, true); color = indexColor.GetColor(_colorTable, _defaultFg, true);
VERIFY_ARE_EQUAL(_colorTable[15], color); VERIFY_ARE_EQUAL(_colorTable[15], color);
color = indexColor.GetColor(view, _defaultBg, false); color = indexColor.GetColor(_colorTable, _defaultBg, false);
VERIFY_ARE_EQUAL(_colorTable[7], color); VERIFY_ARE_EQUAL(_colorTable[7], color);
color = indexColor.GetColor(view, _defaultBg, true); color = indexColor.GetColor(_colorTable, _defaultBg, true);
VERIFY_ARE_EQUAL(_colorTable[15], color); VERIFY_ARE_EQUAL(_colorTable[15], color);
} }
@ -110,18 +99,16 @@ void TextColorTests::TestBrightIndexColor()
VERIFY_IS_TRUE(indexColor.IsLegacy()); VERIFY_IS_TRUE(indexColor.IsLegacy());
VERIFY_IS_FALSE(indexColor.IsRgb()); VERIFY_IS_FALSE(indexColor.IsRgb());
auto view = _GetTableView(); auto color = indexColor.GetColor(_colorTable, _defaultFg, false);
auto color = indexColor.GetColor(view, _defaultFg, false);
VERIFY_ARE_EQUAL(_colorTable[15], color); VERIFY_ARE_EQUAL(_colorTable[15], color);
color = indexColor.GetColor(view, _defaultFg, true); color = indexColor.GetColor(_colorTable, _defaultFg, true);
VERIFY_ARE_EQUAL(_colorTable[15], color); VERIFY_ARE_EQUAL(_colorTable[15], color);
color = indexColor.GetColor(view, _defaultBg, false); color = indexColor.GetColor(_colorTable, _defaultBg, false);
VERIFY_ARE_EQUAL(_colorTable[15], color); VERIFY_ARE_EQUAL(_colorTable[15], color);
color = indexColor.GetColor(view, _defaultBg, true); color = indexColor.GetColor(_colorTable, _defaultBg, true);
VERIFY_ARE_EQUAL(_colorTable[15], color); VERIFY_ARE_EQUAL(_colorTable[15], color);
} }
@ -134,18 +121,16 @@ void TextColorTests::TestRgbColor()
VERIFY_IS_FALSE(rgbColor.IsLegacy()); VERIFY_IS_FALSE(rgbColor.IsLegacy());
VERIFY_IS_TRUE(rgbColor.IsRgb()); VERIFY_IS_TRUE(rgbColor.IsRgb());
auto view = _GetTableView(); auto color = rgbColor.GetColor(_colorTable, _defaultFg, false);
auto color = rgbColor.GetColor(view, _defaultFg, false);
VERIFY_ARE_EQUAL(myColor, color); VERIFY_ARE_EQUAL(myColor, color);
color = rgbColor.GetColor(view, _defaultFg, true); color = rgbColor.GetColor(_colorTable, _defaultFg, true);
VERIFY_ARE_EQUAL(myColor, color); VERIFY_ARE_EQUAL(myColor, color);
color = rgbColor.GetColor(view, _defaultBg, false); color = rgbColor.GetColor(_colorTable, _defaultBg, false);
VERIFY_ARE_EQUAL(myColor, color); VERIFY_ARE_EQUAL(myColor, color);
color = rgbColor.GetColor(view, _defaultBg, true); color = rgbColor.GetColor(_colorTable, _defaultBg, true);
VERIFY_ARE_EQUAL(myColor, color); VERIFY_ARE_EQUAL(myColor, color);
} }
@ -158,57 +143,55 @@ void TextColorTests::TestChangeColor()
VERIFY_IS_FALSE(rgbColor.IsLegacy()); VERIFY_IS_FALSE(rgbColor.IsLegacy());
VERIFY_IS_TRUE(rgbColor.IsRgb()); VERIFY_IS_TRUE(rgbColor.IsRgb());
auto view = _GetTableView(); auto color = rgbColor.GetColor(_colorTable, _defaultFg, false);
auto color = rgbColor.GetColor(view, _defaultFg, false);
VERIFY_ARE_EQUAL(myColor, color); VERIFY_ARE_EQUAL(myColor, color);
color = rgbColor.GetColor(view, _defaultFg, true); color = rgbColor.GetColor(_colorTable, _defaultFg, true);
VERIFY_ARE_EQUAL(myColor, color); VERIFY_ARE_EQUAL(myColor, color);
color = rgbColor.GetColor(view, _defaultBg, false); color = rgbColor.GetColor(_colorTable, _defaultBg, false);
VERIFY_ARE_EQUAL(myColor, color); VERIFY_ARE_EQUAL(myColor, color);
color = rgbColor.GetColor(view, _defaultBg, true); color = rgbColor.GetColor(_colorTable, _defaultBg, true);
VERIFY_ARE_EQUAL(myColor, color); VERIFY_ARE_EQUAL(myColor, color);
rgbColor.SetDefault(); rgbColor.SetDefault();
color = rgbColor.GetColor(view, _defaultFg, false); color = rgbColor.GetColor(_colorTable, _defaultFg, false);
VERIFY_ARE_EQUAL(_defaultFg, color); VERIFY_ARE_EQUAL(_defaultFg, color);
color = rgbColor.GetColor(view, _defaultFg, true); color = rgbColor.GetColor(_colorTable, _defaultFg, true);
VERIFY_ARE_EQUAL(_defaultFg, color); VERIFY_ARE_EQUAL(_defaultFg, color);
color = rgbColor.GetColor(view, _defaultBg, false); color = rgbColor.GetColor(_colorTable, _defaultBg, false);
VERIFY_ARE_EQUAL(_defaultBg, color); VERIFY_ARE_EQUAL(_defaultBg, color);
color = rgbColor.GetColor(view, _defaultBg, true); color = rgbColor.GetColor(_colorTable, _defaultBg, true);
VERIFY_ARE_EQUAL(_defaultBg, color); VERIFY_ARE_EQUAL(_defaultBg, color);
rgbColor.SetIndex(7, false); rgbColor.SetIndex(7, false);
color = rgbColor.GetColor(view, _defaultFg, false); color = rgbColor.GetColor(_colorTable, _defaultFg, false);
VERIFY_ARE_EQUAL(_colorTable[7], color); VERIFY_ARE_EQUAL(_colorTable[7], color);
color = rgbColor.GetColor(view, _defaultFg, true); color = rgbColor.GetColor(_colorTable, _defaultFg, true);
VERIFY_ARE_EQUAL(_colorTable[15], color); VERIFY_ARE_EQUAL(_colorTable[15], color);
color = rgbColor.GetColor(view, _defaultBg, false); color = rgbColor.GetColor(_colorTable, _defaultBg, false);
VERIFY_ARE_EQUAL(_colorTable[7], color); VERIFY_ARE_EQUAL(_colorTable[7], color);
color = rgbColor.GetColor(view, _defaultBg, true); color = rgbColor.GetColor(_colorTable, _defaultBg, true);
VERIFY_ARE_EQUAL(_colorTable[15], color); VERIFY_ARE_EQUAL(_colorTable[15], color);
rgbColor.SetIndex(15, false); rgbColor.SetIndex(15, false);
color = rgbColor.GetColor(view, _defaultFg, false); color = rgbColor.GetColor(_colorTable, _defaultFg, false);
VERIFY_ARE_EQUAL(_colorTable[15], color); VERIFY_ARE_EQUAL(_colorTable[15], color);
color = rgbColor.GetColor(view, _defaultFg, true); color = rgbColor.GetColor(_colorTable, _defaultFg, true);
VERIFY_ARE_EQUAL(_colorTable[15], color); VERIFY_ARE_EQUAL(_colorTable[15], color);
color = rgbColor.GetColor(view, _defaultBg, false); color = rgbColor.GetColor(_colorTable, _defaultBg, false);
VERIFY_ARE_EQUAL(_colorTable[15], color); VERIFY_ARE_EQUAL(_colorTable[15], color);
color = rgbColor.GetColor(view, _defaultBg, true); color = rgbColor.GetColor(_colorTable, _defaultBg, true);
VERIFY_ARE_EQUAL(_colorTable[15], color); VERIFY_ARE_EQUAL(_colorTable[15], color);
} }

View file

@ -146,12 +146,12 @@
<!-- **END VC LIBS HACK** --> <!-- **END VC LIBS HACK** -->
<!-- This is required to get the package dependency in the AppXManifest. --> <!-- This is required to get the package dependency in the AppXManifest. -->
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" /> <Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <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> <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> </PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets'))" /> <Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets'))" />
</Target> </Target>
<Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" /> <Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" />

View file

@ -397,6 +397,10 @@ namespace SettingsModelLocalTests
"name":"action6", "name":"action6",
"command": { "action": "newWindow", "startingDirectory":"C:\\foo", "commandline": "bar.exe" } "command": { "action": "newWindow", "startingDirectory":"C:\\foo", "commandline": "bar.exe" }
}, },
{
"name":"action7_startingDirectoryWithTrailingSlash",
"command": { "action": "newWindow", "startingDirectory":"C:\\", "commandline": "bar.exe" }
},
])" }; ])" };
const auto commands0Json = VerifyParseSucceeded(commands0String); const auto commands0Json = VerifyParseSucceeded(commands0String);
@ -405,7 +409,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(0u, commands.Size()); VERIFY_ARE_EQUAL(0u, commands.Size());
auto warnings = implementation::Command::LayerJson(commands, commands0Json); auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size()); VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(7u, commands.Size()); VERIFY_ARE_EQUAL(8u, commands.Size());
{ {
auto command = commands.Lookup(L"action0"); auto command = commands.Lookup(L"action0");
@ -503,5 +507,20 @@ namespace SettingsModelLocalTests
L"cmdline: \"%s\"", cmdline.c_str())); L"cmdline: \"%s\"", cmdline.c_str()));
VERIFY_ARE_EQUAL(L"--startingDirectory \"C:\\foo\" -- \"bar.exe\"", terminalArgs.ToCommandline()); VERIFY_ARE_EQUAL(L"--startingDirectory \"C:\\foo\" -- \"bar.exe\"", terminalArgs.ToCommandline());
} }
{
auto command = commands.Lookup(L"action7_startingDirectoryWithTrailingSlash");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action());
const auto& realArgs = command.ActionAndArgs().Args().try_as<NewWindowArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& terminalArgs = realArgs.TerminalArgs();
VERIFY_IS_NOT_NULL(terminalArgs);
auto cmdline = terminalArgs.ToCommandline();
Log::Comment(NoThrowString().Format(
L"cmdline: \"%s\"", cmdline.c_str()));
VERIFY_ARE_EQUAL(L"--startingDirectory \"C:\\\\\" -- \"bar.exe\"", terminalArgs.ToCommandline());
}
} }
} }

View file

@ -1971,9 +1971,9 @@ namespace SettingsModelLocalTests
auto settings = implementation::CascadiaSettings::FromJson(settingsObject); auto settings = implementation::CascadiaSettings::FromJson(settingsObject);
VERIFY_ARE_EQUAL(3u, settings->_globals->_actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(3u, settings->_globals->_actionMap->_KeyMap.size());
VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('a') })); VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('A'), 0 }));
VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('b') })); VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('B'), 0 }));
VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') })); VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
for (const auto& warning : settings->_globals->_keybindingsWarnings) for (const auto& warning : settings->_globals->_keybindingsWarnings)
{ {
@ -2124,7 +2124,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(1u, nameMap.Size()); VERIFY_ARE_EQUAL(1u, nameMap.Size());
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('A') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('A'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>(); const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@ -2141,7 +2141,7 @@ namespace SettingsModelLocalTests
Log::Comment(L"Note that we're skipping ctrl+B, since that doesn't have `keys` set."); Log::Comment(L"Note that we're skipping ctrl+B, since that doesn't have `keys` set.");
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('C') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>(); const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@ -2155,7 +2155,7 @@ namespace SettingsModelLocalTests
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('D') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('D'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>(); const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@ -2169,7 +2169,7 @@ namespace SettingsModelLocalTests
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('E') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('E'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>(); const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@ -2183,7 +2183,7 @@ namespace SettingsModelLocalTests
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('F') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('F'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>(); const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@ -2841,7 +2841,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); VERIFY_ARE_EQUAL(0u, settings->_warnings.Size());
VERIFY_ARE_EQUAL(1u, nameMap.Size()); VERIFY_ARE_EQUAL(1u, nameMap.Size());
const KeyChord expectedKeyChord{ true, false, true, static_cast<int>('W') }; const KeyChord expectedKeyChord{ true, false, true, false, static_cast<int>('W'), 0 };
{ {
// Verify NameMap returns correct value // Verify NameMap returns correct value
const auto& cmd{ nameMap.TryLookup(L"foo") }; const auto& cmd{ nameMap.TryLookup(L"foo") };

View file

@ -36,10 +36,15 @@ namespace SettingsModelLocalTests
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml") TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS() END_TEST_CLASS()
TEST_METHOD(KeyChords);
TEST_METHOD(ManyKeysSameAction); TEST_METHOD(ManyKeysSameAction);
TEST_METHOD(LayerKeybindings); TEST_METHOD(LayerKeybindings);
TEST_METHOD(UnbindKeybindings); TEST_METHOD(UnbindKeybindings);
TEST_METHOD(LayerScancodeKeybindings);
TEST_METHOD(TestExplicitUnbind);
TEST_METHOD(TestArbitraryArgs); TEST_METHOD(TestArbitraryArgs);
TEST_METHOD(TestSplitPaneArgs); TEST_METHOD(TestSplitPaneArgs);
@ -53,6 +58,7 @@ namespace SettingsModelLocalTests
TEST_METHOD(TestMoveTabArgs); TEST_METHOD(TestMoveTabArgs);
TEST_METHOD(TestGetKeyBindingForAction); TEST_METHOD(TestGetKeyBindingForAction);
TEST_METHOD(KeybindingsWithoutVkey);
TEST_CLASS_SETUP(ClassSetup) TEST_CLASS_SETUP(ClassSetup)
{ {
@ -61,6 +67,69 @@ namespace SettingsModelLocalTests
} }
}; };
void KeyBindingsTests::KeyChords()
{
struct testCase
{
VirtualKeyModifiers modifiers;
int32_t vkey;
int32_t scanCode;
std::wstring_view expected;
};
static constexpr std::array testCases{
testCase{
VirtualKeyModifiers::None,
'A',
0,
L"a",
},
testCase{
VirtualKeyModifiers::Control,
'A',
0,
L"ctrl+a",
},
testCase{
VirtualKeyModifiers::Control | VirtualKeyModifiers::Shift,
VK_OEM_PLUS,
0,
L"ctrl+shift+plus",
},
testCase{
VirtualKeyModifiers::Control | VirtualKeyModifiers::Menu | VirtualKeyModifiers::Shift | VirtualKeyModifiers::Windows,
255,
0,
L"win+ctrl+alt+shift+vk(255)",
},
testCase{
VirtualKeyModifiers::Control | VirtualKeyModifiers::Menu | VirtualKeyModifiers::Shift | VirtualKeyModifiers::Windows,
0,
123,
L"win+ctrl+alt+shift+sc(123)",
},
};
for (const auto& tc : testCases)
{
Log::Comment(NoThrowString().Format(L"Testing case:\"%s\"", tc.expected.data()));
const auto actualString = KeyChordSerialization::ToString({ tc.modifiers, tc.vkey, tc.scanCode });
VERIFY_ARE_EQUAL(tc.expected, actualString);
auto expectedVkey = tc.vkey;
if (!expectedVkey)
{
expectedVkey = MapVirtualKeyW(tc.scanCode, MAPVK_VSC_TO_VK_EX);
}
const auto actualKeyChord = KeyChordSerialization::FromString(actualString);
VERIFY_ARE_EQUAL(tc.modifiers, actualKeyChord.Modifiers());
VERIFY_ARE_EQUAL(expectedVkey, actualKeyChord.Vkey());
VERIFY_ARE_EQUAL(tc.scanCode, actualKeyChord.ScanCode());
}
}
void KeyBindingsTests::ManyKeysSameAction() void KeyBindingsTests::ManyKeysSameAction()
{ {
const std::string bindings0String{ R"([ { "command": "copy", "keys": ["ctrl+c"] } ])" }; const std::string bindings0String{ R"([ { "command": "copy", "keys": ["ctrl+c"] } ])" };
@ -75,7 +144,6 @@ namespace SettingsModelLocalTests
const auto bindings2Json = VerifyParseSucceeded(bindings2String); const auto bindings2Json = VerifyParseSucceeded(bindings2String);
auto actionMap = winrt::make_self<implementation::ActionMap>(); auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json); actionMap->LayerJson(bindings0Json);
@ -99,7 +167,6 @@ namespace SettingsModelLocalTests
const auto bindings2Json = VerifyParseSucceeded(bindings2String); const auto bindings2Json = VerifyParseSucceeded(bindings2String);
auto actionMap = winrt::make_self<implementation::ActionMap>(); auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json); actionMap->LayerJson(bindings0Json);
@ -129,7 +196,6 @@ namespace SettingsModelLocalTests
const auto bindings5Json = VerifyParseSucceeded(bindings5String); const auto bindings5Json = VerifyParseSucceeded(bindings5String);
auto actionMap = winrt::make_self<implementation::ActionMap>(); auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json); actionMap->LayerJson(bindings0Json);
@ -142,7 +208,7 @@ namespace SettingsModelLocalTests
L"Try unbinding a key using `\"unbound\"` to unbind the key")); L"Try unbinding a key using `\"unbound\"` to unbind the key"));
actionMap->LayerJson(bindings2Json); actionMap->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') })); VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
Log::Comment(NoThrowString().Format( Log::Comment(NoThrowString().Format(
L"Try unbinding a key using `null` to unbind the key")); L"Try unbinding a key using `null` to unbind the key"));
@ -152,7 +218,7 @@ namespace SettingsModelLocalTests
// Then try layering in the bad setting // Then try layering in the bad setting
actionMap->LayerJson(bindings3Json); actionMap->LayerJson(bindings3Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') })); VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
Log::Comment(NoThrowString().Format( Log::Comment(NoThrowString().Format(
L"Try unbinding a key using an unrecognized command to unbind the key")); L"Try unbinding a key using an unrecognized command to unbind the key"));
@ -162,7 +228,7 @@ namespace SettingsModelLocalTests
// Then try layering in the bad setting // Then try layering in the bad setting
actionMap->LayerJson(bindings4Json); actionMap->LayerJson(bindings4Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') })); VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
Log::Comment(NoThrowString().Format( Log::Comment(NoThrowString().Format(
L"Try unbinding a key using a straight up invalid value to unbind the key")); L"Try unbinding a key using a straight up invalid value to unbind the key"));
@ -172,13 +238,38 @@ namespace SettingsModelLocalTests
// Then try layering in the bad setting // Then try layering in the bad setting
actionMap->LayerJson(bindings5Json); actionMap->LayerJson(bindings5Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') })); VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
Log::Comment(NoThrowString().Format( Log::Comment(NoThrowString().Format(
L"Try unbinding a key that wasn't bound at all")); L"Try unbinding a key that wasn't bound at all"));
actionMap->LayerJson(bindings2Json); actionMap->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('c') })); VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }));
}
void KeyBindingsTests::TestExplicitUnbind()
{
const std::string bindings0String{ R"([ { "command": "copy", "keys": ["ctrl+c"] } ])" };
const std::string bindings1String{ R"([ { "command": "unbound", "keys": ["ctrl+c"] } ])" };
const std::string bindings2String{ R"([ { "command": "copy", "keys": ["ctrl+c"] } ])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
const auto bindings1Json = VerifyParseSucceeded(bindings1String);
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
const KeyChord keyChord{ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 };
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord));
actionMap->LayerJson(bindings0Json);
VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord));
actionMap->LayerJson(bindings1Json);
VERIFY_IS_TRUE(actionMap->IsKeyChordExplicitlyUnbound(keyChord));
actionMap->LayerJson(bindings2Json);
VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord));
} }
void KeyBindingsTests::TestArbitraryArgs() void KeyBindingsTests::TestArbitraryArgs()
@ -203,7 +294,6 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String); const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>(); auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json); actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(10u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(10u, actionMap->_KeyMap.size());
@ -211,10 +301,9 @@ namespace SettingsModelLocalTests
{ {
Log::Comment(NoThrowString().Format( Log::Comment(NoThrowString().Format(
L"Verify that `copy` without args parses as Copy(SingleLine=false)")); L"Verify that `copy` without args parses as Copy(SingleLine=false)"));
KeyChord kc{ true, false, false, static_cast<int32_t>('C') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>(); const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.SingleLine()); VERIFY_IS_FALSE(realArgs.SingleLine());
} }
@ -222,10 +311,9 @@ namespace SettingsModelLocalTests
{ {
Log::Comment(NoThrowString().Format( Log::Comment(NoThrowString().Format(
L"Verify that `copy` with args parses them correctly")); L"Verify that `copy` with args parses them correctly"));
KeyChord kc{ true, false, true, static_cast<int32_t>('C') }; KeyChord kc{ true, false, true, false, static_cast<int32_t>('C'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>(); const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.SingleLine()); VERIFY_IS_FALSE(realArgs.SingleLine());
} }
@ -233,10 +321,9 @@ namespace SettingsModelLocalTests
{ {
Log::Comment(NoThrowString().Format( Log::Comment(NoThrowString().Format(
L"Verify that `copy` with args parses them correctly")); L"Verify that `copy` with args parses them correctly"));
KeyChord kc{ false, true, true, static_cast<int32_t>('C') }; KeyChord kc{ false, true, true, false, static_cast<int32_t>('C'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>(); const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_IS_TRUE(realArgs.SingleLine()); VERIFY_IS_TRUE(realArgs.SingleLine());
} }
@ -244,11 +331,10 @@ namespace SettingsModelLocalTests
{ {
Log::Comment(NoThrowString().Format( Log::Comment(NoThrowString().Format(
L"Verify that `newTab` without args parses as NewTab(Index=null)")); L"Verify that `newTab` without args parses as NewTab(Index=null)"));
KeyChord kc{ true, false, false, static_cast<int32_t>('T') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('T'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>(); const auto& realArgs = actionAndArgs.Args().as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_NULL(realArgs.TerminalArgs().ProfileIndex()); VERIFY_IS_NULL(realArgs.TerminalArgs().ProfileIndex());
@ -256,11 +342,10 @@ namespace SettingsModelLocalTests
{ {
Log::Comment(NoThrowString().Format( Log::Comment(NoThrowString().Format(
L"Verify that `newTab` parses args correctly")); L"Verify that `newTab` parses args correctly"));
KeyChord kc{ true, false, true, static_cast<int32_t>('T') }; KeyChord kc{ true, false, true, false, static_cast<int32_t>('T'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>(); const auto& realArgs = actionAndArgs.Args().as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().ProfileIndex()); VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().ProfileIndex());
@ -270,11 +355,10 @@ namespace SettingsModelLocalTests
Log::Comment(NoThrowString().Format( Log::Comment(NoThrowString().Format(
L"Verify that `newTab` with an index greater than the legacy " L"Verify that `newTab` with an index greater than the legacy "
L"args afforded parses correctly")); L"args afforded parses correctly"));
KeyChord kc{ true, false, true, static_cast<int32_t>('Y') }; KeyChord kc{ true, false, true, false, static_cast<int32_t>('Y'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>(); const auto& realArgs = actionAndArgs.Args().as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().ProfileIndex()); VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().ProfileIndex());
@ -284,11 +368,10 @@ namespace SettingsModelLocalTests
{ {
Log::Comment(NoThrowString().Format( Log::Comment(NoThrowString().Format(
L"Verify that `copy` ignores args it doesn't understand")); L"Verify that `copy` ignores args it doesn't understand"));
KeyChord kc{ true, false, true, static_cast<int32_t>('B') }; KeyChord kc{ true, false, true, false, static_cast<int32_t>('B'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>(); const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.SingleLine()); VERIFY_IS_FALSE(realArgs.SingleLine());
} }
@ -296,11 +379,10 @@ namespace SettingsModelLocalTests
{ {
Log::Comment(NoThrowString().Format( Log::Comment(NoThrowString().Format(
L"Verify that `copy` null as it's `args` parses as the default option")); L"Verify that `copy` null as it's `args` parses as the default option"));
KeyChord kc{ true, false, true, static_cast<int32_t>('B') }; KeyChord kc{ true, false, true, false, static_cast<int32_t>('B'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>(); const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.SingleLine()); VERIFY_IS_FALSE(realArgs.SingleLine());
} }
@ -308,11 +390,10 @@ namespace SettingsModelLocalTests
{ {
Log::Comment(NoThrowString().Format( Log::Comment(NoThrowString().Format(
L"Verify that `adjustFontSize` with a positive delta parses args correctly")); L"Verify that `adjustFontSize` with a positive delta parses args correctly"));
KeyChord kc{ true, false, false, static_cast<int32_t>('F') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('F'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<AdjustFontSizeArgs>(); const auto& realArgs = actionAndArgs.Args().as<AdjustFontSizeArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_ARE_EQUAL(1, realArgs.Delta()); VERIFY_ARE_EQUAL(1, realArgs.Delta());
} }
@ -320,11 +401,10 @@ namespace SettingsModelLocalTests
{ {
Log::Comment(NoThrowString().Format( Log::Comment(NoThrowString().Format(
L"Verify that `adjustFontSize` with a negative delta parses args correctly")); L"Verify that `adjustFontSize` with a negative delta parses args correctly"));
KeyChord kc{ true, false, false, static_cast<int32_t>('G') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('G'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<AdjustFontSizeArgs>(); const auto& realArgs = actionAndArgs.Args().as<AdjustFontSizeArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_ARE_EQUAL(-1, realArgs.Delta()); VERIFY_ARE_EQUAL(-1, realArgs.Delta());
} }
@ -342,44 +422,39 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String); const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>(); auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json); actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size());
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('D') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('D'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>(); const auto& realArgs = actionAndArgs.Args().as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle()); VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('E') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('E'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>(); const auto& realArgs = actionAndArgs.Args().as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle()); VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle());
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('G') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('G'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>(); const auto& realArgs = actionAndArgs.Args().as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle()); VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('H') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('H'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>(); const auto& realArgs = actionAndArgs.Args().as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle()); VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
} }
@ -396,37 +471,33 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String); const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>(); auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json); actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size());
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('C') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>(); const auto& realArgs = actionAndArgs.Args().as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_IS_NULL(realArgs.TabColor()); VERIFY_IS_NULL(realArgs.TabColor());
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('D') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('D'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>(); const auto& realArgs = actionAndArgs.Args().as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.TabColor()); VERIFY_IS_NOT_NULL(realArgs.TabColor());
// Remember that COLORREFs are actually BBGGRR order, while the string is in #RRGGBB order // Remember that COLORREFs are actually BBGGRR order, while the string is in #RRGGBB order
VERIFY_ARE_EQUAL(til::color(0x563412), til::color(realArgs.TabColor().Value())); VERIFY_ARE_EQUAL(til::color(0x563412), til::color(realArgs.TabColor().Value()));
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('F') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('F'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>(); const auto& realArgs = actionAndArgs.Args().as<SetTabColorArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_IS_NULL(realArgs.TabColor()); VERIFY_IS_NULL(realArgs.TabColor());
} }
@ -441,16 +512,14 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String); const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>(); auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json); actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('C') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>(); const auto& realArgs = actionAndArgs.Args().as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.SingleLine()); VERIFY_IS_FALSE(realArgs.SingleLine());
} }
@ -470,63 +539,56 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String); const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>(); auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json); actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(6u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(6u, actionMap->_KeyMap.size());
{ {
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_UP) }; KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ScrollUpArgs>(); const auto& realArgs = actionAndArgs.Args().as<ScrollUpArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_IS_NULL(realArgs.RowsToScroll()); VERIFY_IS_NULL(realArgs.RowsToScroll());
} }
{ {
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_DOWN) }; KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_DOWN), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ScrollDownArgs>(); const auto& realArgs = actionAndArgs.Args().as<ScrollDownArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_IS_NULL(realArgs.RowsToScroll()); VERIFY_IS_NULL(realArgs.RowsToScroll());
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>(VK_UP) }; KeyChord kc{ true, false, false, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ScrollUpArgs>(); const auto& realArgs = actionAndArgs.Args().as<ScrollUpArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_IS_NULL(realArgs.RowsToScroll()); VERIFY_IS_NULL(realArgs.RowsToScroll());
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>(VK_DOWN) }; KeyChord kc{ true, false, false, false, static_cast<int32_t>(VK_DOWN), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ScrollDownArgs>(); const auto& realArgs = actionAndArgs.Args().as<ScrollDownArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_IS_NULL(realArgs.RowsToScroll()); VERIFY_IS_NULL(realArgs.RowsToScroll());
} }
{ {
KeyChord kc{ true, false, true, static_cast<int32_t>(VK_UP) }; KeyChord kc{ true, false, true, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ScrollUpArgs>(); const auto& realArgs = actionAndArgs.Args().as<ScrollUpArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.RowsToScroll()); VERIFY_IS_NOT_NULL(realArgs.RowsToScroll());
VERIFY_ARE_EQUAL(10u, realArgs.RowsToScroll().Value()); VERIFY_ARE_EQUAL(10u, realArgs.RowsToScroll().Value());
} }
{ {
KeyChord kc{ true, false, true, static_cast<int32_t>(VK_DOWN) }; KeyChord kc{ true, false, true, false, static_cast<int32_t>(VK_DOWN), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ScrollDownArgs>(); const auto& realArgs = actionAndArgs.Args().as<ScrollDownArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.RowsToScroll()); VERIFY_IS_NOT_NULL(realArgs.RowsToScroll());
VERIFY_ARE_EQUAL(10u, realArgs.RowsToScroll().Value()); VERIFY_ARE_EQUAL(10u, realArgs.RowsToScroll().Value());
@ -535,7 +597,6 @@ namespace SettingsModelLocalTests
const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "scrollDown", "rowsToScroll": -1 } }])" }; const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "scrollDown", "rowsToScroll": -1 } }])" };
const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString); const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString);
auto invalidActionMap = winrt::make_self<implementation::ActionMap>(); auto invalidActionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(invalidActionMap);
VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size());
VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception); VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception);
} }
@ -551,26 +612,23 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String); const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>(); auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json); actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size());
{ {
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_UP) }; KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::MoveTab, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::MoveTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<MoveTabArgs>(); const auto& realArgs = actionAndArgs.Args().as<MoveTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_ARE_EQUAL(realArgs.Direction(), MoveTabDirection::Forward); VERIFY_ARE_EQUAL(realArgs.Direction(), MoveTabDirection::Forward);
} }
{ {
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_DOWN) }; KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_DOWN), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::MoveTab, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::MoveTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<MoveTabArgs>(); const auto& realArgs = actionAndArgs.Args().as<MoveTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_ARE_EQUAL(realArgs.Direction(), MoveTabDirection::Backward); VERIFY_ARE_EQUAL(realArgs.Direction(), MoveTabDirection::Backward);
} }
@ -584,7 +642,6 @@ namespace SettingsModelLocalTests
const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "moveTab", "direction": "bad" } }])" }; const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "moveTab", "direction": "bad" } }])" };
const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString); const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString);
auto invalidActionMap = winrt::make_self<implementation::ActionMap>(); auto invalidActionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(invalidActionMap);
VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size());
VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception); VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception);
} }
@ -601,35 +658,31 @@ namespace SettingsModelLocalTests
const auto bindings0Json = VerifyParseSucceeded(bindings0String); const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto actionMap = winrt::make_self<implementation::ActionMap>(); auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json); actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size());
{ {
KeyChord kc{ false, false, false, static_cast<int32_t>(VK_UP) }; KeyChord kc{ false, false, false, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ToggleCommandPaletteArgs>(); const auto& realArgs = actionAndArgs.Args().as<ToggleCommandPaletteArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_ARE_EQUAL(realArgs.LaunchMode(), CommandPaletteLaunchMode::Action); VERIFY_ARE_EQUAL(realArgs.LaunchMode(), CommandPaletteLaunchMode::Action);
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>(VK_UP) }; KeyChord kc{ true, false, false, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ToggleCommandPaletteArgs>(); const auto& realArgs = actionAndArgs.Args().as<ToggleCommandPaletteArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_ARE_EQUAL(realArgs.LaunchMode(), CommandPaletteLaunchMode::Action); VERIFY_ARE_EQUAL(realArgs.LaunchMode(), CommandPaletteLaunchMode::Action);
} }
{ {
KeyChord kc{ true, false, true, static_cast<int32_t>(VK_UP) }; KeyChord kc{ true, false, true, false, static_cast<int32_t>(VK_UP), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<ToggleCommandPaletteArgs>(); const auto& realArgs = actionAndArgs.Args().as<ToggleCommandPaletteArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value // Verify the args have the expected value
VERIFY_ARE_EQUAL(realArgs.LaunchMode(), CommandPaletteLaunchMode::CommandLine); VERIFY_ARE_EQUAL(realArgs.LaunchMode(), CommandPaletteLaunchMode::CommandLine);
} }
@ -637,7 +690,6 @@ namespace SettingsModelLocalTests
const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "commandPalette", "launchMode": "bad" } }])" }; const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "commandPalette", "launchMode": "bad" } }])" };
const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString); const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString);
auto invalidActionMap = winrt::make_self<implementation::ActionMap>(); auto invalidActionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(invalidActionMap);
VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size());
VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception); VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception);
} }
@ -669,7 +721,6 @@ namespace SettingsModelLocalTests
}; };
auto actionMap = winrt::make_self<implementation::ActionMap>(); auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_IS_NOT_NULL(actionMap);
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
{ {
@ -677,7 +728,7 @@ namespace SettingsModelLocalTests
actionMap->LayerJson(bindings0Json); actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CloseWindow) }; const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CloseWindow) };
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('A') }, kbd); VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('A'), 0 }, kbd);
} }
{ {
Log::Comment(L"command with args"); Log::Comment(L"command with args");
@ -688,7 +739,7 @@ namespace SettingsModelLocalTests
args->SingleLine(true); args->SingleLine(true);
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CopyText, *args) }; const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CopyText, *args) };
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('B') }, kbd); VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('B'), 0 }, kbd);
} }
{ {
Log::Comment(L"command with new terminal args"); Log::Comment(L"command with new terminal args");
@ -700,7 +751,7 @@ namespace SettingsModelLocalTests
auto args{ winrt::make_self<implementation::NewTabArgs>(*newTerminalArgs) }; auto args{ winrt::make_self<implementation::NewTabArgs>(*newTerminalArgs) };
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::NewTab, *args) }; const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::NewTab, *args) };
VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('C') }, kbd); VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast<int32_t>('C'), 0 }, kbd);
} }
{ {
Log::Comment(L"command with hidden args"); Log::Comment(L"command with hidden args");
@ -708,7 +759,45 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size());
const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::ToggleCommandPalette) }; const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::ToggleCommandPalette) };
VerifyKeyChordEquality({ VirtualKeyModifiers::Control | VirtualKeyModifiers::Shift, static_cast<int32_t>('P') }, kbd); VerifyKeyChordEquality({ VirtualKeyModifiers::Control | VirtualKeyModifiers::Shift, static_cast<int32_t>('P'), 0 }, kbd);
} }
} }
void KeyBindingsTests::LayerScancodeKeybindings()
{
Log::Comment(L"Layering a keybinding with a character literal on top of"
L" an equivalent sc() key should replace it.");
// Wrap the first one in `R"!(...)!"` because it has `()` internally.
const std::string bindings0String{ R"!([ { "command": "quakeMode", "keys":"win+sc(41)" } ])!" };
const std::string bindings1String{ R"([ { "keys": "win+`", "command": { "action": "globalSummon", "monitor": "any" } } ])" };
const std::string bindings2String{ R"([ { "keys": "ctrl+shift+`", "command": { "action": "quakeMode" } } ])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
const auto bindings1Json = VerifyParseSucceeded(bindings1String);
const auto bindings2Json = VerifyParseSucceeded(bindings2String);
auto actionMap = winrt::make_self<implementation::ActionMap>();
VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size());
actionMap->LayerJson(bindings1Json);
VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size(), L"Layering the second action should replace the first one.");
actionMap->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size());
}
void KeyBindingsTests::KeybindingsWithoutVkey()
{
const auto json = VerifyParseSucceeded(R"!([{"command": "quakeMode", "keys":"shift+sc(255)"}])!");
const auto actionMap = winrt::make_self<implementation::ActionMap>();
actionMap->LayerJson(json);
const auto action = actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Shift, 0, 255 });
VERIFY_IS_NOT_NULL(action);
}
} }

View file

@ -98,10 +98,10 @@
<!-- From Microsoft.UI.Xaml.targets --> <!-- From Microsoft.UI.Xaml.targets -->
<Native-Platform Condition="'$(Platform)' == 'Win32'">x86</Native-Platform> <Native-Platform Condition="'$(Platform)' == 'Win32'">x86</Native-Platform>
<Native-Platform Condition="'$(Platform)' != 'Win32'">$(Platform)</Native-Platform> <Native-Platform Condition="'$(Platform)' != 'Win32'">$(Platform)</Native-Platform>
<_MUXBinRoot>&quot;$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\runtimes\win10-$(Native-Platform)\native\&quot;</_MUXBinRoot> <_MUXBinRoot>&quot;$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\runtimes\win10-$(Native-Platform)\native\&quot;</_MUXBinRoot>
</PropertyGroup> </PropertyGroup>
<!-- We actually can just straight up reference MUX here, it's fine --> <!-- We actually can just straight up reference MUX here, it's fine -->
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" /> <Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets')" />
</Project> </Project>

View file

@ -37,7 +37,7 @@ namespace SettingsModelLocalTests
TEST_METHOD(TestTerminalArgsForBinding); TEST_METHOD(TestTerminalArgsForBinding);
TEST_METHOD(MakeSettingsForProfileThatDoesntExist); TEST_METHOD(MakeSettingsForProfile);
TEST_METHOD(MakeSettingsForDefaultProfileThatDoesntExist); TEST_METHOD(MakeSettingsForDefaultProfileThatDoesntExist);
TEST_METHOD(TestLayerProfileOnColorScheme); TEST_METHOD(TestLayerProfileOnColorScheme);
@ -113,7 +113,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(12u, actionMapImpl->_KeyMap.size()); VERIFY_ARE_EQUAL(12u, actionMapImpl->_KeyMap.size());
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('A') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('A'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>(); const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@ -126,15 +126,15 @@ namespace SettingsModelLocalTests
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings(); const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(guid0, guid); VERIFY_ARE_EQUAL(guid0, profile.Guid());
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(1, termSettings.HistorySize()); VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('B') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('B'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>(); const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@ -148,15 +148,15 @@ namespace SettingsModelLocalTests
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}", realArgs.TerminalArgs().Profile()); VERIFY_ARE_EQUAL(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}", realArgs.TerminalArgs().Profile());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings(); const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(guid1, guid); VERIFY_ARE_EQUAL(guid1, profile.Guid());
VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(2, termSettings.HistorySize()); VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('C') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('C'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>(); const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@ -170,15 +170,15 @@ namespace SettingsModelLocalTests
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings(); const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(guid1, guid); VERIFY_ARE_EQUAL(guid1, profile.Guid());
VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(2, termSettings.HistorySize()); VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('D') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('D'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>(); const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@ -192,15 +192,15 @@ namespace SettingsModelLocalTests
VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings(); const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(profile2Guid, guid); VERIFY_ARE_EQUAL(profile2Guid, profile.Guid());
VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(3, termSettings.HistorySize()); VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('E') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('E'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>(); const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@ -214,15 +214,15 @@ namespace SettingsModelLocalTests
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline()); VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings(); const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(guid0, guid); VERIFY_ARE_EQUAL(guid0, profile.Guid());
VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(1, termSettings.HistorySize()); VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('F') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('F'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>(); const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
@ -237,15 +237,15 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile());
VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline()); VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings(); const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(guid1, guid); VERIFY_ARE_EQUAL(guid1, profile.Guid());
VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(2, termSettings.HistorySize()); VERIFY_ARE_EQUAL(2, termSettings.HistorySize());
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('G') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('G'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>(); const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
@ -257,15 +257,15 @@ namespace SettingsModelLocalTests
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings(); const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(guid0, guid); VERIFY_ARE_EQUAL(guid0, profile.Guid());
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(1, termSettings.HistorySize()); VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('H') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('H'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>(); const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
@ -278,16 +278,16 @@ namespace SettingsModelLocalTests
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory()); VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings(); const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(guid0, guid); VERIFY_ARE_EQUAL(guid0, profile.Guid());
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory()); VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory());
VERIFY_ARE_EQUAL(1, termSettings.HistorySize()); VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('I') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('I'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>(); const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
@ -301,16 +301,16 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory()); VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory());
VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings(); const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(profile2Guid, guid); VERIFY_ARE_EQUAL(profile2Guid, profile.Guid());
VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory()); VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory());
VERIFY_ARE_EQUAL(3, termSettings.HistorySize()); VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('J') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('J'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>(); const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
@ -323,16 +323,16 @@ namespace SettingsModelLocalTests
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle()); VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings(); const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(guid0, guid); VERIFY_ARE_EQUAL(guid0, profile.Guid());
VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle()); VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle());
VERIFY_ARE_EQUAL(1, termSettings.HistorySize()); VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('K') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('K'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>(); const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
@ -346,16 +346,16 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle()); VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle());
VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings(); const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(profile2Guid, guid); VERIFY_ARE_EQUAL(profile2Guid, profile.Guid());
VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle()); VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle());
VERIFY_ARE_EQUAL(3, termSettings.HistorySize()); VERIFY_ARE_EQUAL(3, termSettings.HistorySize());
} }
{ {
KeyChord kc{ true, false, false, static_cast<int32_t>('L') }; KeyChord kc{ true, false, false, false, static_cast<int32_t>('L'), 0 };
auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>(); const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
@ -371,10 +371,10 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle()); VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle());
VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile());
const auto guid{ settings.GetProfileForArgs(realArgs.TerminalArgs()) }; const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) }; const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
const auto termSettings = settingsStruct.DefaultSettings(); const auto termSettings = settingsStruct.DefaultSettings();
VERIFY_ARE_EQUAL(guid1, guid); VERIFY_ARE_EQUAL(guid1, profile.Guid());
VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline());
VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle()); VERIFY_ARE_EQUAL(L"bar", termSettings.StartingTitle());
VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory()); VERIFY_ARE_EQUAL(L"c:\\foo", termSettings.StartingDirectory());
@ -382,9 +382,9 @@ namespace SettingsModelLocalTests
} }
} }
void TerminalSettingsTests::MakeSettingsForProfileThatDoesntExist() void TerminalSettingsTests::MakeSettingsForProfile()
{ {
// Test that making settings throws when the GUID doesn't exist // Test that making settings generally works.
const std::string settingsString{ R"( const std::string settingsString{ R"(
{ {
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
@ -405,32 +405,32 @@ namespace SettingsModelLocalTests
const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); const auto guid1 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
const auto guid2 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}"); const auto guid2 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
const auto guid3 = ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
const auto profile1 = settings.FindProfile(guid1);
const auto profile2 = settings.FindProfile(guid2);
try try
{ {
auto terminalSettings{ TerminalSettings::CreateWithProfileByID(settings, guid1, nullptr) }; auto terminalSettings{ TerminalSettings::CreateWithProfile(settings, profile1, nullptr) };
VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings); VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings);
VERIFY_ARE_EQUAL(1, terminalSettings.DefaultSettings().HistorySize()); VERIFY_ARE_EQUAL(1, terminalSettings.DefaultSettings().HistorySize());
} }
catch (...) catch (...)
{ {
VERIFY_IS_TRUE(false, L"This call to CreateWithProfileByID should succeed"); VERIFY_IS_TRUE(false, L"This call to CreateWithProfile should succeed");
} }
try try
{ {
auto terminalSettings{ TerminalSettings::CreateWithProfileByID(settings, guid2, nullptr) }; auto terminalSettings{ TerminalSettings::CreateWithProfile(settings, profile2, nullptr) };
VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings); VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings);
VERIFY_ARE_EQUAL(2, terminalSettings.DefaultSettings().HistorySize()); VERIFY_ARE_EQUAL(2, terminalSettings.DefaultSettings().HistorySize());
} }
catch (...) catch (...)
{ {
VERIFY_IS_TRUE(false, L"This call to CreateWithProfileByID should succeed"); VERIFY_IS_TRUE(false, L"This call to CreateWithProfile should succeed");
} }
VERIFY_THROWS(auto terminalSettings = TerminalSettings::CreateWithProfileByID(settings, guid3, nullptr), wil::ResultException, L"This call to constructor should fail");
try try
{ {
const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, nullptr, nullptr) }; const auto termSettings{ TerminalSettings::CreateWithNewTerminalArgs(settings, nullptr, nullptr) };

View file

@ -56,6 +56,7 @@ namespace TerminalAppLocalTests
TEST_METHOD(ParseComboCommandlineIntoArgs); TEST_METHOD(ParseComboCommandlineIntoArgs);
TEST_METHOD(ParseFocusTabArgs); TEST_METHOD(ParseFocusTabArgs);
TEST_METHOD(ParseMoveFocusArgs); TEST_METHOD(ParseMoveFocusArgs);
TEST_METHOD(ParseSwapPaneArgs);
TEST_METHOD(ParseArgumentsWithParsingTerminators); TEST_METHOD(ParseArgumentsWithParsingTerminators);
TEST_METHOD(ParseFocusPaneArgs); TEST_METHOD(ParseFocusPaneArgs);
@ -1207,6 +1208,119 @@ namespace TerminalAppLocalTests
} }
} }
void CommandlineTest::ParseSwapPaneArgs()
{
const wchar_t* subcommand = L"swap-pane";
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand };
Log::Comment(NoThrowString().Format(
L"Just the subcommand, without a direction, should fail."));
_buildCommandlinesExpectFailureHelper(appArgs, 1u, rawCommands);
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"left" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::SwapPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SwapPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Left, myArgs.Direction());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"right" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::SwapPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SwapPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Right, myArgs.Direction());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"up" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::SwapPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SwapPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Up, myArgs.Direction());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"down" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::SwapPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SwapPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Down, myArgs.Direction());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"badDirection" };
Log::Comment(NoThrowString().Format(
L"move-pane with an invalid direction should fail."));
_buildCommandlinesExpectFailureHelper(appArgs, 1u, rawCommands);
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"left", L";", subcommand, L"right" };
_buildCommandlinesHelper(appArgs, 2u, rawCommands);
VERIFY_ARE_EQUAL(3u, appArgs._startupActions.size());
// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::SwapPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SwapPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Left, myArgs.Direction());
actionAndArgs = appArgs._startupActions.at(2);
VERIFY_ARE_EQUAL(ShortcutAction::SwapPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
myArgs = actionAndArgs.Args().try_as<SwapPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Right, myArgs.Direction());
}
}
void CommandlineTest::ParseFocusPaneArgs() void CommandlineTest::ParseFocusPaneArgs()
{ {
BEGIN_TEST_METHOD_PROPERTIES() BEGIN_TEST_METHOD_PROPERTIES()

View file

@ -82,6 +82,8 @@ namespace TerminalAppLocalTests
TEST_METHOD(MoveFocusFromZoomedPane); TEST_METHOD(MoveFocusFromZoomedPane);
TEST_METHOD(CloseZoomedPane); TEST_METHOD(CloseZoomedPane);
TEST_METHOD(SwapPanes);
TEST_METHOD(NextMRUTab); TEST_METHOD(NextMRUTab);
TEST_METHOD(VerifyCommandPaletteTabSwitcherOrder); TEST_METHOD(VerifyCommandPaletteTabSwitcherOrder);
@ -93,6 +95,8 @@ namespace TerminalAppLocalTests
TEST_METHOD(TestPreviewDismissScheme); TEST_METHOD(TestPreviewDismissScheme);
TEST_METHOD(TestPreviewSchemeWhilePreviewing); TEST_METHOD(TestPreviewSchemeWhilePreviewing);
TEST_METHOD(TestClampSwitchToTab);
TEST_CLASS_SETUP(ClassSetup) TEST_CLASS_SETUP(ClassSetup)
{ {
return true; return true;
@ -426,7 +430,7 @@ namespace TerminalAppLocalTests
Log::Comment(L"Duplicate the tab, and don't crash"); Log::Comment(L"Duplicate the tab, and don't crash");
result = RunOnUIThread([&page]() { result = RunOnUIThread([&page]() {
page->_DuplicateFocusedTab(); page->_DuplicateFocusedTab();
VERIFY_ARE_EQUAL(2u, page->_tabs.Size(), L"We should gracefully do nothing here - the profile no longer exists."); VERIFY_ARE_EQUAL(3u, page->_tabs.Size(), L"We should successfully duplicate a tab hosting a deleted profile.");
}); });
VERIFY_SUCCEEDED(result); VERIFY_SUCCEEDED(result);
} }
@ -526,9 +530,9 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(2, VERIFY_ARE_EQUAL(3,
tab->GetLeafPaneCount(), tab->GetLeafPaneCount(),
L"We should gracefully do nothing here - the profile no longer exists."); L"We should successfully duplicate a pane hosting a deleted profile.");
}); });
VERIFY_SUCCEEDED(result); VERIFY_SUCCEEDED(result);
@ -817,6 +821,212 @@ namespace TerminalAppLocalTests
VERIFY_SUCCEEDED(result); VERIFY_SUCCEEDED(result);
} }
void TabTests::SwapPanes()
{
auto page = _commonSetup();
Log::Comment(L"Setup 4 panes.");
// Create the following layout
// -------------------
// | 1 | 2 |
// | | |
// -------------------
// | 3 | 4 |
// | | |
// -------------------
uint32_t firstId = 0, secondId = 0, thirdId = 0, fourthId = 0;
TestOnUIThread([&]() {
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
firstId = tab->_activePane->Id().value();
// We start with 1 tab, split vertically to get
// -------------------
// | 1 | 2 |
// | | |
// -------------------
page->_SplitPane(SplitState::Vertical, SplitType::Duplicate, 0.5f, nullptr);
secondId = tab->_activePane->Id().value();
});
Sleep(250);
TestOnUIThread([&]() {
// After this the `2` pane is focused, go back to `1` being focused
page->_MoveFocus(FocusDirection::Left);
});
Sleep(250);
TestOnUIThread([&]() {
// Split again to make the 3rd tab
// -------------------
// | 1 | |
// | | |
// ---------| 2 |
// | 3 | |
// | | |
// -------------------
page->_SplitPane(SplitState::Horizontal, SplitType::Duplicate, 0.5f, nullptr);
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
// Split again to make the 3rd tab
thirdId = tab->_activePane->Id().value();
});
Sleep(250);
TestOnUIThread([&]() {
// After this the `3` pane is focused, go back to `2` being focused
page->_MoveFocus(FocusDirection::Right);
});
Sleep(250);
TestOnUIThread([&]() {
// Split to create the final pane
// -------------------
// | 1 | 2 |
// | | |
// -------------------
// | 3 | 4 |
// | | |
// -------------------
page->_SplitPane(SplitState::Horizontal, SplitType::Duplicate, 0.5f, nullptr);
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
fourthId = tab->_activePane->Id().value();
});
Sleep(250);
TestOnUIThread([&]() {
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
// just to be complete, make sure we actually have 4 different ids
VERIFY_ARE_NOT_EQUAL(firstId, fourthId);
VERIFY_ARE_NOT_EQUAL(secondId, fourthId);
VERIFY_ARE_NOT_EQUAL(thirdId, fourthId);
VERIFY_ARE_NOT_EQUAL(firstId, thirdId);
VERIFY_ARE_NOT_EQUAL(secondId, thirdId);
VERIFY_ARE_NOT_EQUAL(firstId, secondId);
});
// Gratuitous use of sleep to make sure that the UI has updated properly
// after each operation.
Sleep(250);
// Now try to move the pane through the tree
Log::Comment(L"Move pane to the left. This should swap panes 3 and 4");
// -------------------
// | 1 | 2 |
// | | |
// -------------------
// | 4 | 3 |
// | | |
// -------------------
TestOnUIThread([&]() {
// Set up action
SwapPaneArgs args{ FocusDirection::Left };
ActionEventArgs eventArgs{ args };
page->_HandleSwapPane(nullptr, eventArgs);
});
Sleep(250);
TestOnUIThread([&]() {
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
// Our currently focused pane should be `4`
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());
// Inspect the tree to make sure we swapped
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_firstChild->_secondChild->Id().value());
VERIFY_ARE_EQUAL(thirdId, tab->_rootPane->_secondChild->_secondChild->Id().value());
});
Sleep(250);
Log::Comment(L"Move pane to up. This should swap panes 1 and 4");
// -------------------
// | 4 | 2 |
// | | |
// -------------------
// | 1 | 3 |
// | | |
// -------------------
TestOnUIThread([&]() {
// Set up action
SwapPaneArgs args{ FocusDirection::Up };
ActionEventArgs eventArgs{ args };
page->_HandleSwapPane(nullptr, eventArgs);
});
Sleep(250);
TestOnUIThread([&]() {
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
// Our currently focused pane should be `4`
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());
// Inspect the tree to make sure we swapped
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_firstChild->_firstChild->Id().value());
VERIFY_ARE_EQUAL(firstId, tab->_rootPane->_firstChild->_secondChild->Id().value());
});
Sleep(250);
Log::Comment(L"Move pane to the right. This should swap panes 2 and 4");
// -------------------
// | 2 | 4 |
// | | |
// -------------------
// | 1 | 3 |
// | | |
// -------------------
TestOnUIThread([&]() {
// Set up action
SwapPaneArgs args{ FocusDirection::Right };
ActionEventArgs eventArgs{ args };
page->_HandleSwapPane(nullptr, eventArgs);
});
Sleep(250);
TestOnUIThread([&]() {
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
// Our currently focused pane should be `4`
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());
// Inspect the tree to make sure we swapped
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_secondChild->_firstChild->Id().value());
VERIFY_ARE_EQUAL(secondId, tab->_rootPane->_firstChild->_firstChild->Id().value());
});
Sleep(250);
Log::Comment(L"Move pane down. This should swap panes 3 and 4");
// -------------------
// | 2 | 3 |
// | | |
// -------------------
// | 1 | 4 |
// | | |
// -------------------
TestOnUIThread([&]() {
// Set up action
SwapPaneArgs args{ FocusDirection::Down };
ActionEventArgs eventArgs{ args };
page->_HandleSwapPane(nullptr, eventArgs);
});
Sleep(250);
TestOnUIThread([&]() {
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
// Our currently focused pane should be `4`
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());
// Inspect the tree to make sure we swapped
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_secondChild->_secondChild->Id().value());
VERIFY_ARE_EQUAL(thirdId, tab->_rootPane->_secondChild->_firstChild->Id().value());
});
}
void TabTests::NextMRUTab() void TabTests::NextMRUTab()
{ {
// This is a test for GH#8025 - we want to make sure that we can do both // This is a test for GH#8025 - we want to make sure that we can do both
@ -1342,4 +1552,55 @@ namespace TerminalAppLocalTests
}); });
} }
void TabTests::TestClampSwitchToTab()
{
Log::Comment(L"Test that switching to a tab index higher than the number of tabs just clamps to the last tab.");
auto page = _commonSetup();
VERIFY_IS_NOT_NULL(page);
Log::Comment(L"Create a second tab");
TestOnUIThread([&page]() {
NewTerminalArgs newTerminalArgs{ 1 };
page->_OpenNewTab(newTerminalArgs);
});
VERIFY_ARE_EQUAL(2u, page->_tabs.Size());
Log::Comment(L"Create a third tab");
TestOnUIThread([&page]() {
NewTerminalArgs newTerminalArgs{ 2 };
page->_OpenNewTab(newTerminalArgs);
});
VERIFY_ARE_EQUAL(3u, page->_tabs.Size());
TestOnUIThread([&page]() {
auto focusedTabIndexOpt{ page->_GetFocusedTabIndex() };
VERIFY_IS_TRUE(focusedTabIndexOpt.has_value());
VERIFY_ARE_EQUAL(2u, focusedTabIndexOpt.value());
});
TestOnUIThread([&page]() {
Log::Comment(L"Switch to the first tab");
page->_SelectTab(0);
});
TestOnUIThread([&page]() {
auto focusedTabIndexOpt{ page->_GetFocusedTabIndex() };
VERIFY_IS_TRUE(focusedTabIndexOpt.has_value());
VERIFY_ARE_EQUAL(0u, focusedTabIndexOpt.value());
});
TestOnUIThread([&page]() {
Log::Comment(L"Switch to the tab 6, which is greater than number of tabs. This should switch to the third tab");
page->_SelectTab(6);
});
TestOnUIThread([&page]() {
auto focusedTabIndexOpt{ page->_GetFocusedTabIndex() };
VERIFY_IS_TRUE(focusedTabIndexOpt.has_value());
VERIFY_ARE_EQUAL(2u, focusedTabIndexOpt.value());
});
}
} }

View file

@ -92,11 +92,11 @@
<!-- From Microsoft.UI.Xaml.targets --> <!-- From Microsoft.UI.Xaml.targets -->
<Native-Platform Condition="'$(Platform)' == 'Win32'">x86</Native-Platform> <Native-Platform Condition="'$(Platform)' == 'Win32'">x86</Native-Platform>
<Native-Platform Condition="'$(Platform)' != 'Win32'">$(Platform)</Native-Platform> <Native-Platform Condition="'$(Platform)' != 'Win32'">$(Platform)</Native-Platform>
<_MUXBinRoot>&quot;$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\runtimes\win10-$(Native-Platform)\native\&quot;</_MUXBinRoot> <_MUXBinRoot>&quot;$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\runtimes\win10-$(Native-Platform)\native\&quot;</_MUXBinRoot>
</PropertyGroup> </PropertyGroup>
<!-- We actually can just straight up reference MUX here, it's fine --> <!-- We actually can just straight up reference MUX here, it's fine -->
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" /> <Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" /> <Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
</Project> </Project>

View file

@ -123,7 +123,7 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<Import Project="$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" /> <Import Project="$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="$(OpenConsoleDir)\src\common.build.post.props" /> <Import Project="$(OpenConsoleDir)\src\common.build.post.props" />

View file

@ -28,8 +28,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
CATCH_LOG(); CATCH_LOG();
} }
// This is a private constructor to be used in unit tests, where we don't // This constructor is intended to be used in unit tests,
// want each Monarch to necessarily use the current PID. // but we need to make it public in order to use make_self
// in the tests. It's not exposed through the idl though
// so it's not _truly_ fully public which should be acceptable.
Monarch::Monarch(const uint64_t testPID) : Monarch::Monarch(const uint64_t testPID) :
_ourPID{ testPID } _ourPID{ testPID }
{ {
@ -78,6 +80,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
peasant.IdentifyWindowsRequested({ this, &Monarch::_identifyWindows }); peasant.IdentifyWindowsRequested({ this, &Monarch::_identifyWindows });
peasant.RenameRequested({ this, &Monarch::_renameRequested }); peasant.RenameRequested({ this, &Monarch::_renameRequested });
peasant.ShowTrayIconRequested([this](auto&&, auto&&) { _ShowTrayIconRequestedHandlers(*this, nullptr); });
peasant.HideTrayIconRequested([this](auto&&, auto&&) { _HideTrayIconRequestedHandlers(*this, nullptr); });
_peasants[newPeasantsId] = peasant; _peasants[newPeasantsId] = peasant;
TraceLoggingWrite(g_hRemotingProvider, TraceLoggingWrite(g_hRemotingProvider,
@ -201,6 +206,12 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
_clearOldMruEntries(id); _clearOldMruEntries(id);
} }
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_lookupPeasantIdForName",
TraceLoggingWideString(std::wstring{ name }.c_str(), "name", "the name we're looking for"),
TraceLoggingUInt64(result, "peasantID", "the ID of the peasant with that name"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
return result; return result;
} }
@ -732,24 +743,55 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
try try
{ {
args.FoundMatch(false); args.FoundMatch(false);
// If a WindowID is provided from the args, use that first.
uint64_t windowId = 0; uint64_t windowId = 0;
// If no name was provided, then just summon the MRU window. if (args.WindowID())
if (searchedForName.empty())
{ {
// Use the value of the `desktop` arg to determine if we should windowId = args.WindowID().Value();
// limit to the current desktop (desktop:onCurrent) or not
// (desktop:any or desktop:toCurrent)
windowId = _getMostRecentPeasantID(args.OnCurrentDesktop(), false);
} }
else else
{ {
// Try to find a peasant that currently has this name // If no name was provided, then just summon the MRU window.
windowId = _lookupPeasantIdForName(searchedForName); if (searchedForName.empty())
{
// Use the value of the `desktop` arg to determine if we should
// limit to the current desktop (desktop:onCurrent) or not
// (desktop:any or desktop:toCurrent)
windowId = _getMostRecentPeasantID(args.OnCurrentDesktop(), false);
}
else
{
// Try to find a peasant that currently has this name
windowId = _lookupPeasantIdForName(searchedForName);
}
} }
if (auto targetPeasant{ _getPeasant(windowId) }) if (auto targetPeasant{ _getPeasant(windowId) })
{ {
targetPeasant.Summon(args.SummonBehavior()); targetPeasant.Summon(args.SummonBehavior());
args.FoundMatch(true); args.FoundMatch(true);
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SummonWindow_Success",
TraceLoggingWideString(searchedForName.c_str(), "searchedForName", "The name of the window we tried to summon"),
TraceLoggingUInt64(windowId, "peasantID", "The id of the window we tried to summon"),
TraceLoggingBoolean(args.OnCurrentDesktop(), "OnCurrentDesktop", "true iff the window needs to be on the current virtual desktop"),
TraceLoggingBoolean(args.SummonBehavior().MoveToCurrentDesktop(), "MoveToCurrentDesktop", "if true, move the window to the current virtual desktop"),
TraceLoggingBoolean(args.SummonBehavior().ToggleVisibility(), "ToggleVisibility", "true if we should toggle the visibility of the window"),
TraceLoggingUInt32(args.SummonBehavior().DropdownDuration(), "DropdownDuration", "the duration to dropdown the window"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
else
{
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SummonWindow_NoPeasant",
TraceLoggingWideString(searchedForName.c_str(), "searchedForName", "The name of the window we tried to summon"),
TraceLoggingUInt64(windowId, "peasantID", "The id of the window we tried to summon"),
TraceLoggingBoolean(args.OnCurrentDesktop(), "OnCurrentDesktop", "true iff the window needs to be on the current virtual desktop"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
} }
} }
catch (...) catch (...)
@ -762,4 +804,56 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TraceLoggingKeyword(TIL_KEYWORD_TRACE)); TraceLoggingKeyword(TIL_KEYWORD_TRACE));
} }
} }
// Method Description:
// - This method creates a map of peasant IDs to peasant names
// while removing dead peasants.
// Arguments:
// - <none>
// Return Value:
// - A map of peasant IDs to their names.
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> Monarch::GetPeasantNames()
{
auto names = winrt::single_threaded_map<uint64_t, winrt::hstring>();
std::vector<uint64_t> peasantsToErase{};
for (const auto& [id, p] : _peasants)
{
try
{
names.Insert(id, p.WindowName());
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
peasantsToErase.push_back(id);
}
}
// Remove the dead peasants we came across while iterating.
for (const auto& id : peasantsToErase)
{
_peasants.erase(id);
_clearOldMruEntries(id);
}
return names.GetView();
}
void Monarch::SummonAllWindows()
{
auto callback = [](auto&& p, auto&& /*id*/) {
SummonWindowBehavior args{};
args.ToggleVisibility(false);
p.Summon(args);
};
auto onError = [](auto&& id) {
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SummonAll_Failed",
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not summon"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
};
_forAllPeasantsIgnoringTheDead(callback, onError);
}
} }

View file

@ -41,6 +41,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
struct Monarch : public MonarchT<Monarch> struct Monarch : public MonarchT<Monarch>
{ {
Monarch(); Monarch();
Monarch(const uint64_t testPID);
~Monarch(); ~Monarch();
uint64_t GetPID(); uint64_t GetPID();
@ -51,10 +52,14 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void HandleActivatePeasant(const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args); void HandleActivatePeasant(const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args);
void SummonWindow(const Remoting::SummonWindowSelectionArgs& args); void SummonWindow(const Remoting::SummonWindowSelectionArgs& args);
void SummonAllWindows();
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> GetPeasantNames();
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs); TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(ShowTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
private: private:
Monarch(const uint64_t testPID);
uint64_t _ourPID; uint64_t _ourPID;
uint64_t _nextPeasantID{ 1 }; uint64_t _nextPeasantID{ 1 };

View file

@ -28,6 +28,7 @@ namespace Microsoft.Terminal.Remoting
Boolean FoundMatch; Boolean FoundMatch;
SummonWindowBehavior SummonBehavior; SummonWindowBehavior SummonBehavior;
Windows.Foundation.IReference<UInt64> WindowID;
} }
@ -40,6 +41,11 @@ namespace Microsoft.Terminal.Remoting
void HandleActivatePeasant(WindowActivatedArgs args); void HandleActivatePeasant(WindowActivatedArgs args);
void SummonWindow(SummonWindowSelectionArgs args); void SummonWindow(SummonWindowSelectionArgs args);
void SummonAllWindows();
Windows.Foundation.Collections.IMapView<UInt64, String> GetPeasantNames { get; };
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested; event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideTrayIconRequested;
}; };
} }

View file

@ -20,8 +20,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
{ {
} }
// This is a private constructor to be used in unit tests, where we don't // This constructor is intended to be used in unit tests,
// want each Peasant to necessarily use the current PID. // but we need to make it public in order to use make_self
// in the tests. It's not exposed through the idl though
// so it's not _truly_ fully public which should be acceptable.
Peasant::Peasant(const uint64_t testPID) : Peasant::Peasant(const uint64_t testPID) :
_ourPID{ testPID } _ourPID{ testPID }
{ {
@ -31,6 +33,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
{ {
_id = id; _id = id;
} }
uint64_t Peasant::GetID() uint64_t Peasant::GetID()
{ {
return _id; return _id;
@ -222,4 +225,36 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE)); TraceLoggingKeyword(TIL_KEYWORD_TRACE));
} }
void Peasant::RequestShowTrayIcon()
{
try
{
_ShowTrayIconRequestedHandlers(*this, nullptr);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_RequestShowTrayIcon",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
void Peasant::RequestHideTrayIcon()
{
try
{
_HideTrayIconRequestedHandlers(*this, nullptr);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_RequestHideTrayIcon",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
} }

View file

@ -28,6 +28,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void RequestIdentifyWindows(); void RequestIdentifyWindows();
void DisplayWindowId(); void DisplayWindowId();
void RequestRename(const winrt::Microsoft::Terminal::Remoting::RenameRequestArgs& args); void RequestRename(const winrt::Microsoft::Terminal::Remoting::RenameRequestArgs& args);
void RequestShowTrayIcon();
void RequestHideTrayIcon();
winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs GetLastActivatedArgs(); winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs GetLastActivatedArgs();
@ -40,6 +42,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TYPED_EVENT(DisplayWindowIdRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); TYPED_EVENT(DisplayWindowIdRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(RenameRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::RenameRequestArgs); TYPED_EVENT(RenameRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::RenameRequestArgs);
TYPED_EVENT(SummonRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::SummonWindowBehavior); TYPED_EVENT(SummonRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::SummonWindowBehavior);
TYPED_EVENT(ShowTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
private: private:
Peasant(const uint64_t testPID); Peasant(const uint64_t testPID);

View file

@ -64,6 +64,8 @@ namespace Microsoft.Terminal.Remoting
void RequestIdentifyWindows(); // Tells us to raise a IdentifyWindowsRequested void RequestIdentifyWindows(); // Tells us to raise a IdentifyWindowsRequested
void RequestRename(RenameRequestArgs args); // Tells us to raise a RenameRequested void RequestRename(RenameRequestArgs args); // Tells us to raise a RenameRequested
void Summon(SummonWindowBehavior behavior); void Summon(SummonWindowBehavior behavior);
void RequestShowTrayIcon();
void RequestHideTrayIcon();
event Windows.Foundation.TypedEventHandler<Object, WindowActivatedArgs> WindowActivated; event Windows.Foundation.TypedEventHandler<Object, WindowActivatedArgs> WindowActivated;
event Windows.Foundation.TypedEventHandler<Object, CommandlineArgs> ExecuteCommandlineRequested; event Windows.Foundation.TypedEventHandler<Object, CommandlineArgs> ExecuteCommandlineRequested;
@ -71,6 +73,8 @@ namespace Microsoft.Terminal.Remoting
event Windows.Foundation.TypedEventHandler<Object, Object> DisplayWindowIdRequested; event Windows.Foundation.TypedEventHandler<Object, Object> DisplayWindowIdRequested;
event Windows.Foundation.TypedEventHandler<Object, RenameRequestArgs> RenameRequested; event Windows.Foundation.TypedEventHandler<Object, RenameRequestArgs> RenameRequested;
event Windows.Foundation.TypedEventHandler<Object, SummonWindowBehavior> SummonRequested; event Windows.Foundation.TypedEventHandler<Object, SummonWindowBehavior> SummonRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideTrayIconRequested;
}; };
[default_interface] runtimeclass Peasant : IPeasant [default_interface] runtimeclass Peasant : IPeasant

View file

@ -34,6 +34,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
WINRT_PROPERTY(bool, FoundMatch, false); WINRT_PROPERTY(bool, FoundMatch, false);
WINRT_PROPERTY(bool, OnCurrentDesktop, false); WINRT_PROPERTY(bool, OnCurrentDesktop, false);
WINRT_PROPERTY(SummonWindowBehavior, SummonBehavior); WINRT_PROPERTY(SummonWindowBehavior, SummonBehavior);
WINRT_PROPERTY(Windows::Foundation::IReference<uint64_t>, WindowID);
}; };
} }

View file

@ -254,6 +254,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// window, and when the current monarch dies. // window, and when the current monarch dies.
_monarch.FindTargetWindowRequested({ this, &WindowManager::_raiseFindTargetWindowRequested }); _monarch.FindTargetWindowRequested({ this, &WindowManager::_raiseFindTargetWindowRequested });
_monarch.ShowTrayIconRequested([this](auto&&, auto&&) { _ShowTrayIconRequestedHandlers(*this, nullptr); });
_monarch.HideTrayIconRequested([this](auto&&, auto&&) { _HideTrayIconRequestedHandlers(*this, nullptr); });
_BecameMonarchHandlers(*this, nullptr); _BecameMonarchHandlers(*this, nullptr);
} }
@ -509,4 +511,57 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
_monarch.SummonWindow(args); _monarch.SummonWindow(args);
} }
void WindowManager::SummonAllWindows()
{
if constexpr (Feature_TrayIcon::IsEnabled())
{
_monarch.SummonAllWindows();
}
}
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> WindowManager::GetPeasantNames()
{
// We should only get called when we're the monarch since the monarch
// is the only one that knows about all peasants.
return _monarch.GetPeasantNames();
}
// Method Description:
// - Ask the monarch to show a tray icon.
// Arguments:
// - <none>
// Return Value:
// - <none>
winrt::fire_and_forget WindowManager::RequestShowTrayIcon()
{
co_await winrt::resume_background();
_peasant.RequestShowTrayIcon();
}
// Method Description:
// - Ask the monarch to hide its tray icon.
// Arguments:
// - <none>
// Return Value:
// - <none>
winrt::fire_and_forget WindowManager::RequestHideTrayIcon()
{
auto strongThis{ get_strong() };
co_await winrt::resume_background();
_peasant.RequestHideTrayIcon();
}
bool WindowManager::DoesQuakeWindowExist()
{
const auto names = GetPeasantNames();
for (const auto [id, name] : names)
{
if (name == QuakeWindowName)
{
return true;
}
}
return false;
}
} }

View file

@ -40,8 +40,17 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
bool IsMonarch(); bool IsMonarch();
void SummonWindow(const Remoting::SummonWindowSelectionArgs& args); void SummonWindow(const Remoting::SummonWindowSelectionArgs& args);
void SummonAllWindows();
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> GetPeasantNames();
winrt::fire_and_forget RequestShowTrayIcon();
winrt::fire_and_forget RequestHideTrayIcon();
bool DoesQuakeWindowExist();
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs); TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(BecameMonarch, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); TYPED_EVENT(BecameMonarch, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(ShowTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
private: private:
bool _shouldCreateWindow{ false }; bool _shouldCreateWindow{ false };

View file

@ -12,7 +12,14 @@ namespace Microsoft.Terminal.Remoting
IPeasant CurrentWindow(); IPeasant CurrentWindow();
Boolean IsMonarch { get; }; Boolean IsMonarch { get; };
void SummonWindow(SummonWindowSelectionArgs args); void SummonWindow(SummonWindowSelectionArgs args);
void SummonAllWindows();
void RequestShowTrayIcon();
void RequestHideTrayIcon();
Boolean DoesQuakeWindowExist();
Windows.Foundation.Collections.IMapView<UInt64, String> GetPeasantNames();
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested; event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> BecameMonarch; event Windows.Foundation.TypedEventHandler<Object, Object> BecameMonarch;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideTrayIconRequested;
}; };
} }

View file

@ -20,7 +20,8 @@
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<!-- Include the MUX Controls resources --> <!-- Include the MUX Controls resources -->
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" /> <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls"
ControlsResourcesVersion="Version1" />
<ResourceDictionary> <ResourceDictionary>
<!-- <!--
@ -55,12 +56,18 @@
<!-- Define resources for Dark mode here --> <!-- Define resources for Dark mode here -->
<SolidColorBrush x:Key="TabViewBackground" <SolidColorBrush x:Key="TabViewBackground"
Color="#FF333333" /> Color="#FF333333" />
<SolidColorBrush x:Key="UnfocusedBorderBrush"
Color="#FF333333" />
</ResourceDictionary> </ResourceDictionary>
<ResourceDictionary x:Key="Light"> <ResourceDictionary x:Key="Light">
<!-- Define resources for Light mode here --> <!-- Define resources for Light mode here -->
<SolidColorBrush x:Key="TabViewBackground" <SolidColorBrush x:Key="TabViewBackground"
Color="#FFCCCCCC" /> Color="#FFCCCCCC" />
<SolidColorBrush x:Key="UnfocusedBorderBrush"
Color="#FFCCCCCC" />
</ResourceDictionary> </ResourceDictionary>
</ResourceDictionary.ThemeDictionaries> </ResourceDictionary.ThemeDictionaries>

View file

@ -143,6 +143,20 @@ namespace winrt::TerminalApp::implementation
} }
} }
void TerminalPage::_HandleMovePane(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (args == nullptr)
{
args.Handled(false);
}
else if (const auto& realArgs = args.ActionArgs().try_as<MovePaneArgs>())
{
auto moved = _MovePane(realArgs.TabIndex());
args.Handled(moved);
}
}
void TerminalPage::_HandleSplitPane(const IInspectable& /*sender*/, void TerminalPage::_HandleSplitPane(const IInspectable& /*sender*/,
const ActionEventArgs& args) const ActionEventArgs& args)
{ {
@ -161,6 +175,13 @@ namespace winrt::TerminalApp::implementation
} }
} }
void TerminalPage::_HandleToggleSplitOrientation(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
_ToggleSplitOrientation();
args.Handled(true);
}
void TerminalPage::_HandleTogglePaneZoom(const IInspectable& /*sender*/, void TerminalPage::_HandleTogglePaneZoom(const IInspectable& /*sender*/,
const ActionEventArgs& args) const ActionEventArgs& args)
{ {
@ -257,12 +278,12 @@ namespace winrt::TerminalApp::implementation
{ {
if (args == nullptr) if (args == nullptr)
{ {
_OpenNewTab(nullptr); LOG_IF_FAILED(_OpenNewTab(nullptr));
args.Handled(true); args.Handled(true);
} }
else if (const auto& realArgs = args.ActionArgs().try_as<NewTabArgs>()) else if (const auto& realArgs = args.ActionArgs().try_as<NewTabArgs>())
{ {
_OpenNewTab(realArgs.TerminalArgs()); LOG_IF_FAILED(_OpenNewTab(realArgs.TerminalArgs()));
args.Handled(true); args.Handled(true);
} }
} }
@ -307,8 +328,29 @@ namespace winrt::TerminalApp::implementation
} }
else else
{ {
_MoveFocus(realArgs.FocusDirection()); // Mark as handled only when the move succeeded (e.g. when there
args.Handled(true); // is a pane to move to), otherwise mark as unhandled so the
// keychord can propagate to the terminal (GH#6129)
const auto moveSucceeded = _MoveFocus(realArgs.FocusDirection());
args.Handled(moveSucceeded);
}
}
}
void TerminalPage::_HandleSwapPane(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (const auto& realArgs = args.ActionArgs().try_as<SwapPaneArgs>())
{
if (realArgs.Direction() == FocusDirection::None)
{
// Do nothing
args.Handled(false);
}
else
{
auto swapped = _SwapPane(realArgs.Direction());
args.Handled(swapped);
} }
} }
} }
@ -711,11 +753,10 @@ namespace winrt::TerminalApp::implementation
newTerminalArgs = NewTerminalArgs(); newTerminalArgs = NewTerminalArgs();
} }
const auto profileGuid{ _settings.GetProfileForArgs(newTerminalArgs) }; const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) };
const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings) };
// Manually fill in the evaluated profile. // Manually fill in the evaluated profile.
newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profileGuid)); newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profile.Guid()));
_OpenNewWindow(false, newTerminalArgs); _OpenNewWindow(false, newTerminalArgs);
actionArgs.Handled(true); actionArgs.Handled(true);
} }

View file

@ -192,6 +192,8 @@ void AppCommandlineArgs::_buildParser()
_buildSplitPaneParser(); _buildSplitPaneParser();
_buildFocusTabParser(); _buildFocusTabParser();
_buildMoveFocusParser(); _buildMoveFocusParser();
_buildMovePaneParser();
_buildSwapPaneParser();
_buildFocusPaneParser(); _buildFocusPaneParser();
} }
@ -296,6 +298,43 @@ void AppCommandlineArgs::_buildSplitPaneParser()
setupSubcommand(_newPaneCommand); setupSubcommand(_newPaneCommand);
setupSubcommand(_newPaneShort); setupSubcommand(_newPaneShort);
} }
// Method Description:
// - Adds the `move-pane` subcommand and related options to the commandline parser.
// - Additionally adds the `mp` subcommand, which is just a shortened version of `move-pane`
// Arguments:
// - <none>
// Return Value:
// - <none>
void AppCommandlineArgs::_buildMovePaneParser()
{
_movePaneCommand = _app.add_subcommand("move-pane", RS_A(L"CmdMovePaneDesc"));
_movePaneShort = _app.add_subcommand("mp", RS_A(L"CmdMPDesc"));
auto setupSubcommand = [this](auto* subcommand) {
subcommand->add_option("-t,--tab",
_movePaneTabIndex,
RS_A(L"CmdMovePaneTabArgDesc"));
// When ParseCommand is called, if this subcommand was provided, this
// callback function will be triggered on the same thread. We can be sure
// that `this` will still be safe - this function just lets us know this
// command was parsed.
subcommand->callback([&, this]() {
// Build the action from the values we've parsed on the commandline.
ActionAndArgs movePaneAction{};
if (_movePaneTabIndex >= 0)
{
movePaneAction.Action(ShortcutAction::MovePane);
MovePaneArgs args{ static_cast<unsigned int>(_movePaneTabIndex) };
movePaneAction.Args(args);
_startupActions.push_back(movePaneAction);
}
});
};
setupSubcommand(_movePaneCommand);
setupSubcommand(_movePaneShort);
}
// Method Description: // Method Description:
// - Adds the `focus-tab` subcommand and related options to the commandline parser. // - Adds the `focus-tab` subcommand and related options to the commandline parser.
@ -341,6 +380,11 @@ void AppCommandlineArgs::_buildFocusTabParser()
else if (_focusNextTab || _focusPrevTab) else if (_focusNextTab || _focusPrevTab)
{ {
focusTabAction.Action(_focusNextTab ? ShortcutAction::NextTab : ShortcutAction::PrevTab); focusTabAction.Action(_focusNextTab ? ShortcutAction::NextTab : ShortcutAction::PrevTab);
// GH#10070 - make sure to not use the MRU order when switching
// tabs on the commandline. That wouldn't make any sense!
focusTabAction.Args(_focusNextTab ?
static_cast<IActionArgs>(NextTabArgs(TabSwitcherMode::Disabled)) :
static_cast<IActionArgs>(PrevTabArgs(TabSwitcherMode::Disabled)));
_startupActions.push_back(std::move(focusTabAction)); _startupActions.push_back(std::move(focusTabAction));
} }
}); });
@ -350,6 +394,15 @@ void AppCommandlineArgs::_buildFocusTabParser()
setupSubcommand(_focusTabShort); setupSubcommand(_focusTabShort);
} }
static const std::map<std::string, FocusDirection> focusDirectionMap = {
{ "left", FocusDirection::Left },
{ "right", FocusDirection::Right },
{ "up", FocusDirection::Up },
{ "down", FocusDirection::Down },
{ "nextInOrder", FocusDirection::NextInOrder },
{ "previousInOrder", FocusDirection::PreviousInOrder },
};
// Method Description: // Method Description:
// - Adds the `move-focus` subcommand and related options to the commandline parser. // - Adds the `move-focus` subcommand and related options to the commandline parser.
// - Additionally adds the `mf` subcommand, which is just a shortened version of `move-focus` // - Additionally adds the `mf` subcommand, which is just a shortened version of `move-focus`
@ -363,18 +416,11 @@ void AppCommandlineArgs::_buildMoveFocusParser()
_moveFocusShort = _app.add_subcommand("mf", RS_A(L"CmdMFDesc")); _moveFocusShort = _app.add_subcommand("mf", RS_A(L"CmdMFDesc"));
auto setupSubcommand = [this](auto* subcommand) { auto setupSubcommand = [this](auto* subcommand) {
std::map<std::string, FocusDirection> map = {
{ "left", FocusDirection::Left },
{ "right", FocusDirection::Right },
{ "up", FocusDirection::Up },
{ "down", FocusDirection::Down }
};
auto* directionOpt = subcommand->add_option("direction", auto* directionOpt = subcommand->add_option("direction",
_moveFocusDirection, _moveFocusDirection,
RS_A(L"CmdMoveFocusDirectionArgDesc")); RS_A(L"CmdMoveFocusDirectionArgDesc"));
directionOpt->transform(CLI::CheckedTransformer(map, CLI::ignore_case)); directionOpt->transform(CLI::CheckedTransformer(focusDirectionMap, CLI::ignore_case));
directionOpt->required(); directionOpt->required();
// When ParseCommand is called, if this subcommand was provided, this // When ParseCommand is called, if this subcommand was provided, this
// callback function will be triggered on the same thread. We can be sure // callback function will be triggered on the same thread. We can be sure
@ -398,6 +444,44 @@ void AppCommandlineArgs::_buildMoveFocusParser()
setupSubcommand(_moveFocusShort); setupSubcommand(_moveFocusShort);
} }
// Method Description:
// - Adds the `swap-pane` subcommand and related options to the commandline parser.
// Arguments:
// - <none>
// Return Value:
// - <none>
void AppCommandlineArgs::_buildSwapPaneParser()
{
_swapPaneCommand = _app.add_subcommand("swap-pane", RS_A(L"CmdSwapPaneDesc"));
auto setupSubcommand = [this](auto* subcommand) {
auto* directionOpt = subcommand->add_option("direction",
_swapPaneDirection,
RS_A(L"CmdSwapPaneDirectionArgDesc"));
directionOpt->transform(CLI::CheckedTransformer(focusDirectionMap, CLI::ignore_case));
directionOpt->required();
// When ParseCommand is called, if this subcommand was provided, this
// callback function will be triggered on the same thread. We can be sure
// that `this` will still be safe - this function just lets us know this
// command was parsed.
subcommand->callback([&, this]() {
if (_swapPaneDirection != FocusDirection::None)
{
SwapPaneArgs args{ _swapPaneDirection };
ActionAndArgs actionAndArgs{};
actionAndArgs.Action(ShortcutAction::SwapPane);
actionAndArgs.Args(args);
_startupActions.push_back(std::move(actionAndArgs));
}
});
};
setupSubcommand(_swapPaneCommand);
}
// Method Description: // Method Description:
// - Adds the `focus-pane` subcommand and related options to the commandline parser. // - Adds the `focus-pane` subcommand and related options to the commandline parser.
// - Additionally adds the `fp` subcommand, which is just a shortened version of `focus-pane` // - Additionally adds the `fp` subcommand, which is just a shortened version of `focus-pane`
@ -519,6 +603,13 @@ NewTerminalArgs AppCommandlineArgs::_getNewTerminalArgs(AppCommandlineArgs::NewT
args.Profile(winrt::to_hstring(_profileName)); args.Profile(winrt::to_hstring(_profileName));
} }
if (!*subcommand.profileNameOption && !_commandline.empty())
{
// If there's no profile, but there IS a command line, set the tab title to the first part of the command
// This will ensure that the tab we spawn has a name (since it didn't get one from its profile!)
args.TabTitle(winrt::to_hstring(til::at(_commandline, 0)));
}
if (*subcommand.startingDirectoryOption) if (*subcommand.startingDirectoryOption)
{ {
args.StartingDirectory(winrt::to_hstring(_startingDirectory)); args.StartingDirectory(winrt::to_hstring(_startingDirectory));
@ -574,6 +665,9 @@ bool AppCommandlineArgs::_noCommandsProvided()
*_focusTabShort || *_focusTabShort ||
*_moveFocusCommand || *_moveFocusCommand ||
*_moveFocusShort || *_moveFocusShort ||
*_movePaneCommand ||
*_movePaneShort ||
*_swapPaneCommand ||
*_focusPaneCommand || *_focusPaneCommand ||
*_focusPaneShort || *_focusPaneShort ||
*_newPaneShort.subcommand || *_newPaneShort.subcommand ||
@ -602,11 +696,13 @@ void AppCommandlineArgs::_resetStateToDefault()
_splitPaneSize = 0.5f; _splitPaneSize = 0.5f;
_splitDuplicate = false; _splitDuplicate = false;
_movePaneTabIndex = -1;
_focusTabIndex = -1; _focusTabIndex = -1;
_focusNextTab = false; _focusNextTab = false;
_focusPrevTab = false; _focusPrevTab = false;
_moveFocusDirection = FocusDirection::None; _moveFocusDirection = FocusDirection::None;
_swapPaneDirection = FocusDirection::None;
_focusPaneTarget = -1; _focusPaneTarget = -1;

View file

@ -82,6 +82,9 @@ private:
CLI::App* _focusTabShort; CLI::App* _focusTabShort;
CLI::App* _moveFocusCommand; CLI::App* _moveFocusCommand;
CLI::App* _moveFocusShort; CLI::App* _moveFocusShort;
CLI::App* _movePaneCommand;
CLI::App* _movePaneShort;
CLI::App* _swapPaneCommand;
CLI::App* _focusPaneCommand; CLI::App* _focusPaneCommand;
CLI::App* _focusPaneShort; CLI::App* _focusPaneShort;
@ -95,6 +98,7 @@ private:
bool _suppressApplicationTitle{ false }; bool _suppressApplicationTitle{ false };
winrt::Microsoft::Terminal::Settings::Model::FocusDirection _moveFocusDirection{ winrt::Microsoft::Terminal::Settings::Model::FocusDirection::None }; winrt::Microsoft::Terminal::Settings::Model::FocusDirection _moveFocusDirection{ winrt::Microsoft::Terminal::Settings::Model::FocusDirection::None };
winrt::Microsoft::Terminal::Settings::Model::FocusDirection _swapPaneDirection{ winrt::Microsoft::Terminal::Settings::Model::FocusDirection::None };
// _commandline will contain the command line with which we'll be spawning a new terminal // _commandline will contain the command line with which we'll be spawning a new terminal
std::vector<std::string> _commandline; std::vector<std::string> _commandline;
@ -104,6 +108,7 @@ private:
bool _splitDuplicate{ false }; bool _splitDuplicate{ false };
float _splitPaneSize{ 0.5f }; float _splitPaneSize{ 0.5f };
int _movePaneTabIndex{ -1 };
int _focusTabIndex{ -1 }; int _focusTabIndex{ -1 };
bool _focusNextTab{ false }; bool _focusNextTab{ false };
bool _focusPrevTab{ false }; bool _focusPrevTab{ false };
@ -128,6 +133,8 @@ private:
void _buildSplitPaneParser(); void _buildSplitPaneParser();
void _buildFocusTabParser(); void _buildFocusTabParser();
void _buildMoveFocusParser(); void _buildMoveFocusParser();
void _buildMovePaneParser();
void _buildSwapPaneParser();
void _buildFocusPaneParser(); void _buildFocusPaneParser();
bool _noCommandsProvided(); bool _noCommandsProvided();
void _resetStateToDefault(); void _resetStateToDefault();

View file

@ -21,6 +21,11 @@ namespace winrt::TerminalApp::implementation
return false; return false;
} }
bool AppKeyBindings::IsKeyChordExplicitlyUnbound(const KeyChord& kc)
{
return _actionMap.IsKeyChordExplicitlyUnbound(kc);
}
void AppKeyBindings::SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch) void AppKeyBindings::SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch)
{ {
_dispatch = dispatch; _dispatch = dispatch;

View file

@ -20,6 +20,7 @@ namespace winrt::TerminalApp::implementation
AppKeyBindings() = default; AppKeyBindings() = default;
bool TryKeyChord(winrt::Microsoft::Terminal::Control::KeyChord const& kc); bool TryKeyChord(winrt::Microsoft::Terminal::Control::KeyChord const& kc);
bool IsKeyChordExplicitlyUnbound(winrt::Microsoft::Terminal::Control::KeyChord const& kc);
void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch); void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch);
void SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap); void SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap);

View file

@ -203,12 +203,16 @@ namespace winrt::TerminalApp::implementation
_isElevated = _isUserAdmin(); _isElevated = _isUserAdmin();
_root = winrt::make_self<TerminalPage>(); _root = winrt::make_self<TerminalPage>();
_reloadSettings = std::make_shared<ThrottledFuncTrailing<>>(_root->Dispatcher(), std::chrono::milliseconds(100), [weakSelf = get_weak()]() { _reloadSettings = std::make_shared<ThrottledFuncTrailing<>>(winrt::Windows::System::DispatcherQueue::GetForCurrentThread(), std::chrono::milliseconds(100), [weakSelf = get_weak()]() {
if (auto self{ weakSelf.get() }) if (auto self{ weakSelf.get() })
{ {
self->_ReloadSettings(); self->_ReloadSettings();
} }
}); });
_languageProfileNotifier = winrt::make_self<LanguageProfileNotifier>([this]() {
_reloadSettings->Run();
});
} }
// Method Description: // Method Description:
@ -1125,28 +1129,11 @@ namespace winrt::TerminalApp::implementation
} }
} }
// Method Description: winrt::TerminalApp::TaskbarState AppLogic::TaskbarState()
// - Gets the taskbar state value from the last active control
// Return Value:
// - The taskbar state of the last active control
size_t AppLogic::GetLastActiveControlTaskbarState()
{ {
if (_root) if (_root)
{ {
return _root->GetLastActiveControlTaskbarState(); return _root->TaskbarState();
}
return {};
}
// Method Description:
// - Gets the taskbar progress value from the last active control
// Return Value:
// - The taskbar progress of the last active control
size_t AppLogic::GetLastActiveControlTaskbarProgress()
{
if (_root)
{
return _root->GetLastActiveControlTaskbarProgress();
} }
return {}; return {};
} }
@ -1229,6 +1216,11 @@ namespace winrt::TerminalApp::implementation
auto actions = winrt::single_threaded_vector<ActionAndArgs>(std::move(appArgs.GetStartupActions())); auto actions = winrt::single_threaded_vector<ActionAndArgs>(std::move(appArgs.GetStartupActions()));
_root->ProcessStartupActions(actions, false, cwd); _root->ProcessStartupActions(actions, false, cwd);
if (appArgs.IsHandoffListener())
{
_root->SetInboundListener(true);
}
} }
// Return the result of parsing with commandline, though it may or may not be used. // Return the result of parsing with commandline, though it may or may not be used.
return result; return result;
@ -1450,4 +1442,39 @@ namespace winrt::TerminalApp::implementation
return _root->IsQuakeWindow(); return _root->IsQuakeWindow();
} }
bool AppLogic::GetMinimizeToTray()
{
if constexpr (Feature_TrayIcon::IsEnabled())
{
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
}
return _settings.GlobalSettings().MinimizeToTray();
}
else
{
return false;
}
}
bool AppLogic::GetAlwaysShowTrayIcon()
{
if constexpr (Feature_TrayIcon::IsEnabled())
{
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
}
return _settings.GlobalSettings().AlwaysShowTrayIcon();
}
else
{
return false;
}
}
} }

View file

@ -5,8 +5,9 @@
#include "AppLogic.g.h" #include "AppLogic.g.h"
#include "FindTargetWindowResult.g.h" #include "FindTargetWindowResult.g.h"
#include "TerminalPage.h"
#include "Jumplist.h" #include "Jumplist.h"
#include "LanguageProfileNotifier.h"
#include "TerminalPage.h"
#include <inc/cppwinrt_utils.h> #include <inc/cppwinrt_utils.h>
#include <ThrottledFunc.h> #include <ThrottledFunc.h>
@ -89,8 +90,10 @@ namespace winrt::TerminalApp::implementation
void WindowCloseButtonClicked(); void WindowCloseButtonClicked();
size_t GetLastActiveControlTaskbarState(); winrt::TerminalApp::TaskbarState TaskbarState();
size_t GetLastActiveControlTaskbarProgress();
bool GetMinimizeToTray();
bool GetAlwaysShowTrayIcon();
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog); winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog);
@ -110,12 +113,8 @@ namespace winrt::TerminalApp::implementation
// ALSO: If you add any UIElements as roots here, make sure they're // ALSO: If you add any UIElements as roots here, make sure they're
// updated in _ApplyTheme. The root currently is _root. // updated in _ApplyTheme. The root currently is _root.
winrt::com_ptr<TerminalPage> _root{ nullptr }; winrt::com_ptr<TerminalPage> _root{ nullptr };
Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr }; Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr };
wil::unique_folder_change_reader_nothrow _reader;
std::shared_ptr<ThrottledFuncTrailing<>> _reloadSettings;
til::throttled_func_trailing<> _reloadState;
winrt::hstring _settingsLoadExceptionText; winrt::hstring _settingsLoadExceptionText;
HRESULT _settingsLoadedResult = S_OK; HRESULT _settingsLoadedResult = S_OK;
bool _loadedInitialSettings = false; bool _loadedInitialSettings = false;
@ -124,6 +123,15 @@ namespace winrt::TerminalApp::implementation
::TerminalApp::AppCommandlineArgs _appArgs; ::TerminalApp::AppCommandlineArgs _appArgs;
::TerminalApp::AppCommandlineArgs _settingsAppArgs; ::TerminalApp::AppCommandlineArgs _settingsAppArgs;
std::shared_ptr<ThrottledFuncTrailing<>> _reloadSettings;
til::throttled_func_trailing<> _reloadState;
// These fields invoke _reloadSettings and must be destroyed before _reloadSettings.
// (C++ destroys members in reverse-declaration-order.)
winrt::com_ptr<LanguageProfileNotifier> _languageProfileNotifier;
wil::unique_folder_change_reader_nothrow _reader;
static TerminalApp::FindTargetWindowResult _doFindTargetWindow(winrt::array_view<const hstring> args, static TerminalApp::FindTargetWindowResult _doFindTargetWindow(winrt::array_view<const hstring> args,
const Microsoft::Terminal::Settings::Model::WindowingMode& windowingBehavior); const Microsoft::Terminal::Settings::Model::WindowingMode& windowingBehavior);

View file

@ -68,8 +68,10 @@ namespace TerminalApp
void TitlebarClicked(); void TitlebarClicked();
void WindowCloseButtonClicked(); void WindowCloseButtonClicked();
UInt64 GetLastActiveControlTaskbarState(); TaskbarState TaskbarState{ get; };
UInt64 GetLastActiveControlTaskbarProgress();
Boolean GetMinimizeToTray();
Boolean GetAlwaysShowTrayIcon();
FindTargetWindowResult FindTargetWindow(String[] args); FindTargetWindowResult FindTargetWindow(String[] args);

View file

@ -250,10 +250,12 @@ namespace winrt::TerminalApp::implementation
void CommandPalette::_previewKeyDownHandler(IInspectable const& /*sender*/, void CommandPalette::_previewKeyDownHandler(IInspectable const& /*sender*/,
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e) Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
{ {
auto key = e.OriginalKey(); const auto key = e.OriginalKey();
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down); const auto scanCode = e.KeyStatus().ScanCode;
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down); const auto coreWindow = CoreWindow::GetForCurrentThread();
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down); const auto ctrlDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
const auto altDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
const auto shiftDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
// Some keypresses such as Tab, Return, Esc, and Arrow Keys are ignored by controls because // Some keypresses such as Tab, Return, Esc, and Arrow Keys are ignored by controls because
// they're not considered input key presses. While they don't raise KeyDown events, // they're not considered input key presses. While they don't raise KeyDown events,
@ -264,7 +266,7 @@ namespace winrt::TerminalApp::implementation
// a really widely used keyboard navigation key. // a really widely used keyboard navigation key.
if (_currentMode == CommandPaletteMode::TabSwitchMode && _actionMap) if (_currentMode == CommandPaletteMode::TabSwitchMode && _actionMap)
{ {
winrt::Microsoft::Terminal::Control::KeyChord kc{ ctrlDown, altDown, shiftDown, static_cast<int32_t>(key) }; winrt::Microsoft::Terminal::Control::KeyChord kc{ ctrlDown, altDown, shiftDown, false, static_cast<int32_t>(key), static_cast<int32_t>(scanCode) };
if (const auto cmd{ _actionMap.GetActionByKeyChord(kc) }) if (const auto cmd{ _actionMap.GetActionByKeyChord(kc) })
{ {
if (cmd.ActionAndArgs().Action() == ShortcutAction::PrevTab) if (cmd.ActionAndArgs().Action() == ShortcutAction::PrevTab)
@ -402,9 +404,10 @@ namespace winrt::TerminalApp::implementation
// - <none> // - <none>
void CommandPalette::_anchorKeyUpHandler() void CommandPalette::_anchorKeyUpHandler()
{ {
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down); const auto coreWindow = CoreWindow::GetForCurrentThread();
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down); const auto ctrlDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down); const auto altDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
const auto shiftDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
if (!ctrlDown && !altDown && !shiftDown) if (!ctrlDown && !altDown && !shiftDown)
{ {

View file

@ -19,6 +19,7 @@ namespace winrt::Microsoft::TerminalApp::implementation
_wrappedConnection{ std::move(wrappedConnection) } _wrappedConnection{ std::move(wrappedConnection) }
{ {
} }
void Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/) {}
~DebugInputTapConnection() = default; ~DebugInputTapConnection() = default;
void Start() void Start()
{ {

View file

@ -13,6 +13,7 @@ namespace winrt::Microsoft::TerminalApp::implementation
{ {
public: public:
explicit DebugTapConnection(Microsoft::Terminal::TerminalConnection::ITerminalConnection wrappedConnection); explicit DebugTapConnection(Microsoft::Terminal::TerminalConnection::ITerminalConnection wrappedConnection);
void Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/){};
~DebugTapConnection(); ~DebugTapConnection();
void Start(); void Start();
void WriteInput(hstring const& data); void WriteInput(hstring const& data);

View file

@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "LanguageProfileNotifier.h"
using namespace winrt::TerminalApp::implementation;
LanguageProfileNotifier::LanguageProfileNotifier(std::function<void()>&& callback) :
_callback{ std::move(callback) },
_currentKeyboardLayout{ GetKeyboardLayout(0) }
{
const auto manager = wil::CoCreateInstance<ITfThreadMgr>(CLSID_TF_ThreadMgr);
_source = manager.query<ITfSource>();
if (FAILED(_source->AdviseSink(IID_ITfInputProcessorProfileActivationSink, static_cast<ITfInputProcessorProfileActivationSink*>(this), &_cookie)))
{
_cookie = TF_INVALID_COOKIE;
THROW_LAST_ERROR();
}
}
LanguageProfileNotifier::~LanguageProfileNotifier()
{
if (_cookie != TF_INVALID_COOKIE)
{
_source->UnadviseSink(_cookie);
}
}
STDMETHODIMP LanguageProfileNotifier::OnActivated(DWORD /*dwProfileType*/, LANGID /*langid*/, REFCLSID /*clsid*/, REFGUID /*catid*/, REFGUID /*guidProfile*/, HKL hkl, DWORD /*dwFlags*/)
{
if (hkl && hkl != _currentKeyboardLayout)
{
_currentKeyboardLayout = hkl;
try
{
_callback();
}
CATCH_RETURN();
}
return S_OK;
}

View file

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
namespace winrt::TerminalApp::implementation
{
class LanguageProfileNotifier : public winrt::implements<LanguageProfileNotifier, ITfInputProcessorProfileActivationSink>
{
public:
explicit LanguageProfileNotifier(std::function<void()>&& callback);
~LanguageProfileNotifier();
STDMETHODIMP OnActivated(DWORD dwProfileType, LANGID langid, REFCLSID clsid, REFGUID catid, REFGUID guidProfile, HKL hkl, DWORD dwFlags);
private:
std::function<void()> _callback;
wil::com_ptr<ITfSource> _source;
DWORD _cookie = TF_INVALID_COOKIE;
HKL _currentKeyboardLayout;
};
}

File diff suppressed because it is too large Load diff

View file

@ -21,6 +21,18 @@
#pragma once #pragma once
#include "../../cascadia/inc/cppwinrt_utils.h" #include "../../cascadia/inc/cppwinrt_utils.h"
#include "TaskbarState.h"
// fwdecl unittest classes
namespace TerminalAppLocalTests
{
class TabTests;
};
namespace winrt::TerminalApp::implementation
{
struct TerminalTab;
}
enum class Borders : int enum class Borders : int
{ {
@ -35,13 +47,21 @@ DEFINE_ENUM_FLAG_OPERATORS(Borders);
class Pane : public std::enable_shared_from_this<Pane> class Pane : public std::enable_shared_from_this<Pane>
{ {
public: public:
Pane(const GUID& profile, Pane(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile,
const winrt::Microsoft::Terminal::Control::TermControl& control, const winrt::Microsoft::Terminal::Control::TermControl& control,
const bool lastFocused = false); const bool lastFocused = false);
std::shared_ptr<Pane> GetActivePane(); std::shared_ptr<Pane> GetActivePane();
winrt::Microsoft::Terminal::Control::TermControl GetTerminalControl(); winrt::Microsoft::Terminal::Control::TermControl GetTerminalControl();
std::optional<GUID> GetFocusedProfile(); winrt::Microsoft::Terminal::Settings::Model::Profile GetFocusedProfile();
// Method Description:
// - If this is a leaf pane, return its profile.
// - If this is a branch/root pane, return nullptr.
winrt::Microsoft::Terminal::Settings::Model::Profile GetProfile() const
{
return _profile;
}
winrt::Windows::UI::Xaml::Controls::Grid GetRootElement(); winrt::Windows::UI::Xaml::Controls::Grid GetRootElement();
@ -51,16 +71,21 @@ public:
void SetActive(); void SetActive();
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings,
const GUID& profile); const winrt::Microsoft::Terminal::Settings::Model::Profile& profile);
void ResizeContent(const winrt::Windows::Foundation::Size& newSize); void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
void Relayout(); void Relayout();
bool ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction); bool ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
bool NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction); std::shared_ptr<Pane> NavigateDirection(const std::shared_ptr<Pane> sourcePane, const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool SwapPanes(std::shared_ptr<Pane> first, std::shared_ptr<Pane> second);
std::shared_ptr<Pane> NextPane(const std::shared_ptr<Pane> pane);
std::shared_ptr<Pane> PreviousPane(const std::shared_ptr<Pane> pane);
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Split(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType, std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Split(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType,
const float splitSize, const float splitSize,
const GUID& profile, const winrt::Microsoft::Terminal::Settings::Model::Profile& profile,
const winrt::Microsoft::Terminal::Control::TermControl& control); const winrt::Microsoft::Terminal::Control::TermControl& control);
bool ToggleSplitOrientation();
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
std::optional<winrt::Microsoft::Terminal::Settings::Model::SplitState> PreCalculateAutoSplit(const std::shared_ptr<Pane> target, std::optional<winrt::Microsoft::Terminal::Settings::Model::SplitState> PreCalculateAutoSplit(const std::shared_ptr<Pane> target,
const winrt::Windows::Foundation::Size parentSize) const; const winrt::Windows::Foundation::Size parentSize) const;
@ -71,6 +96,10 @@ public:
void Shutdown(); void Shutdown();
void Close(); void Close();
std::shared_ptr<Pane> AttachPane(std::shared_ptr<Pane> pane,
winrt::Microsoft::Terminal::Settings::Model::SplitState splitType);
std::shared_ptr<Pane> DetachPane(std::shared_ptr<Pane> pane);
int GetLeafPaneCount() const noexcept; int GetLeafPaneCount() const noexcept;
void Maximize(std::shared_ptr<Pane> zoomedPane); void Maximize(std::shared_ptr<Pane> zoomedPane);
@ -79,15 +108,47 @@ public:
std::optional<uint32_t> Id() noexcept; std::optional<uint32_t> Id() noexcept;
void Id(uint32_t id) noexcept; void Id(uint32_t id) noexcept;
bool FocusPane(const uint32_t id); bool FocusPane(const uint32_t id);
bool FocusPane(const std::shared_ptr<Pane> pane);
std::shared_ptr<Pane> FindPane(const uint32_t id);
bool ContainsReadOnly() const; bool ContainsReadOnly() const;
// Method Description:
// - A helper method for ad-hoc recursion on a pane tree. Walks the pane
// tree, calling a predicate on each pane in a depth-first pattern.
// - If the predicate returns true, recursion is stopped early.
// Arguments:
// - f: The function to be applied to each pane.
// Return Value:
// - true if the predicate returned true on any pane.
template<typename F>
//requires std::predicate<F, std::shared_ptr<Pane>>
bool WalkTree(F f)
{
if (f(shared_from_this()))
{
return true;
}
if (!_IsLeaf())
{
return _firstChild->WalkTree(f) || _secondChild->WalkTree(f);
}
return false;
}
void CollectTaskbarStates(std::vector<winrt::TerminalApp::TaskbarState>& states);
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>); WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>); DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
DECLARE_EVENT(LostFocus, _LostFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>); DECLARE_EVENT(LostFocus, _LostFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
DECLARE_EVENT(PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler<bool>); DECLARE_EVENT(PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler<bool>);
DECLARE_EVENT(Detached, _PaneDetachedHandlers, winrt::delegate<std::shared_ptr<Pane>>);
private: private:
struct PanePoint;
struct PaneNeighborSearch;
struct SnapSizeResult; struct SnapSizeResult;
struct SnapChildrenSizeResult; struct SnapChildrenSizeResult;
struct LayoutSizeNode; struct LayoutSizeNode;
@ -107,7 +168,7 @@ private:
std::optional<uint32_t> _id; std::optional<uint32_t> _id;
bool _lastActive{ false }; bool _lastActive{ false };
std::optional<GUID> _profile{ std::nullopt }; winrt::Microsoft::Terminal::Settings::Model::Profile _profile{ nullptr };
winrt::event_token _connectionStateChangedToken{ 0 }; winrt::event_token _connectionStateChangedToken{ 0 };
winrt::event_token _firstClosedToken{ 0 }; winrt::event_token _firstClosedToken{ 0 };
winrt::event_token _secondClosedToken{ 0 }; winrt::event_token _secondClosedToken{ 0 };
@ -128,16 +189,25 @@ private:
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> _Split(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType, std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> _Split(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType,
const float splitSize, const float splitSize,
const GUID& profile, std::shared_ptr<Pane> newPane);
const winrt::Microsoft::Terminal::Control::TermControl& control);
void _CreateRowColDefinitions(); void _CreateRowColDefinitions();
void _ApplySplitDefinitions(); void _ApplySplitDefinitions();
void _SetupEntranceAnimation(); void _SetupEntranceAnimation();
void _UpdateBorders(); void _UpdateBorders();
Borders _GetCommonBorders();
bool _Resize(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction); bool _Resize(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
bool _NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
std::shared_ptr<Pane> _FindParentOfPane(const std::shared_ptr<Pane> pane);
bool _IsAdjacent(const std::shared_ptr<Pane> first, const PanePoint firstOffset, const std::shared_ptr<Pane> second, const PanePoint secondOffset, const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction) const;
PaneNeighborSearch _FindNeighborForPane(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction,
PaneNeighborSearch searchResult,
const bool focusIsSecondSide,
const PanePoint offset);
PaneNeighborSearch _FindPaneAndNeighbor(const std::shared_ptr<Pane> sourcePane,
const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction,
const PanePoint offset);
void _CloseChild(const bool closeFirst); void _CloseChild(const bool closeFirst);
winrt::fire_and_forget _CloseChildRoutine(const bool closeFirst); winrt::fire_and_forget _CloseChildRoutine(const bool closeFirst);
@ -200,6 +270,19 @@ private:
static void _SetupResources(); static void _SetupResources();
struct PanePoint
{
float x;
float y;
};
struct PaneNeighborSearch
{
std::shared_ptr<Pane> source;
std::shared_ptr<Pane> neighbor;
PanePoint sourceOffset;
};
struct SnapSizeResult struct SnapSizeResult
{ {
float lower; float lower;
@ -236,4 +319,7 @@ private:
private: private:
void _AssignChildNode(std::unique_ptr<LayoutSizeNode>& nodeField, const LayoutSizeNode* const newNode); void _AssignChildNode(std::unique_ptr<LayoutSizeNode>& nodeField, const LayoutSizeNode* const newNode);
}; };
friend struct winrt::TerminalApp::implementation::TerminalTab;
friend class ::TerminalAppLocalTests::TabTests;
}; };

View file

@ -282,6 +282,16 @@
<data name="CmdFocusTabTargetArgDesc" xml:space="preserve"> <data name="CmdFocusTabTargetArgDesc" xml:space="preserve">
<value>Move focus the tab at the given index</value> <value>Move focus the tab at the given index</value>
</data> </data>
<data name="CmdMovePaneTabArgDesc" xml:space="preserve">
<value>Move focused pane to the tab at the given index</value>
</data>
<data name="CmdMovePaneDesc" xml:space="preserve">
<value>Move focused pane to another tab</value>
</data>
<data name="CmdMPDesc" xml:space="preserve">
<value>An alias for the "move-pane" subcommand.</value>
<comment>{Locked="\"move-pane\""}</comment>
</data>
<data name="CmdSplitPaneSizeArgDesc" xml:space="preserve"> <data name="CmdSplitPaneSizeArgDesc" xml:space="preserve">
<value>Specify the size as a percentage of the parent pane. Valid values are between (0,1), exclusive.</value> <value>Specify the size as a percentage of the parent pane. Valid values are between (0,1), exclusive.</value>
</data> </data>
@ -359,6 +369,12 @@
<data name="CmdMoveFocusDirectionArgDesc" xml:space="preserve"> <data name="CmdMoveFocusDirectionArgDesc" xml:space="preserve">
<value>The direction to move focus in</value> <value>The direction to move focus in</value>
</data> </data>
<data name="CmdSwapPaneDesc" xml:space="preserve">
<value>Swap the focused pane with the adjacent pane in the specified direction</value>
</data>
<data name="CmdSwapPaneDirectionArgDesc" xml:space="preserve">
<value>The direction to move the focused pane in</value>
</data>
<data name="CmdFocusDesc" xml:space="preserve"> <data name="CmdFocusDesc" xml:space="preserve">
<value>Launch the window in focus mode</value> <value>Launch the window in focus mode</value>
</data> </data>
@ -637,6 +653,14 @@
<data name="CommandPaletteMenuItem" xml:space="preserve"> <data name="CommandPaletteMenuItem" xml:space="preserve">
<value>Command Palette</value> <value>Command Palette</value>
</data> </data>
<data name="TrayIconFocusTerminal" xml:space="preserve">
<value>Focus Terminal</value>
<comment>This is displayed as a label for the context menu item that focuses the terminal.</comment>
</data>
<data name="TrayIconWindowSubmenu" xml:space="preserve">
<value>Windows</value>
<comment>This is displayed as a label for the context menu item that holds the submenu of available windows.</comment>
</data>
<data name="ShellExtension_OpenInTerminalMenuItem_Dev" xml:space="preserve"> <data name="ShellExtension_OpenInTerminalMenuItem_Dev" xml:space="preserve">
<value>Open in Windows Terminal (Dev)</value> <value>Open in Windows Terminal (Dev)</value>
<comment>{Locked} The dev build will never be seen in multiple languages</comment> <comment>{Locked} The dev build will never be seen in multiple languages</comment>
@ -649,4 +673,16 @@
<value>Open in Windows Terminal</value> <value>Open in Windows Terminal</value>
<comment>{Locked="Windows"} This is a menu item that will be displayed in the Windows File Explorer that launches the non-preview version of Windows Terminal</comment> <comment>{Locked="Windows"} This is a menu item that will be displayed in the Windows File Explorer that launches the non-preview version of Windows Terminal</comment>
</data> </data>
<data name="DropPathTabRun.Text" xml:space="preserve">
<value>Open a new tab in given starting directory</value>
</data>
<data name="SplitTabText" xml:space="preserve">
<value>Split Tab</value>
</data>
<data name="DropPathTabNewWindow.Text" xml:space="preserve">
<value>Open a new window with given starting directory</value>
</data>
<data name="DropPathTabSplit.Text" xml:space="preserve">
<value>Split the window and start in given directory</value>
</data>
</root> </root>

View file

@ -56,13 +56,13 @@ namespace winrt::TerminalApp::implementation
// - existingConnection: An optional connection that is already established to a PTY // - existingConnection: An optional connection that is already established to a PTY
// for this tab to host instead of creating one. // for this tab to host instead of creating one.
// If not defined, the tab will create the connection. // If not defined, the tab will create the connection.
void TerminalPage::_OpenNewTab(const NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection) HRESULT TerminalPage::_OpenNewTab(const NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection)
try try
{ {
const auto profileGuid{ _settings.GetProfileForArgs(newTerminalArgs) }; const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) };
const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings) }; const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings) };
_CreateNewTabFromSettings(profileGuid, settings, existingConnection); _CreateNewTabWithProfileAndSettings(profile, settings, existingConnection);
const uint32_t tabCount = _tabs.Size(); const uint32_t tabCount = _tabs.Size();
const bool usedManualProfile = (newTerminalArgs != nullptr) && const bool usedManualProfile = (newTerminalArgs != nullptr) &&
@ -70,7 +70,7 @@ namespace winrt::TerminalApp::implementation
newTerminalArgs.Profile().empty()); newTerminalArgs.Profile().empty());
// Lookup the name of the color scheme used by this profile. // Lookup the name of the color scheme used by this profile.
const auto scheme = _settings.GetColorSchemeForProfile(profileGuid); const auto scheme = _settings.GetColorSchemeForProfile(profile);
// If they explicitly specified `null` as the scheme (indicating _no_ scheme), log // If they explicitly specified `null` as the scheme (indicating _no_ scheme), log
// that as the empty string. // that as the empty string.
const auto schemeName = scheme ? scheme.Name() : L"\0"; const auto schemeName = scheme ? scheme.Name() : L"\0";
@ -82,48 +82,25 @@ namespace winrt::TerminalApp::implementation
TraceLoggingUInt32(1u, "EventVer", "Version of this event"), TraceLoggingUInt32(1u, "EventVer", "Version of this event"),
TraceLoggingUInt32(tabCount, "TabCount", "Count of tabs currently opened in TerminalApp"), TraceLoggingUInt32(tabCount, "TabCount", "Count of tabs currently opened in TerminalApp"),
TraceLoggingBool(usedManualProfile, "ProfileSpecified", "Whether the new tab specified a profile explicitly"), TraceLoggingBool(usedManualProfile, "ProfileSpecified", "Whether the new tab specified a profile explicitly"),
TraceLoggingGuid(profileGuid, "ProfileGuid", "The GUID of the profile spawned in the new tab"), TraceLoggingGuid(profile.Guid(), "ProfileGuid", "The GUID of the profile spawned in the new tab"),
TraceLoggingBool(settings.DefaultSettings().UseAcrylic(), "UseAcrylic", "The acrylic preference from the settings"), TraceLoggingBool(settings.DefaultSettings().UseAcrylic(), "UseAcrylic", "The acrylic preference from the settings"),
TraceLoggingFloat64(settings.DefaultSettings().TintOpacity(), "TintOpacity", "Opacity preference from the settings"), TraceLoggingFloat64(settings.DefaultSettings().TintOpacity(), "TintOpacity", "Opacity preference from the settings"),
TraceLoggingWideString(settings.DefaultSettings().FontFace().c_str(), "FontFace", "Font face chosen in the settings"), TraceLoggingWideString(settings.DefaultSettings().FontFace().c_str(), "FontFace", "Font face chosen in the settings"),
TraceLoggingWideString(schemeName.data(), "SchemeName", "Color scheme set in the settings"), TraceLoggingWideString(schemeName.data(), "SchemeName", "Color scheme set in the settings"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
return S_OK;
} }
CATCH_LOG(); CATCH_RETURN();
// Method Description: // Method Description:
// - Creates a new tab with the given settings. If the tab bar is not being // - Sets up state, event handlers, etc on a tab object that was just made.
// currently displayed, it will be shown.
// Arguments: // Arguments:
// - profileGuid: ID to use to lookup profile settings for this connection // - newTabImpl: the uninitialized tab.
// - settings: the TerminalSettings object to use to create the TerminalControl with. void TerminalPage::_InitializeTab(winrt::com_ptr<TerminalTab> newTabImpl)
// - existingConnection: optionally receives a connection from the outside world instead of attempting to create one
void TerminalPage::_CreateNewTabFromSettings(GUID profileGuid, const TerminalSettingsCreateResult& settings, TerminalConnection::ITerminalConnection existingConnection)
{ {
// Initialize the new tab newTabImpl->Initialize();
// Create a connection based on the values in our settings object if we weren't given one.
auto connection = existingConnection ? existingConnection : _CreateConnectionFromSettings(profileGuid, settings.DefaultSettings());
TerminalConnection::ITerminalConnection debugConnection{ nullptr };
if (_settings.GlobalSettings().DebugFeaturesEnabled())
{
const CoreWindow window = CoreWindow::GetForCurrentThread();
const auto rAltState = window.GetKeyState(VirtualKey::RightMenu);
const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu);
const bool bothAltsPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) &&
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
if (bothAltsPressed)
{
std::tie(connection, debugConnection) = OpenDebugTapConnection(connection);
}
}
// Give term control a child of the settings so that any overrides go in the child
// This way, when we do a settings reload we just update the parent and the overrides remain
auto term = _InitControl(settings, connection);
auto newTabImpl = winrt::make_self<TerminalTab>(profileGuid, term);
// Add the new tab to the list of our tabs. // Add the new tab to the list of our tabs.
_tabs.Append(*newTabImpl); _tabs.Append(*newTabImpl);
@ -136,7 +113,7 @@ namespace winrt::TerminalApp::implementation
_UpdateTabIndices(); _UpdateTabIndices();
// Hookup our event handlers to the new terminal // Hookup our event handlers to the new terminal
_RegisterTerminalEvents(term, *newTabImpl); _RegisterTabEvents(*newTabImpl);
// Don't capture a strong ref to the tab. If the tab is removed as this // Don't capture a strong ref to the tab. If the tab is removed as this
// is called, we don't really care anymore about handling the event. // is called, we don't really care anymore about handling the event.
@ -184,14 +161,26 @@ namespace winrt::TerminalApp::implementation
} }
}); });
newTabImpl->SplitTabRequested([weakTab, weakThis{ get_weak() }]() {
auto page{ weakThis.get() };
auto tab{ weakTab.get() };
if (page && tab)
{
page->_SplitTab(*tab);
}
});
auto tabViewItem = newTabImpl->TabViewItem(); auto tabViewItem = newTabImpl->TabViewItem();
_tabView.TabItems().Append(tabViewItem); _tabView.TabItems().Append(tabViewItem);
// Set this tab's icon to the icon from the user's profile // Set this tab's icon to the icon from the user's profile
const auto profile = _settings.FindProfile(profileGuid); if (const auto profile{ newTabImpl->GetFocusedProfile() })
if (profile != nullptr && !profile.Icon().empty())
{ {
newTabImpl->UpdateIcon(profile.Icon()); if (!profile.Icon().empty())
{
newTabImpl->UpdateIcon(profile.Icon());
}
} }
tabViewItem.PointerReleased({ this, &TerminalPage::_OnTabClick }); tabViewItem.PointerReleased({ this, &TerminalPage::_OnTabClick });
@ -224,19 +213,73 @@ namespace winrt::TerminalApp::implementation
} }
}); });
if (debugConnection) // this will only be set if global debugging is on and tap is active
{
auto newControl = _InitControl(settings, debugConnection);
_RegisterTerminalEvents(newControl, *newTabImpl);
// Split (auto) with the debug tap.
newTabImpl->SplitPane(SplitState::Automatic, 0.5f, profileGuid, newControl);
}
// This kicks off TabView::SelectionChanged, in response to which // This kicks off TabView::SelectionChanged, in response to which
// we'll attach the terminal's Xaml control to the Xaml root. // we'll attach the terminal's Xaml control to the Xaml root.
_tabView.SelectedItem(tabViewItem); _tabView.SelectedItem(tabViewItem);
} }
// Method Description:
// - Create a new tab using a specified pane as the root.
// Arguments:
// - pane: The pane to use as the root.
void TerminalPage::_CreateNewTabFromPane(std::shared_ptr<Pane> pane)
{
auto newTabImpl = winrt::make_self<TerminalTab>(pane);
_InitializeTab(newTabImpl);
}
// Method Description:
// - Creates a new tab with the given settings. If the tab bar is not being
// currently displayed, it will be shown.
// Arguments:
// - profile: profile settings for this connection
// - settings: the TerminalSettings object to use to create the TerminalControl with.
// - existingConnection: optionally receives a connection from the outside world instead of attempting to create one
void TerminalPage::_CreateNewTabWithProfileAndSettings(const Profile& profile, const TerminalSettingsCreateResult& settings, TerminalConnection::ITerminalConnection existingConnection)
{
// Initialize the new tab
// Create a connection based on the values in our settings object if we weren't given one.
auto connection = existingConnection ? existingConnection : _CreateConnectionFromSettings(profile, settings.DefaultSettings());
// If we had an `existingConnection`, then this is an inbound handoff from somewhere else.
// We need to tell it about our size information so it can match the dimensions of what
// we are about to present.
if (existingConnection)
{
connection.Resize(settings.DefaultSettings().InitialRows(), settings.DefaultSettings().InitialCols());
}
TerminalConnection::ITerminalConnection debugConnection{ nullptr };
if (_settings.GlobalSettings().DebugFeaturesEnabled())
{
const CoreWindow window = CoreWindow::GetForCurrentThread();
const auto rAltState = window.GetKeyState(VirtualKey::RightMenu);
const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu);
const bool bothAltsPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) &&
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
if (bothAltsPressed)
{
std::tie(connection, debugConnection) = OpenDebugTapConnection(connection);
}
}
// Give term control a child of the settings so that any overrides go in the child
// This way, when we do a settings reload we just update the parent and the overrides remain
auto term = _InitControl(settings, connection);
auto newTabImpl = winrt::make_self<TerminalTab>(profile, term);
_RegisterTerminalEvents(term);
_InitializeTab(newTabImpl);
if (debugConnection) // this will only be set if global debugging is on and tap is active
{
auto newControl = _InitControl(settings, debugConnection);
_RegisterTerminalEvents(newControl);
// Split (auto) with the debug tap.
newTabImpl->SplitPane(SplitState::Automatic, 0.5f, profile, newControl);
}
}
// Method Description: // Method Description:
// - Get the icon of the currently focused terminal control, and set its // - Get the icon of the currently focused terminal control, and set its
// tab's icon to that icon. // tab's icon to that icon.
@ -244,19 +287,9 @@ namespace winrt::TerminalApp::implementation
// - tab: the Tab to update the title for. // - tab: the Tab to update the title for.
void TerminalPage::_UpdateTabIcon(TerminalTab& tab) void TerminalPage::_UpdateTabIcon(TerminalTab& tab)
{ {
const auto lastFocusedProfileOpt = tab.GetFocusedProfile(); if (const auto profile = tab.GetFocusedProfile())
if (lastFocusedProfileOpt.has_value())
{ {
const auto lastFocusedProfile = lastFocusedProfileOpt.value(); tab.UpdateIcon(profile.Icon());
const auto matchingProfile = _settings.FindProfile(lastFocusedProfile);
if (matchingProfile)
{
tab.UpdateIcon(matchingProfile.Icon());
}
else
{
tab.UpdateIcon({});
}
} }
} }
@ -310,23 +343,16 @@ namespace winrt::TerminalApp::implementation
{ {
try try
{ {
// TODO: GH#5047 - In the future, we should get the Profile of // TODO: GH#5047 - We're duplicating the whole profile, which might
// the focused pane, and use that to build a new instance of the // be a dangling reference to old settings.
// settings so we can duplicate this tab/pane.
// //
// Currently, if the profile doesn't exist anymore in our // In the future, it may be preferable to just duplicate the
// settings, we'll silently do nothing. // current control's live settings (which will include changes
// // made through VT).
// In the future, it will be preferable to just duplicate the
// current control's settings, but we can't do that currently,
// because we won't be able to create a new instance of the
// connection without keeping an instance of the original Profile
// object around.
const auto& profileGuid = tab.GetFocusedProfile(); if (const auto profile = tab.GetFocusedProfile())
if (profileGuid.has_value())
{ {
const auto settingsCreateResult{ TerminalSettings::CreateWithProfileByID(_settings, profileGuid.value(), *_bindings) }; const auto settingsCreateResult{ TerminalSettings::CreateWithProfile(_settings, profile, *_bindings) };
const auto workingDirectory = tab.GetActiveTerminalControl().WorkingDirectory(); const auto workingDirectory = tab.GetActiveTerminalControl().WorkingDirectory();
const auto validWorkingDirectory = !workingDirectory.empty(); const auto validWorkingDirectory = !workingDirectory.empty();
if (validWorkingDirectory) if (validWorkingDirectory)
@ -334,7 +360,7 @@ namespace winrt::TerminalApp::implementation
settingsCreateResult.DefaultSettings().StartingDirectory(workingDirectory); settingsCreateResult.DefaultSettings().StartingDirectory(workingDirectory);
} }
_CreateNewTabFromSettings(profileGuid.value(), settingsCreateResult); _CreateNewTabWithProfileAndSettings(profile, settingsCreateResult);
const auto runtimeTabText{ tab.GetTabText() }; const auto runtimeTabText{ tab.GetTabText() };
if (!runtimeTabText.empty()) if (!runtimeTabText.empty())
@ -349,6 +375,20 @@ namespace winrt::TerminalApp::implementation
CATCH_LOG(); CATCH_LOG();
} }
// Method Description:
// - Sets the specified tab as the focused tab and splits its active pane
// Arguments:
// - tab: tab to split
void TerminalPage::_SplitTab(TerminalTab& tab)
{
try
{
_SetFocusedTab(tab);
_SplitPane(tab, SplitState::Automatic, SplitType::Duplicate);
}
CATCH_LOG();
}
// Method Description: // Method Description:
// - Removes the tab (both TerminalControl and XAML) after prompting for approval // - Removes the tab (both TerminalControl and XAML) after prompting for approval
// Arguments: // Arguments:
@ -497,24 +537,25 @@ namespace winrt::TerminalApp::implementation
// TerminalPage::_OnTabSelectionChanged // TerminalPage::_OnTabSelectionChanged
// Return Value: // Return Value:
// true iff we were able to select that tab index, false otherwise // true iff we were able to select that tab index, false otherwise
bool TerminalPage::_SelectTab(const uint32_t tabIndex) bool TerminalPage::_SelectTab(uint32_t tabIndex)
{ {
if (tabIndex >= 0 && tabIndex < _tabs.Size()) // GH#9369 - if the argument is out of range, then clamp to the number
{ // of available tabs. Previously, we'd just silently do nothing if the
auto tab{ _tabs.GetAt(tabIndex) }; // value was greater than the number of tabs.
if (_startupState == StartupState::InStartup) tabIndex = std::clamp(tabIndex, 0u, _tabs.Size() - 1);
{
_tabView.SelectedItem(tab.TabViewItem());
_UpdatedSelectedTab(tab);
}
else
{
_SetFocusedTab(tab);
}
return true; auto tab{ _tabs.GetAt(tabIndex) };
if (_startupState == StartupState::InStartup)
{
_tabView.SelectedItem(tab.TabViewItem());
_UpdatedSelectedTab(tab);
} }
return false; else
{
_SetFocusedTab(tab);
}
return true;
} }
// Method Description: // Method Description:

View file

@ -5,9 +5,19 @@
#include "TabRowControl.h" #include "TabRowControl.h"
#include "TabRowControl.g.cpp" #include "TabRowControl.g.cpp"
#include <LibraryResources.h>
using namespace winrt::Windows::ApplicationModel::DataTransfer;
using namespace winrt; using namespace winrt;
using namespace winrt::Microsoft::UI::Xaml; using namespace winrt::Microsoft::UI::Xaml;
using namespace winrt::Windows::UI::Text;
namespace winrt
{
namespace MUX = Microsoft::UI::Xaml;
namespace WUX = Windows::UI::Xaml;
}
namespace winrt::TerminalApp::implementation namespace winrt::TerminalApp::implementation
{ {
@ -23,4 +33,53 @@ namespace winrt::TerminalApp::implementation
void TabRowControl::OnNewTabButtonClick(IInspectable const&, Controls::SplitButtonClickEventArgs const&) void TabRowControl::OnNewTabButtonClick(IInspectable const&, Controls::SplitButtonClickEventArgs const&)
{ {
} }
// Method Description:
// - Bound in Drag&Drop of the Xaml editor to the [+] button.
// Arguments:
// <unused>
void TabRowControl::OnNewTabButtonDrop(IInspectable const&, winrt::Windows::UI::Xaml::DragEventArgs const&)
{
}
// Method Description:
// - Bound in Drag-over of the Xaml editor to the [+] button.
// Allows drop of 'StorageItems' which will be used as StartingDirectory
// Arguments:
// - <unused>
// - e: DragEventArgs which hold the items
void TabRowControl::OnNewTabButtonDragOver(IInspectable const&, winrt::Windows::UI::Xaml::DragEventArgs const& e)
{
// We can only handle drag/dropping StorageItems (files).
// If the format on the clipboard is anything else, returning
// early here will prevent the drag/drop from doing anything.
if (!e.DataView().Contains(StandardDataFormats::StorageItems()))
{
return;
}
// Make sure to set the AcceptedOperation, so that we can later receive the path in the Drop event
e.AcceptedOperation(DataPackageOperation::Copy);
const auto modifiers = static_cast<uint32_t>(e.Modifiers());
if (WI_IsFlagSet(modifiers, static_cast<uint32_t>(DragDrop::DragDropModifiers::Alt)))
{
e.DragUIOverride().Caption(RS_(L"DropPathTabSplit/Text"));
}
else if (WI_IsFlagSet(modifiers, static_cast<uint32_t>(DragDrop::DragDropModifiers::Shift)))
{
e.DragUIOverride().Caption(RS_(L"DropPathTabNewWindow/Text"));
}
else
{
e.DragUIOverride().Caption(RS_(L"DropPathTabRun/Text"));
}
// Sets if the caption is visible
e.DragUIOverride().IsCaptionVisible(true);
// Sets if the dragged content is visible
e.DragUIOverride().IsContentVisible(false);
// Sets if the glyph is visible
e.DragUIOverride().IsGlyphVisible(false);
}
} }

View file

@ -14,6 +14,8 @@ namespace winrt::TerminalApp::implementation
TabRowControl(); TabRowControl();
void OnNewTabButtonClick(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::Controls::SplitButtonClickEventArgs const& args); void OnNewTabButtonClick(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::Controls::SplitButtonClickEventArgs const& args);
void OnNewTabButtonDrop(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::DragEventArgs const& e);
void OnNewTabButtonDragOver(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::DragEventArgs const& e);
}; };
} }

View file

@ -25,11 +25,14 @@
x:Uid="NewTabSplitButton" x:Uid="NewTabSplitButton"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
AllowDrop="True"
AutomationProperties.AccessibilityView="Control" AutomationProperties.AccessibilityView="Control"
BorderThickness="0" BorderThickness="0"
Click="OnNewTabButtonClick" Click="OnNewTabButtonClick"
Content="&#xE710;" Content="&#xE710;"
CornerRadius="{Binding Source={ThemeResource OverlayCornerRadius}, Converter={StaticResource TopCornerRadiusFilterConverter}}" CornerRadius="{Binding Source={ThemeResource OverlayCornerRadius}, Converter={StaticResource TopCornerRadiusFilterConverter}}"
DragOver="OnNewTabButtonDragOver"
Drop="OnNewTabButtonDrop"
FontFamily="Segoe MDL2 Assets" FontFamily="Segoe MDL2 Assets"
FontSize="12" FontSize="12"
FontWeight="SemiLight" FontWeight="SemiLight"

View file

@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "TaskbarState.h"
#include "TaskbarState.g.cpp"
namespace winrt::TerminalApp::implementation
{
// Default to unset, 0%.
TaskbarState::TaskbarState() :
TaskbarState(0, 0){};
TaskbarState::TaskbarState(const uint64_t dispatchTypesState, const uint64_t progressParam) :
_State{ dispatchTypesState },
_Progress{ progressParam } {}
uint64_t TaskbarState::Priority() const
{
// This seemingly nonsensical ordering is from
// https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist3-setprogressstate#how-the-taskbar-button-chooses-the-progress-indicator-for-a-group
switch (_State)
{
case 0: // Clear = 0,
return 5;
case 1: // Set = 1,
return 3;
case 2: // Error = 2,
return 1;
case 3: // Indeterminate = 3,
return 4;
case 4: // Paused = 4
return 2;
}
// Here, return 6, to definitely be greater than all the other valid values.
// This should never really happen.
return 6;
}
int TaskbarState::ComparePriority(const winrt::TerminalApp::TaskbarState& lhs, const winrt::TerminalApp::TaskbarState& rhs)
{
return lhs.Priority() < rhs.Priority();
}
}

View file

@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "inc/cppwinrt_utils.h"
#include "TaskbarState.g.h"
// fwdecl unittest classes
namespace TerminalAppLocalTests
{
class TabTests;
};
namespace winrt::TerminalApp::implementation
{
struct TaskbarState : TaskbarStateT<TaskbarState>
{
public:
TaskbarState();
TaskbarState(const uint64_t dispatchTypesState, const uint64_t progress);
static int ComparePriority(const winrt::TerminalApp::TaskbarState& lhs, const winrt::TerminalApp::TaskbarState& rhs);
uint64_t Priority() const;
WINRT_PROPERTY(uint64_t, State, 0);
WINRT_PROPERTY(uint64_t, Progress, 0);
};
}
namespace winrt::TerminalApp::factory_implementation
{
BASIC_FACTORY(TaskbarState);
}

View file

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace TerminalApp
{
[default_interface] runtimeclass TaskbarState
{
TaskbarState();
TaskbarState(UInt64 dispatchTypesState, UInt64 progress);
UInt64 State{ get; };
UInt64 Progress{ get; };
UInt64 Priority { get; };
}
}

View file

@ -53,7 +53,7 @@
<Page Include="TabRowControl.xaml"> <Page Include="TabRowControl.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Page> </Page>
<Page Include="TabHeaderControl.xaml"> <Page Include="TabHeaderControl.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Page> </Page>
<Page Include="HighlightedTextControl.xaml"> <Page Include="HighlightedTextControl.xaml">
@ -74,6 +74,7 @@
<ClInclude Include="Commandline.h" /> <ClInclude Include="Commandline.h" />
<ClInclude Include="CommandLinePaletteItem.h" /> <ClInclude Include="CommandLinePaletteItem.h" />
<ClInclude Include="Jumplist.h" /> <ClInclude Include="Jumplist.h" />
<ClInclude Include="LanguageProfileNotifier.h" />
<ClInclude Include="MinMaxCloseControl.h"> <ClInclude Include="MinMaxCloseControl.h">
<DependentUpon>MinMaxCloseControl.xaml</DependentUpon> <DependentUpon>MinMaxCloseControl.xaml</DependentUpon>
</ClInclude> </ClInclude>
@ -89,6 +90,9 @@
<DependentUpon>TabBase.idl</DependentUpon> <DependentUpon>TabBase.idl</DependentUpon>
</ClInclude> </ClInclude>
<ClInclude Include="TabPaletteItem.h" /> <ClInclude Include="TabPaletteItem.h" />
<ClInclude Include="TaskbarState.h">
<DependentUpon>TaskbarState.idl</DependentUpon>
</ClInclude>
<ClInclude Include="TerminalTab.h"> <ClInclude Include="TerminalTab.h">
<DependentUpon>TerminalTab.idl</DependentUpon> <DependentUpon>TerminalTab.idl</DependentUpon>
</ClInclude> </ClInclude>
@ -112,7 +116,7 @@
<ClInclude Include="HighlightedTextControl.h"> <ClInclude Include="HighlightedTextControl.h">
<DependentUpon>HighlightedTextControl.xaml</DependentUpon> <DependentUpon>HighlightedTextControl.xaml</DependentUpon>
</ClInclude> </ClInclude>
<ClInclude Include="HighlightedText.h" /> <ClInclude Include="HighlightedText.h" />
<ClInclude Include="ColorPickupFlyout.h"> <ClInclude Include="ColorPickupFlyout.h">
<DependentUpon>ColorPickupFlyout.xaml</DependentUpon> <DependentUpon>ColorPickupFlyout.xaml</DependentUpon>
</ClInclude> </ClInclude>
@ -139,7 +143,7 @@
<ClInclude Include="AppLogic.h"> <ClInclude Include="AppLogic.h">
<DependentUpon>AppLogic.idl</DependentUpon> <DependentUpon>AppLogic.idl</DependentUpon>
</ClInclude> </ClInclude>
<ClInclude Include="Toast.h"/> <ClInclude Include="Toast.h" />
</ItemGroup> </ItemGroup>
<!-- ========================= Cpp Files ======================== --> <!-- ========================= Cpp Files ======================== -->
<ItemGroup> <ItemGroup>
@ -149,6 +153,7 @@
<ClCompile Include="AppCommandlineArgs.cpp" /> <ClCompile Include="AppCommandlineArgs.cpp" />
<ClCompile Include="Commandline.cpp" /> <ClCompile Include="Commandline.cpp" />
<ClCompile Include="Jumplist.cpp" /> <ClCompile Include="Jumplist.cpp" />
<ClCompile Include="LanguageProfileNotifier.cpp" />
<ClCompile Include="MinMaxCloseControl.cpp"> <ClCompile Include="MinMaxCloseControl.cpp">
<DependentUpon>MinMaxCloseControl.xaml</DependentUpon> <DependentUpon>MinMaxCloseControl.xaml</DependentUpon>
</ClCompile> </ClCompile>
@ -164,6 +169,9 @@
<DependentUpon>TabBase.idl</DependentUpon> <DependentUpon>TabBase.idl</DependentUpon>
</ClCompile> </ClCompile>
<ClCompile Include="TabPaletteItem.cpp" /> <ClCompile Include="TabPaletteItem.cpp" />
<ClCompile Include="TaskbarState.cpp">
<DependentUpon>TaskbarState.idl</DependentUpon>
</ClCompile>
<ClCompile Include="TerminalTab.cpp"> <ClCompile Include="TerminalTab.cpp">
<DependentUpon>TerminalTab.idl</DependentUpon> <DependentUpon>TerminalTab.idl</DependentUpon>
</ClCompile> </ClCompile>
@ -195,7 +203,7 @@
<ClCompile Include="HighlightedTextControl.cpp"> <ClCompile Include="HighlightedTextControl.cpp">
<DependentUpon>HighlightedTextControl.xaml</DependentUpon> <DependentUpon>HighlightedTextControl.xaml</DependentUpon>
</ClCompile> </ClCompile>
<ClCompile Include="HighlightedText.cpp" /> <ClCompile Include="HighlightedText.cpp" />
<ClCompile Include="ColorPickupFlyout.cpp"> <ClCompile Include="ColorPickupFlyout.cpp">
<DependentUpon>ColorPickupFlyout.xaml</DependentUpon> <DependentUpon>ColorPickupFlyout.xaml</DependentUpon>
</ClCompile> </ClCompile>
@ -256,6 +264,7 @@
</Midl> </Midl>
<Midl Include="TabBase.idl" /> <Midl Include="TabBase.idl" />
<Midl Include="TabPaletteItem.idl" /> <Midl Include="TabPaletteItem.idl" />
<Midl Include="TaskbarState.idl" />
<Midl Include="TerminalTab.idl" /> <Midl Include="TerminalTab.idl" />
<Midl Include="TerminalPage.idl"> <Midl Include="TerminalPage.idl">
<DependentUpon>TerminalPage.xaml</DependentUpon> <DependentUpon>TerminalPage.xaml</DependentUpon>
@ -280,7 +289,7 @@
<DependentUpon>HighlightedTextControl.xaml</DependentUpon> <DependentUpon>HighlightedTextControl.xaml</DependentUpon>
<SubType>Code</SubType> <SubType>Code</SubType>
</Midl> </Midl>
<Midl Include="HighlightedText.idl" /> <Midl Include="HighlightedText.idl" />
<Midl Include="ColorPickupFlyout.idl"> <Midl Include="ColorPickupFlyout.idl">
<DependentUpon>ColorPickupFlyout.xaml</DependentUpon> <DependentUpon>ColorPickupFlyout.xaml</DependentUpon>
<SubType>Code</SubType> <SubType>Code</SubType>
@ -371,13 +380,13 @@
<!-- ========================= Globals ======================== --> <!-- ========================= Globals ======================== -->
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" /> <Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" /> <Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" /> <Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <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> <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> </PropertyGroup>
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets'))" /> <Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" /> <Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
</Target> </Target>

View file

@ -13,9 +13,6 @@
<ClCompile Include="Pane.cpp"> <ClCompile Include="Pane.cpp">
<Filter>pane</Filter> <Filter>pane</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="Tab.cpp">
<Filter>tab</Filter>
</ClCompile>
<ClCompile Include="Pane.LayoutSizeNode.cpp"> <ClCompile Include="Pane.LayoutSizeNode.cpp">
<Filter>pane</Filter> <Filter>pane</Filter>
</ClCompile> </ClCompile>
@ -23,7 +20,6 @@
<ClCompile Include="Commandline.cpp" /> <ClCompile Include="Commandline.cpp" />
<ClCompile Include="ColorHelper.cpp" /> <ClCompile Include="ColorHelper.cpp" />
<ClCompile Include="DebugTapConnection.cpp" /> <ClCompile Include="DebugTapConnection.cpp" />
<ClCompile Include="Utils.cpp" />
<ClCompile Include="Jumplist.cpp" /> <ClCompile Include="Jumplist.cpp" />
<ClCompile Include="Tab.cpp"> <ClCompile Include="Tab.cpp">
<Filter>tab</Filter> <Filter>tab</Filter>
@ -49,10 +45,10 @@
<ClCompile Include="HighlightedText.cpp"> <ClCompile Include="HighlightedText.cpp">
<Filter>highlightedText</Filter> <Filter>highlightedText</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="Toast.cpp" />
<ClCompile Include="LanguageProfileNotifier.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="Utils.h" />
<ClInclude Include="TerminalWarnings.h" />
<ClInclude Include="pch.h" /> <ClInclude Include="pch.h" />
<ClInclude Include="App.base.h"> <ClInclude Include="App.base.h">
<Filter>app</Filter> <Filter>app</Filter>
@ -60,9 +56,6 @@
<ClInclude Include="Pane.h"> <ClInclude Include="Pane.h">
<Filter>pane</Filter> <Filter>pane</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="Tab.h">
<Filter>tab</Filter>
</ClInclude>
<ClInclude Include="AppCommandlineArgs.h" /> <ClInclude Include="AppCommandlineArgs.h" />
<ClInclude Include="Commandline.h" /> <ClInclude Include="Commandline.h" />
<ClInclude Include="DebugTapConnection.h" /> <ClInclude Include="DebugTapConnection.h" />
@ -92,14 +85,13 @@
<ClInclude Include="HighlightedText.h"> <ClInclude Include="HighlightedText.h">
<Filter>highlightedText</Filter> <Filter>highlightedText</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="Toast.h" />
<ClInclude Include="LanguageProfileNotifier.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Midl Include="AppLogic.idl"> <Midl Include="AppLogic.idl">
<Filter>app</Filter> <Filter>app</Filter>
</Midl> </Midl>
<Midl Include="ActionArgs.idl">
<Filter>settings</Filter>
</Midl>
<Midl Include="AppKeyBindings.idl"> <Midl Include="AppKeyBindings.idl">
<Filter>settings</Filter> <Filter>settings</Filter>
</Midl> </Midl>
@ -107,9 +99,6 @@
<Filter>settings</Filter> <Filter>settings</Filter>
</Midl> </Midl>
<Midl Include="IDirectKeyListener.idl" /> <Midl Include="IDirectKeyListener.idl" />
<Midl Include="ITab.idl">
<Filter>tab</Filter>
</Midl>
<Midl Include="SettingsTab.idl"> <Midl Include="SettingsTab.idl">
<Filter>tab</Filter> <Filter>tab</Filter>
</Midl> </Midl>
@ -125,6 +114,9 @@
<Midl Include="TerminalTabStatus.idl"> <Midl Include="TerminalTabStatus.idl">
<Filter>tab</Filter> <Filter>tab</Filter>
</Midl> </Midl>
<Midl Include="PaletteItemTemplateSelector.idl" />
<Midl Include="TabBase.idl" />
<Midl Include="EmptyStringVisibilityConverter.idl" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="packages.config" /> <None Include="packages.config" />
@ -160,9 +152,6 @@
<Midl Include="ActionPaletteItem.idl"> <Midl Include="ActionPaletteItem.idl">
<Filter>commandPalette</Filter> <Filter>commandPalette</Filter>
</Midl> </Midl>
<Midl Include="FilterableListItem.idl">
<Filter>commandPalette</Filter>
</Midl>
<Midl Include="CommandLinePaletteItem.idl"> <Midl Include="CommandLinePaletteItem.idl">
<Filter>commandPalette</Filter> <Filter>commandPalette</Filter>
</Midl> </Midl>

View file

@ -6,6 +6,7 @@
#include "Utils.h" #include "Utils.h"
#include "../../types/inc/utils.hpp" #include "../../types/inc/utils.hpp"
#include <filesystem>
#include <LibraryResources.h> #include <LibraryResources.h>
#include "TerminalPage.g.cpp" #include "TerminalPage.g.cpp"
@ -18,6 +19,8 @@
#include "RenameWindowRequestedArgs.g.cpp" #include "RenameWindowRequestedArgs.g.cpp"
#include "../inc/WindowingBehavior.h" #include "../inc/WindowingBehavior.h"
#include <til/latch.h>
using namespace winrt; using namespace winrt;
using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::Foundation::Collections;
using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::UI::Xaml;
@ -139,6 +142,44 @@ namespace winrt::TerminalApp::implementation
} }
CATCH_LOG(); CATCH_LOG();
if (_settings.GlobalSettings().UseAcrylicInTabRow())
{
const auto res = Application::Current().Resources();
const auto lightKey = winrt::box_value(L"Light");
const auto darkKey = winrt::box_value(L"Dark");
const auto tabViewBackgroundKey = winrt::box_value(L"TabViewBackground");
for (auto const& dictionary : res.MergedDictionaries())
{
// Don't change MUX resources
if (dictionary.Source())
{
continue;
}
for (auto const& kvPair : dictionary.ThemeDictionaries())
{
const auto themeDictionary = kvPair.Value().as<winrt::Windows::UI::Xaml::ResourceDictionary>();
if (themeDictionary.HasKey(tabViewBackgroundKey))
{
const auto backgroundSolidBrush = themeDictionary.Lookup(tabViewBackgroundKey).as<Media::SolidColorBrush>();
const til::color backgroundColor = backgroundSolidBrush.Color();
const auto acrylicBrush = Media::AcrylicBrush();
acrylicBrush.BackgroundSource(Media::AcrylicBackgroundSource::HostBackdrop);
acrylicBrush.FallbackColor(backgroundColor);
acrylicBrush.TintColor(backgroundColor);
acrylicBrush.TintOpacity(0.5);
themeDictionary.Insert(tabViewBackgroundKey, acrylicBrush);
}
}
}
}
_tabRow.PointerMoved({ get_weak(), &TerminalPage::_RestorePointerCursorHandler }); _tabRow.PointerMoved({ get_weak(), &TerminalPage::_RestorePointerCursorHandler });
_tabView.CanReorderTabs(!isElevated); _tabView.CanReorderTabs(!isElevated);
_tabView.CanDragTabs(!isElevated); _tabView.CanDragTabs(!isElevated);
@ -172,40 +213,13 @@ namespace winrt::TerminalApp::implementation
_newTabButton.Click([weakThis{ get_weak() }](auto&&, auto&&) { _newTabButton.Click([weakThis{ get_weak() }](auto&&, auto&&) {
if (auto page{ weakThis.get() }) if (auto page{ weakThis.get() })
{ {
// if alt is pressed, open a pane page->_OpenNewTerminal(NewTerminalArgs());
const CoreWindow window = CoreWindow::GetForCurrentThread(); }
const auto rAltState = window.GetKeyState(VirtualKey::RightMenu); });
const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu); _newTabButton.Drop([weakThis{ get_weak() }](Windows::Foundation::IInspectable const&, winrt::Windows::UI::Xaml::DragEventArgs e) {
const bool altPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) || if (auto page{ weakThis.get() })
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down); {
page->NewTerminalByDrop(e);
const auto shiftState{ window.GetKeyState(VirtualKey::Shift) };
const auto rShiftState = window.GetKeyState(VirtualKey::RightShift);
const auto lShiftState = window.GetKeyState(VirtualKey::LeftShift);
const auto shiftPressed{ WI_IsFlagSet(shiftState, CoreVirtualKeyStates::Down) ||
WI_IsFlagSet(lShiftState, CoreVirtualKeyStates::Down) ||
WI_IsFlagSet(rShiftState, CoreVirtualKeyStates::Down) };
// Check for DebugTap
bool debugTap = page->_settings.GlobalSettings().DebugFeaturesEnabled() &&
WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) &&
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
if (altPressed && !debugTap)
{
page->_SplitPane(SplitState::Automatic,
SplitType::Manual,
0.5f,
nullptr);
}
else if (shiftPressed && !debugTap)
{
page->_OpenNewWindow(false, NewTerminalArgs());
}
else
{
page->_OpenNewTab(nullptr);
}
} }
}); });
_tabView.SelectionChanged({ this, &TerminalPage::_OnTabSelectionChanged }); _tabView.SelectionChanged({ this, &TerminalPage::_OnTabSelectionChanged });
@ -262,6 +276,36 @@ namespace winrt::TerminalApp::implementation
CATCH_LOG(); CATCH_LOG();
} }
winrt::fire_and_forget TerminalPage::NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e)
{
Windows::Foundation::Collections::IVectorView<Windows::Storage::IStorageItem> items;
try
{
items = co_await e.DataView().GetStorageItemsAsync();
}
CATCH_LOG();
if (items.Size() == 1)
{
std::filesystem::path path(items.GetAt(0).Path().c_str());
if (!std::filesystem::is_directory(path))
{
path = path.parent_path();
}
NewTerminalArgs args;
args.StartingDirectory(winrt::hstring{ path.wstring() });
this->_OpenNewTerminal(args);
TraceLoggingWrite(
g_hTerminalAppProvider,
"NewTabByDragDrop",
TraceLoggingDescription("Event emitted when the user drag&drops onto the new tab button"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
}
}
// Method Description: // Method Description:
// - This method is called once command palette action was chosen for dispatching // - This method is called once command palette action was chosen for dispatching
// We'll use this event to dispatch this command. // We'll use this event to dispatch this command.
@ -344,34 +388,12 @@ namespace winrt::TerminalApp::implementation
winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::StartInboundListener(); winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::StartInboundListener();
} }
// If we failed to start the listener, it will throw. // If we failed to start the listener, it will throw.
// We should fail fast here or the Terminal will be in a very strange state. // We don't want to fail fast here because if a peasant has some trouble with
// We only start the listener if the Terminal was started with the COM server // starting the listener, we don't want it to crash and take all its tabs down
// `-Embedding` flag and we make no tabs as a result. // with it.
// Therefore, if the listener cannot start itself up to make that tab with
// the inbound connection that caused the COM activation in the first place...
// we would be left with an empty terminal frame with no tabs.
// Instead, crash out so COM sees the server die and things unwind
// without a weird empty frame window.
catch (...) catch (...)
{ {
// However, we cannot always fail fast because of MSFT:33501832. Sometimes the COM catalog LOG_CAUGHT_EXCEPTION();
// tears the state between old and new versions and fails here for that reason.
// As we're always becoming an inbound server in the monarch, even when COM didn't strictly
// ask us yet...we might just crash always.
// Instead... we're going to differentiate. If COM started us... we will fail fast
// so it sees the process die and falls back.
// If we were just starting normally as a Monarch and opportunistically listening for
// inbound connections... then we'll just log the failure and move on assuming
// the version state is torn and will fix itself whenever the packaging upgrade
// tasks decide to clean up.
if (_isEmbeddingInboundListener)
{
FAIL_FAST_CAUGHT_EXCEPTION();
}
else
{
LOG_CAUGHT_EXCEPTION();
}
} }
} }
} }
@ -435,6 +457,11 @@ namespace winrt::TerminalApp::implementation
co_return; co_return;
} }
} }
// GH#6586: now that we're done processing all startup commands,
// focus the active control. This will work as expected for both
// commandline invocations and for `wt` action invocations.
_GetActiveControl().Focus(FocusState::Programmatic);
} }
if (initial) if (initial)
{ {
@ -622,43 +649,7 @@ namespace winrt::TerminalApp::implementation
if (auto page{ weakThis.get() }) if (auto page{ weakThis.get() })
{ {
NewTerminalArgs newTerminalArgs{ profileIndex }; NewTerminalArgs newTerminalArgs{ profileIndex };
page->_OpenNewTerminal(newTerminalArgs);
// if alt is pressed, open a pane
const CoreWindow window = CoreWindow::GetForCurrentThread();
const auto rAltState = window.GetKeyState(VirtualKey::RightMenu);
const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu);
const bool altPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) ||
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
const auto shiftState{ window.GetKeyState(VirtualKey::Shift) };
const auto rShiftState = window.GetKeyState(VirtualKey::RightShift);
const auto lShiftState = window.GetKeyState(VirtualKey::LeftShift);
const auto shiftPressed{ WI_IsFlagSet(shiftState, CoreVirtualKeyStates::Down) ||
WI_IsFlagSet(lShiftState, CoreVirtualKeyStates::Down) ||
WI_IsFlagSet(rShiftState, CoreVirtualKeyStates::Down) };
// Check for DebugTap
bool debugTap = page->_settings.GlobalSettings().DebugFeaturesEnabled() &&
WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) &&
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
if (altPressed && !debugTap)
{
page->_SplitPane(SplitState::Automatic,
SplitType::Manual,
0.5f,
newTerminalArgs);
}
else if (shiftPressed && !debugTap)
{
// Manually fill in the evaluated profile.
newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(page->_settings.GetProfileForArgs(newTerminalArgs)));
page->_OpenNewWindow(false, newTerminalArgs);
}
else
{
page->_OpenNewTab(newTerminalArgs);
}
} }
}); });
newTabFlyout.Items().Append(profileMenuItem); newTabFlyout.Items().Append(profileMenuItem);
@ -752,6 +743,54 @@ namespace winrt::TerminalApp::implementation
_newTabButton.Flyout().ShowAt(_newTabButton); _newTabButton.Flyout().ShowAt(_newTabButton);
} }
void TerminalPage::_OpenNewTerminal(const NewTerminalArgs newTerminalArgs)
{
// if alt is pressed, open a pane
const CoreWindow window = CoreWindow::GetForCurrentThread();
const auto rAltState = window.GetKeyState(VirtualKey::RightMenu);
const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu);
const bool altPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) ||
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
const auto shiftState{ window.GetKeyState(VirtualKey::Shift) };
const auto rShiftState = window.GetKeyState(VirtualKey::RightShift);
const auto lShiftState = window.GetKeyState(VirtualKey::LeftShift);
const auto shiftPressed{ WI_IsFlagSet(shiftState, CoreVirtualKeyStates::Down) ||
WI_IsFlagSet(lShiftState, CoreVirtualKeyStates::Down) ||
WI_IsFlagSet(rShiftState, CoreVirtualKeyStates::Down) };
// Check for DebugTap
bool debugTap = this->_settings.GlobalSettings().DebugFeaturesEnabled() &&
WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) &&
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
if (altPressed && !debugTap)
{
this->_SplitPane(SplitState::Automatic,
SplitType::Manual,
0.5f,
newTerminalArgs);
}
else if (shiftPressed && !debugTap)
{
// Manually fill in the evaluated profile.
if (newTerminalArgs.ProfileIndex() != nullptr)
{
// We want to promote the index to a GUID because there is no "launch to profile index" command.
const auto profile = _settings.GetProfileForArgs(newTerminalArgs);
if (profile)
{
newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profile.Guid()));
}
}
this->_OpenNewWindow(false, newTerminalArgs);
}
else
{
LOG_IF_FAILED(this->_OpenNewTab(newTerminalArgs));
}
}
winrt::fire_and_forget TerminalPage::_RemoveOnCloseRoutine(Microsoft::UI::Xaml::Controls::TabViewItem tabViewItem, winrt::com_ptr<TerminalPage> page) winrt::fire_and_forget TerminalPage::_RemoveOnCloseRoutine(Microsoft::UI::Xaml::Controls::TabViewItem tabViewItem, winrt::com_ptr<TerminalPage> page)
{ {
co_await winrt::resume_foreground(page->_tabView.Dispatcher()); co_await winrt::resume_foreground(page->_tabView.Dispatcher());
@ -765,14 +804,18 @@ namespace winrt::TerminalApp::implementation
// Method Description: // Method Description:
// - Creates a new connection based on the profile settings // - Creates a new connection based on the profile settings
// Arguments: // Arguments:
// - the profile GUID we want the settings from // - the profile we want the settings from
// - the terminal settings // - the terminal settings
// Return value: // Return value:
// - the desired connection // - the desired connection
TerminalConnection::ITerminalConnection TerminalPage::_CreateConnectionFromSettings(GUID profileGuid, TerminalConnection::ITerminalConnection TerminalPage::_CreateConnectionFromSettings(Profile profile,
TerminalSettings settings) TerminalSettings settings)
{ {
const auto profile = _settings.FindProfile(profileGuid); if (!profile)
{
// Use the default profile if we didn't get one as an argument.
profile = _settings.FindProfile(_settings.GlobalSettings().DefaultProfile());
}
TerminalConnection::ITerminalConnection connection{ nullptr }; TerminalConnection::ITerminalConnection connection{ nullptr };
@ -785,18 +828,20 @@ namespace winrt::TerminalApp::implementation
// TODO GH#4661: Replace this with directly using the AzCon when our VT is better // TODO GH#4661: Replace this with directly using the AzCon when our VT is better
std::filesystem::path azBridgePath{ wil::GetModuleFileNameW<std::wstring>(nullptr) }; std::filesystem::path azBridgePath{ wil::GetModuleFileNameW<std::wstring>(nullptr) };
azBridgePath.replace_filename(L"TerminalAzBridge.exe"); azBridgePath.replace_filename(L"TerminalAzBridge.exe");
connection = TerminalConnection::ConptyConnection(azBridgePath.wstring(), connection = TerminalConnection::ConptyConnection();
L".", connection.Initialize(TerminalConnection::ConptyConnection::CreateSettings(azBridgePath.wstring(),
L"Azure", L".",
nullptr, L"Azure",
settings.InitialRows(), nullptr,
settings.InitialCols(), ::base::saturated_cast<uint32_t>(settings.InitialRows()),
winrt::guid()); ::base::saturated_cast<uint32_t>(settings.InitialCols()),
winrt::guid()));
} }
else else
{ {
std::wstring guidWString = Utils::GuidToString(profileGuid); // profile is guaranteed to exist here
std::wstring guidWString = Utils::GuidToString(profile.Guid());
StringMap envMap{}; StringMap envMap{};
envMap.Insert(L"WT_PROFILE_ID", guidWString); envMap.Insert(L"WT_PROFILE_ID", guidWString);
@ -817,18 +862,24 @@ namespace winrt::TerminalApp::implementation
// construction, because the connection might not spawn the child // construction, because the connection might not spawn the child
// process until later, on another thread, after we've already // process until later, on another thread, after we've already
// restored the CWD to it's original value. // restored the CWD to it's original value.
std::wstring cwdString{ wil::GetCurrentDirectoryW<std::wstring>() }; winrt::hstring newWorkingDirectory{ settings.StartingDirectory() };
std::filesystem::path cwd{ cwdString }; if (newWorkingDirectory.size() <= 1 ||
cwd /= settings.StartingDirectory().c_str(); !(newWorkingDirectory[0] == L'~' || newWorkingDirectory[0] == L'/'))
{ // We only want to resolve the new WD against the CWD if it doesn't look like a Linux path (see GH#592)
std::wstring cwdString{ wil::GetCurrentDirectoryW<std::wstring>() };
std::filesystem::path cwd{ cwdString };
cwd /= settings.StartingDirectory().c_str();
newWorkingDirectory = winrt::hstring{ cwd.wstring() };
}
auto conhostConn = TerminalConnection::ConptyConnection( auto conhostConn = TerminalConnection::ConptyConnection();
settings.Commandline(), conhostConn.Initialize(TerminalConnection::ConptyConnection::CreateSettings(settings.Commandline(),
winrt::hstring{ cwd.c_str() }, newWorkingDirectory,
settings.StartingTitle(), settings.StartingTitle(),
envMap.GetView(), envMap.GetView(),
settings.InitialRows(), ::base::saturated_cast<uint32_t>(settings.InitialRows()),
settings.InitialCols(), ::base::saturated_cast<uint32_t>(settings.InitialCols()),
winrt::guid()); winrt::guid()));
sessionGuid = conhostConn.Guid(); sessionGuid = conhostConn.Guid();
connection = conhostConn; connection = conhostConn;
@ -839,7 +890,7 @@ namespace winrt::TerminalApp::implementation
"ConnectionCreated", "ConnectionCreated",
TraceLoggingDescription("Event emitted upon the creation of a connection"), TraceLoggingDescription("Event emitted upon the creation of a connection"),
TraceLoggingGuid(connectionType, "ConnectionTypeGuid", "The type of the connection"), TraceLoggingGuid(connectionType, "ConnectionTypeGuid", "The type of the connection"),
TraceLoggingGuid(profileGuid, "ProfileGuid", "The profile's GUID"), TraceLoggingGuid(profile.Guid(), "ProfileGuid", "The profile's GUID"),
TraceLoggingGuid(sessionGuid, "SessionGuid", "The WT_SESSION's GUID"), TraceLoggingGuid(sessionGuid, "SessionGuid", "The WT_SESSION's GUID"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
@ -914,12 +965,14 @@ namespace winrt::TerminalApp::implementation
// - <none> // - <none>
void TerminalPage::_KeyDownHandler(Windows::Foundation::IInspectable const& /*sender*/, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e) void TerminalPage::_KeyDownHandler(Windows::Foundation::IInspectable const& /*sender*/, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
{ {
auto key = e.OriginalKey(); const auto key = e.OriginalKey();
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down); const auto scanCode = e.KeyStatus().ScanCode;
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down); const auto coreWindow = CoreWindow::GetForCurrentThread();
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down); const auto ctrlDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
const auto altDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
const auto shiftDown = WI_IsFlagSet(coreWindow.GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
winrt::Microsoft::Terminal::Control::KeyChord kc{ ctrlDown, altDown, shiftDown, static_cast<int32_t>(key) }; winrt::Microsoft::Terminal::Control::KeyChord kc{ ctrlDown, altDown, shiftDown, false, static_cast<int32_t>(key), static_cast<int32_t>(scanCode) };
if (const auto cmd{ _settings.ActionMap().GetActionByKeyChord(kc) }) if (const auto cmd{ _settings.ActionMap().GetActionByKeyChord(kc) })
{ {
if (CommandPalette().Visibility() == Visibility::Visible && cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette) if (CommandPalette().Visibility() == Visibility::Visible && cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette)
@ -980,11 +1033,9 @@ namespace winrt::TerminalApp::implementation
// handle. This includes: // handle. This includes:
// * the Copy and Paste events, for setting and retrieving clipboard data // * the Copy and Paste events, for setting and retrieving clipboard data
// on the right thread // on the right thread
// * the TitleChanged event, for changing the text of the tab
// Arguments: // Arguments:
// - term: The newly created TermControl to connect the events for // - term: The newly created TermControl to connect the events for
// - hostingTab: The Tab that's hosting this TermControl instance void TerminalPage::_RegisterTerminalEvents(TermControl term)
void TerminalPage::_RegisterTerminalEvents(TermControl term, TerminalTab& hostingTab)
{ {
term.RaiseNotice({ this, &TerminalPage::_ControlNoticeRaisedHandler }); term.RaiseNotice({ this, &TerminalPage::_ControlNoticeRaisedHandler });
@ -999,10 +1050,20 @@ namespace winrt::TerminalApp::implementation
term.HidePointerCursor({ get_weak(), &TerminalPage::_HidePointerCursorHandler }); term.HidePointerCursor({ get_weak(), &TerminalPage::_HidePointerCursorHandler });
term.RestorePointerCursor({ get_weak(), &TerminalPage::_RestorePointerCursorHandler }); term.RestorePointerCursor({ get_weak(), &TerminalPage::_RestorePointerCursorHandler });
// Add an event handler for when the terminal or tab wants to set a
// progress indicator on the taskbar
term.SetTaskbarProgress({ get_weak(), &TerminalPage::_SetTaskbarProgressHandler });
}
// Bind Tab events to the TermControl and the Tab's Pane // Method Description:
hostingTab.Initialize(term); // - Connects event handlers to the TerminalTab for events that we want to
// handle. This includes:
// * the TitleChanged event, for changing the text of the tab
// * the Color{Selected,Cleared} events to change the color of a tab.
// Arguments:
// - hostingTab: The Tab that's hosting this TermControl instance
void TerminalPage::_RegisterTabEvents(TerminalTab& hostingTab)
{
auto weakTab{ hostingTab.get_weak() }; auto weakTab{ hostingTab.get_weak() };
auto weakThis{ get_weak() }; auto weakThis{ get_weak() };
// PropertyChanged is the generic mechanism by which the Tab // PropertyChanged is the generic mechanism by which the Tab
@ -1054,7 +1115,6 @@ namespace winrt::TerminalApp::implementation
// Add an event handler for when the terminal or tab wants to set a // Add an event handler for when the terminal or tab wants to set a
// progress indicator on the taskbar // progress indicator on the taskbar
hostingTab.TaskbarProgressChanged({ get_weak(), &TerminalPage::_SetTaskbarProgressHandler }); hostingTab.TaskbarProgressChanged({ get_weak(), &TerminalPage::_SetTaskbarProgressHandler });
term.SetTaskbarProgress({ get_weak(), &TerminalPage::_SetTaskbarProgressHandler });
// TODO GH#3327: Once we support colorizing the NewTab button based on // TODO GH#3327: Once we support colorizing the NewTab button based on
// the color of the tab, we'll want to make sure to call // the color of the tab, we'll want to make sure to call
@ -1101,14 +1161,33 @@ namespace winrt::TerminalApp::implementation
// Arguments: // Arguments:
// - direction: The direction to move the focus in. // - direction: The direction to move the focus in.
// Return Value: // Return Value:
// - <none> // - Whether changing the focus succeeded. This allows a keychord to propagate
void TerminalPage::_MoveFocus(const FocusDirection& direction) // to the terminal when no other panes are present (GH#6219)
bool TerminalPage::_MoveFocus(const FocusDirection& direction)
{ {
if (const auto terminalTab{ _GetFocusedTabImpl() }) if (const auto terminalTab{ _GetFocusedTabImpl() })
{ {
_UnZoomIfNeeded(); _UnZoomIfNeeded();
terminalTab->NavigateFocus(direction); return terminalTab->NavigateFocus(direction);
} }
return false;
}
// Method Description:
// - Attempt to swap the positions of the focused pane with another pane.
// See Pane::SwapPane for details.
// Arguments:
// - direction: The direction to move the focused pane in.
// Return Value:
// - true if panes were swapped.
bool TerminalPage::_SwapPane(const FocusDirection& direction)
{
if (const auto terminalTab{ _GetFocusedTabImpl() })
{
_UnZoomIfNeeded();
return terminalTab->SwapPane(direction);
}
return false;
} }
TermControl TerminalPage::_GetActiveControl() TermControl TerminalPage::_GetActiveControl()
@ -1170,6 +1249,56 @@ namespace winrt::TerminalApp::implementation
} }
} }
// Method Description:
// - Moves the currently active pane on the currently active tab to the
// specified tab. If the tab index is greater than the number of
// tabs, then a new tab will be created for the pane. Similarly, if a pane
// is the last remaining pane on a tab, that tab will be closed upon moving.
// - No move will occur if the tabIdx is the same as the current tab, or if
// the specified tab is not a host of terminals (such as the settings tab).
// Arguments:
// - tabIdx: The target tab index.
// Return Value:
// - true if the pane was successfully moved to the new tab.
bool TerminalPage::_MovePane(const uint32_t tabIdx)
{
auto focusedTab{ _GetFocusedTabImpl() };
if (!focusedTab)
{
return false;
}
// If we are trying to move from the current tab to the current tab do nothing.
if (_GetFocusedTabIndex() == tabIdx)
{
return false;
}
// Moving the pane from the current tab might close it, so get the next
// tab before its index changes.
if (_tabs.Size() > tabIdx)
{
auto targetTab = _GetTerminalTabImpl(_tabs.GetAt(tabIdx));
// if the selected tab is not a host of terminals (e.g. settings)
// don't attempt to add a pane to it.
if (!targetTab)
{
return false;
}
auto pane = focusedTab->DetachPane();
targetTab->AttachPane(pane);
_SetFocusedTab(*targetTab);
}
else
{
auto pane = focusedTab->DetachPane();
_CreateNewTabFromPane(pane);
}
return true;
}
// Method Description: // Method Description:
// - Split the focused pane either horizontally or vertically, and place the // - Split the focused pane either horizontally or vertically, and place the
// given TermControl into the newly created pane. // given TermControl into the newly created pane.
@ -1200,26 +1329,50 @@ namespace winrt::TerminalApp::implementation
return; return;
} }
_SplitPane(*focusedTab, splitType, splitMode, splitSize, newTerminalArgs);
}
// Method Description:
// - Split the focused pane of the given tab, either horizontally or vertically, and place the
// given TermControl into the newly created pane.
// - If splitType == SplitState::None, this method does nothing.
// Arguments:
// - tab: The tab that is going to be split.
// - splitType: one value from the TerminalApp::SplitState enum, indicating how the
// new pane should be split from its parent.
// - splitMode: value from TerminalApp::SplitType enum, indicating the profile to be used in the newly split pane.
// - newTerminalArgs: An object that may contain a blob of parameters to
// control which profile is created and with possible other
// configurations. See CascadiaSettings::BuildSettings for more details.
void TerminalPage::_SplitPane(TerminalTab& tab,
const SplitState splitType,
const SplitType splitMode,
const float splitSize,
const NewTerminalArgs& newTerminalArgs)
{
// Do nothing if we're requesting no split.
if (splitType == SplitState::None)
{
return;
}
try try
{ {
TerminalSettingsCreateResult controlSettings{ nullptr }; TerminalSettingsCreateResult controlSettings{ nullptr };
GUID realGuid; Profile profile{ nullptr };
bool profileFound = false;
if (splitMode == SplitType::Duplicate) if (splitMode == SplitType::Duplicate)
{ {
std::optional<GUID> current_guid = focusedTab->GetFocusedProfile(); profile = tab.GetFocusedProfile();
if (current_guid) if (profile)
{ {
profileFound = true; controlSettings = TerminalSettings::CreateWithProfile(_settings, profile, *_bindings);
controlSettings = TerminalSettings::CreateWithProfileByID(_settings, current_guid.value(), *_bindings); const auto workingDirectory = tab.GetActiveTerminalControl().WorkingDirectory();
const auto workingDirectory = focusedTab->GetActiveTerminalControl().WorkingDirectory();
const auto validWorkingDirectory = !workingDirectory.empty(); const auto validWorkingDirectory = !workingDirectory.empty();
if (validWorkingDirectory) if (validWorkingDirectory)
{ {
controlSettings.DefaultSettings().StartingDirectory(workingDirectory); controlSettings.DefaultSettings().StartingDirectory(workingDirectory);
} }
realGuid = current_guid.value();
} }
// TODO: GH#5047 - In the future, we should get the Profile of // TODO: GH#5047 - In the future, we should get the Profile of
// the focused pane, and use that to build a new instance of the // the focused pane, and use that to build a new instance of the
@ -1234,13 +1387,13 @@ namespace winrt::TerminalApp::implementation
// connection without keeping an instance of the original Profile // connection without keeping an instance of the original Profile
// object around. // object around.
} }
if (!profileFound) if (!profile)
{ {
realGuid = _settings.GetProfileForArgs(newTerminalArgs); profile = _settings.GetProfileForArgs(newTerminalArgs);
controlSettings = TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings); controlSettings = TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings);
} }
const auto controlConnection = _CreateConnectionFromSettings(realGuid, controlSettings.DefaultSettings()); const auto controlConnection = _CreateConnectionFromSettings(profile, controlSettings.DefaultSettings());
const float contentWidth = ::base::saturated_cast<float>(_tabContent.ActualWidth()); const float contentWidth = ::base::saturated_cast<float>(_tabContent.ActualWidth());
const float contentHeight = ::base::saturated_cast<float>(_tabContent.ActualHeight()); const float contentHeight = ::base::saturated_cast<float>(_tabContent.ActualHeight());
@ -1249,10 +1402,10 @@ namespace winrt::TerminalApp::implementation
auto realSplitType = splitType; auto realSplitType = splitType;
if (realSplitType == SplitState::Automatic) if (realSplitType == SplitState::Automatic)
{ {
realSplitType = focusedTab->PreCalculateAutoSplit(availableSpace); realSplitType = tab.PreCalculateAutoSplit(availableSpace);
} }
const auto canSplit = focusedTab->PreCalculateCanSplit(realSplitType, splitSize, availableSpace); const auto canSplit = tab.PreCalculateCanSplit(realSplitType, splitSize, availableSpace);
if (!canSplit) if (!canSplit)
{ {
return; return;
@ -1261,15 +1414,38 @@ namespace winrt::TerminalApp::implementation
auto newControl = _InitControl(controlSettings, controlConnection); auto newControl = _InitControl(controlSettings, controlConnection);
// Hookup our event handlers to the new terminal // Hookup our event handlers to the new terminal
_RegisterTerminalEvents(newControl, *focusedTab); _RegisterTerminalEvents(newControl);
_UnZoomIfNeeded(); _UnZoomIfNeeded();
focusedTab->SplitPane(realSplitType, splitSize, realGuid, newControl); tab.SplitPane(realSplitType, splitSize, profile, newControl);
// After GH#6586, the control will no longer focus itself
// automatically when it's finished being laid out. Manually focus
// the control here instead.
if (_startupState == StartupState::Initialized)
{
_GetActiveControl().Focus(FocusState::Programmatic);
}
} }
CATCH_LOG(); CATCH_LOG();
} }
// Method Description:
// - Switches the split orientation of the currently focused pane.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TerminalPage::_ToggleSplitOrientation()
{
if (const auto terminalTab{ _GetFocusedTabImpl() })
{
_UnZoomIfNeeded();
terminalTab->ToggleSplitOrientation();
}
}
// Method Description: // Method Description:
// - Attempt to move a separator between panes, as to resize each child on // - Attempt to move a separator between panes, as to resize each child on
// either size of the separator. See Pane::ResizePane for details. // either size of the separator. See Pane::ResizePane for details.
@ -1833,40 +2009,56 @@ namespace winrt::TerminalApp::implementation
_HookupKeyBindings(_settings.ActionMap()); _HookupKeyBindings(_settings.ActionMap());
// Refresh UI elements // Refresh UI elements
auto profiles = _settings.ActiveProfiles();
for (const auto& profile : profiles) // Mapping by GUID isn't _excellent_ because the defaults profile doesn't have a stable GUID; however,
// when we stabilize its guid this will become fully safe.
std::unordered_map<winrt::guid, std::pair<Profile, TerminalSettingsCreateResult>> profileGuidSettingsMap;
const auto profileDefaults{ _settings.ProfileDefaults() };
const auto allProfiles{ _settings.AllProfiles() };
profileGuidSettingsMap.reserve(allProfiles.Size() + 1);
// Include the Defaults profile for consideration
profileGuidSettingsMap.insert_or_assign(profileDefaults.Guid(), std::pair{ profileDefaults, nullptr });
for (const auto& newProfile : allProfiles)
{ {
const auto profileGuid = profile.Guid(); // Avoid creating a TerminalSettings right now. They're not totally cheap, and we suspect that users with many
// panes may not be using all of their profiles at the same time. Lazy evaluation is king!
try profileGuidSettingsMap.insert_or_assign(newProfile.Guid(), std::pair{ newProfile, nullptr });
{
// This can throw an exception if the profileGuid does
// not belong to an actual profile in the list of profiles.
auto settings{ TerminalSettings::CreateWithProfileByID(_settings, profileGuid, *_bindings) };
for (auto tab : _tabs)
{
if (auto terminalTab = _GetTerminalTabImpl(tab))
{
terminalTab->UpdateSettings(settings, profileGuid);
}
}
}
CATCH_LOG();
} }
// GH#2455: If there are any panes with controls that had been for (const auto& tab : _tabs)
// initialized with a Profile that no longer exists in our list of
// profiles, we'll leave it unmodified. The profile doesn't exist
// anymore, so we can't possibly update its settings.
// Update the icon of the tab for the currently focused profile in that tab.
// Only do this for TerminalTabs. Other types of tabs won't have multiple panes
// and profiles so the Title and Icon will be set once and only once on init.
for (auto tab : _tabs)
{ {
if (auto terminalTab = _GetTerminalTabImpl(tab)) if (auto terminalTab{ _GetTerminalTabImpl(tab) })
{ {
terminalTab->UpdateSettings();
// Manually enumerate the panes in each tab; this will let us recycle TerminalSettings
// objects but only have to iterate one time.
terminalTab->GetRootPane()->WalkTree([&](auto&& pane) {
if (const auto profile{ pane->GetProfile() })
{
const auto found{ profileGuidSettingsMap.find(profile.Guid()) };
// GH#2455: If there are any panes with controls that had been
// initialized with a Profile that no longer exists in our list of
// profiles, we'll leave it unmodified. The profile doesn't exist
// anymore, so we can't possibly update its settings.
if (found != profileGuidSettingsMap.cend())
{
auto& pair{ found->second };
if (!pair.second)
{
pair.second = TerminalSettings::CreateWithProfile(_settings, pair.first, *_bindings);
}
pane->UpdateSettings(pair.second, pair.first);
}
}
return false;
});
// Update the icon of the tab for the currently focused profile in that tab.
// Only do this for TerminalTabs. Other types of tabs won't have multiple panes
// and profiles so the Title and Icon will be set once and only once on init.
_UpdateTabIcon(*terminalTab); _UpdateTabIcon(*terminalTab);
// Force the TerminalTab to re-grab its currently active control's title. // Force the TerminalTab to re-grab its currently active control's title.
@ -2017,29 +2209,35 @@ namespace winrt::TerminalApp::implementation
} }
// Method Description: // Method Description:
// - Gets the taskbar state value from the last active control // - Get the combined taskbar state for the page. This is the combination of
// all the states of all the tabs, which are themselves a combination of
// all their panes. Taskbar states are given a priority based on the rules
// in:
// https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist3-setprogressstate
// under "How the Taskbar Button Chooses the Progress Indicator for a Group"
// Arguments:
// - <none>
// Return Value: // Return Value:
// - The taskbar state of the last active control // - A TaskbarState object representing the combined taskbar state and
size_t TerminalPage::GetLastActiveControlTaskbarState() // progress percentage of all our tabs.
winrt::TerminalApp::TaskbarState TerminalPage::TaskbarState() const
{ {
if (auto control{ _GetActiveControl() }) auto state{ winrt::make<winrt::TerminalApp::implementation::TaskbarState>() };
{
return gsl::narrow_cast<size_t>(control.TaskbarState());
}
return {};
}
// Method Description: for (const auto& tab : _tabs)
// - Gets the taskbar progress value from the last active control
// Return Value:
// - The taskbar progress of the last active control
size_t TerminalPage::GetLastActiveControlTaskbarProgress()
{
if (auto control{ _GetActiveControl() })
{ {
return gsl::narrow_cast<size_t>(control.TaskbarProgress()); if (auto tabImpl{ _GetTerminalTabImpl(tab) })
{
auto tabState{ tabImpl->GetCombinedTaskbarState() };
// lowest priority wins
if (tabState.Priority() < state.Priority())
{
state = tabState;
}
}
} }
return {};
return state;
} }
// Method Description: // Method Description:
@ -2325,13 +2523,38 @@ namespace winrt::TerminalApp::implementation
return _isAlwaysOnTop; return _isAlwaysOnTop;
} }
void TerminalPage::_OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection) HRESULT TerminalPage::_OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection)
{ {
// TODO: GH 9458 will give us more context so we can try to choose a better profile. // We need to be on the UI thread in order for _OpenNewTab to run successfully.
_OpenNewTab(nullptr, connection); // HasThreadAccess will return true if we're currently on a UI thread and false otherwise.
// When we're on a COM thread, we'll need to dispatch the calls to the UI thread
// and wait on it hence the locking mechanism.
if (Dispatcher().HasThreadAccess())
{
// TODO: GH 9458 will give us more context so we can try to choose a better profile.
auto hr = _OpenNewTab(nullptr, connection);
// Request a summon of this window to the foreground // Request a summon of this window to the foreground
_SummonWindowRequestedHandlers(*this, nullptr); _SummonWindowRequestedHandlers(*this, nullptr);
return hr;
}
else
{
til::latch latch{ 1 };
HRESULT finalVal = S_OK;
Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [&]() {
finalVal = _OpenNewTab(nullptr, connection);
_SummonWindowRequestedHandlers(*this, nullptr);
latch.count_down();
});
latch.wait();
return finalVal;
}
} }
// Method Description: // Method Description:

View file

@ -58,6 +58,8 @@ namespace winrt::TerminalApp::implementation
void Create(); void Create();
winrt::fire_and_forget NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e);
hstring Title(); hstring Title();
void TitlebarClicked(); void TitlebarClicked();
@ -83,8 +85,7 @@ namespace winrt::TerminalApp::implementation
winrt::TerminalApp::IDialogPresenter DialogPresenter() const; winrt::TerminalApp::IDialogPresenter DialogPresenter() const;
void DialogPresenter(winrt::TerminalApp::IDialogPresenter dialogPresenter); void DialogPresenter(winrt::TerminalApp::IDialogPresenter dialogPresenter);
size_t GetLastActiveControlTaskbarState(); winrt::TerminalApp::TaskbarState TaskbarState() const;
size_t GetLastActiveControlTaskbarProgress();
void ShowKeyboardServiceWarning(); void ShowKeyboardServiceWarning();
winrt::hstring KeyboardServiceDisabledText(); winrt::hstring KeyboardServiceDisabledText();
@ -186,12 +187,15 @@ namespace winrt::TerminalApp::implementation
void _CreateNewTabFlyout(); void _CreateNewTabFlyout();
void _OpenNewTabDropdown(); void _OpenNewTabDropdown();
void _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr); HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
void _CreateNewTabFromSettings(GUID profileGuid, const Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr); void _CreateNewTabFromPane(std::shared_ptr<Pane> pane);
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(GUID profileGuid, Microsoft::Terminal::Settings::Model::TerminalSettings settings); void _CreateNewTabWithProfileAndSettings(const Microsoft::Terminal::Settings::Model::Profile& profile, const Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettings settings);
winrt::fire_and_forget _OpenNewWindow(const bool elevate, const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs); winrt::fire_and_forget _OpenNewWindow(const bool elevate, const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
void _OpenNewTerminal(const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
bool _displayingCloseDialog{ false }; bool _displayingCloseDialog{ false };
void _SettingsButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); void _SettingsButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
void _CommandPaletteButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); void _CommandPaletteButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
@ -215,12 +219,16 @@ namespace winrt::TerminalApp::implementation
void _DuplicateFocusedTab(); void _DuplicateFocusedTab();
void _DuplicateTab(const TerminalTab& tab); void _DuplicateTab(const TerminalTab& tab);
void _SplitTab(TerminalTab& tab);
winrt::Windows::Foundation::IAsyncAction _HandleCloseTabRequested(winrt::TerminalApp::TabBase tab); winrt::Windows::Foundation::IAsyncAction _HandleCloseTabRequested(winrt::TerminalApp::TabBase tab);
void _CloseTabAtIndex(uint32_t index); void _CloseTabAtIndex(uint32_t index);
void _RemoveTab(const winrt::TerminalApp::TabBase& tab); void _RemoveTab(const winrt::TerminalApp::TabBase& tab);
winrt::fire_and_forget _RemoveTabs(const std::vector<winrt::TerminalApp::TabBase> tabs); winrt::fire_and_forget _RemoveTabs(const std::vector<winrt::TerminalApp::TabBase> tabs);
void _RegisterTerminalEvents(Microsoft::Terminal::Control::TermControl term, TerminalTab& hostingTab); void _InitializeTab(winrt::com_ptr<TerminalTab> newTabImpl);
void _RegisterTerminalEvents(Microsoft::Terminal::Control::TermControl term);
void _RegisterTabEvents(TerminalTab& hostingTab);
void _DismissTabContextMenus(); void _DismissTabContextMenus();
void _FocusCurrentTab(const bool focusAlways); void _FocusCurrentTab(const bool focusAlways);
@ -229,8 +237,10 @@ namespace winrt::TerminalApp::implementation
void _ResizeTabContent(const winrt::Windows::Foundation::Size& newSize); void _ResizeTabContent(const winrt::Windows::Foundation::Size& newSize);
void _SelectNextTab(const bool bMoveRight, const Windows::Foundation::IReference<Microsoft::Terminal::Settings::Model::TabSwitcherMode>& customTabSwitcherMode); void _SelectNextTab(const bool bMoveRight, const Windows::Foundation::IReference<Microsoft::Terminal::Settings::Model::TabSwitcherMode>& customTabSwitcherMode);
bool _SelectTab(const uint32_t tabIndex); bool _SelectTab(uint32_t tabIndex);
void _MoveFocus(const Microsoft::Terminal::Settings::Model::FocusDirection& direction); bool _MoveFocus(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool _SwapPane(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool _MovePane(const uint32_t tabIdx);
winrt::Microsoft::Terminal::Control::TermControl _GetActiveControl(); winrt::Microsoft::Terminal::Control::TermControl _GetActiveControl();
std::optional<uint32_t> _GetFocusedTabIndex() const noexcept; std::optional<uint32_t> _GetFocusedTabIndex() const noexcept;
@ -249,7 +259,13 @@ namespace winrt::TerminalApp::implementation
const Microsoft::Terminal::Settings::Model::SplitType splitMode = Microsoft::Terminal::Settings::Model::SplitType::Manual, const Microsoft::Terminal::Settings::Model::SplitType splitMode = Microsoft::Terminal::Settings::Model::SplitType::Manual,
const float splitSize = 0.5f, const float splitSize = 0.5f,
const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr); const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr);
void _SplitPane(TerminalTab& tab,
const Microsoft::Terminal::Settings::Model::SplitState splitType,
const Microsoft::Terminal::Settings::Model::SplitType splitMode = Microsoft::Terminal::Settings::Model::SplitType::Manual,
const float splitSize = 0.5f,
const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr);
void _ResizePane(const Microsoft::Terminal::Settings::Model::ResizeDirection& direction); void _ResizePane(const Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
void _ToggleSplitOrientation();
void _ScrollPage(ScrollDirection scrollDirection); void _ScrollPage(ScrollDirection scrollDirection);
void _ScrollToBufferEdge(ScrollDirection scrollDirection); void _ScrollToBufferEdge(ScrollDirection scrollDirection);
@ -331,7 +347,7 @@ namespace winrt::TerminalApp::implementation
winrt::Microsoft::Terminal::Settings::Model::Command _lastPreviewedCommand{ nullptr }; winrt::Microsoft::Terminal::Settings::Model::Command _lastPreviewedCommand{ nullptr };
winrt::Microsoft::Terminal::Settings::Model::TerminalSettings _originalSettings{ nullptr }; winrt::Microsoft::Terminal::Settings::Model::TerminalSettings _originalSettings{ nullptr };
void _OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection); HRESULT _OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection);
void _HandleToggleInboundPty(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); void _HandleToggleInboundPty(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args);
void _WindowRenamerActionClick(const IInspectable& sender, const IInspectable& eventArgs); void _WindowRenamerActionClick(const IInspectable& sender, const IInspectable& eventArgs);

View file

@ -1,5 +1,6 @@
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT license. // Licensed under the MIT license.
import "TaskbarState.idl";
namespace TerminalApp namespace TerminalApp
{ {
@ -42,8 +43,7 @@ namespace TerminalApp
void ShowKeyboardServiceWarning(); void ShowKeyboardServiceWarning();
String KeyboardServiceDisabledText { get; }; String KeyboardServiceDisabledText { get; };
UInt64 GetLastActiveControlTaskbarState(); TaskbarState TaskbarState{ get; };
UInt64 GetLastActiveControlTaskbarProgress();
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged; event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed; event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed;

View file

@ -25,19 +25,66 @@ namespace winrt
namespace winrt::TerminalApp::implementation namespace winrt::TerminalApp::implementation
{ {
TerminalTab::TerminalTab(const GUID& profile, const TermControl& control) TerminalTab::TerminalTab(const Profile& profile, const TermControl& control)
{ {
_rootPane = std::make_shared<Pane>(profile, control, true); _rootPane = std::make_shared<Pane>(profile, control, true);
_rootPane->Id(_nextPaneId); _rootPane->Id(_nextPaneId);
_activePane = _rootPane;
_mruPanes.insert(_mruPanes.begin(), _nextPaneId); _mruPanes.insert(_mruPanes.begin(), _nextPaneId);
++_nextPaneId; ++_nextPaneId;
_rootPane->Closed([=](auto&& /*s*/, auto&& /*e*/) { _Setup();
}
TerminalTab::TerminalTab(std::shared_ptr<Pane> rootPane)
{
_rootPane = rootPane;
_activePane = nullptr;
auto firstId = _nextPaneId;
_rootPane->WalkTree([&](std::shared_ptr<Pane> pane) {
// update the IDs on each pane
if (pane->_IsLeaf())
{
pane->Id(_nextPaneId);
_nextPaneId++;
}
// Try to find the pane marked active (if it exists)
if (pane->_lastActive)
{
_activePane = pane;
}
return false;
});
// In case none of the panes were already marked as the focus, just
// focus the first one.
if (_activePane == nullptr)
{
_rootPane->FocusPane(firstId);
_activePane = _rootPane->GetActivePane();
}
// Set the active control
_mruPanes.insert(_mruPanes.begin(), _activePane->Id().value());
_Setup();
}
// Method Description:
// - Shared setup for the constructors. Assumed that _rootPane has been set.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TerminalTab::_Setup()
{
_rootClosedToken = _rootPane->Closed([=](auto&& /*s*/, auto&& /*e*/) {
_ClosedHandlers(nullptr, nullptr); _ClosedHandlers(nullptr, nullptr);
}); });
_activePane = _rootPane;
Content(_rootPane->GetRootElement()); Content(_rootPane->GetRootElement());
_MakeTabViewItem(); _MakeTabViewItem();
@ -144,19 +191,31 @@ namespace winrt::TerminalApp::implementation
// that was last focused. // that was last focused.
TermControl TerminalTab::GetActiveTerminalControl() const TermControl TerminalTab::GetActiveTerminalControl() const
{ {
return _activePane->GetTerminalControl(); if (_activePane)
{
return _activePane->GetTerminalControl();
}
return nullptr;
} }
// Method Description: // Method Description:
// - Called after construction of a Tab object to bind event handlers to its // - Called after construction of a Tab object to bind event handlers to its
// associated Pane and TermControl object // associated Pane and TermControl objects
// Arguments: // Arguments:
// - control: reference to the TermControl object to bind event to // - <none>
// Return Value: // Return Value:
// - <none> // - <none>
void TerminalTab::Initialize(const TermControl& control) void TerminalTab::Initialize()
{ {
_BindEventHandlers(control); _rootPane->WalkTree([&](std::shared_ptr<Pane> pane) {
// Attach event handlers to each new pane
_AttachEventHandlersToPane(pane);
if (auto control = pane->GetTerminalControl())
{
_AttachEventHandlersToControl(pane->Id().value(), control);
}
return false;
});
} }
// Method Description: // Method Description:
@ -177,10 +236,9 @@ namespace winrt::TerminalApp::implementation
{ {
lastFocusedControl.Focus(_focusState); lastFocusedControl.Focus(_focusState);
// Update our own progress state, and fire an event signaling // Update our own progress state. This will fire an event signaling
// that our taskbar progress changed. // that our taskbar progress changed.
_UpdateProgressState(); _UpdateProgressState();
_TaskbarProgressChangedHandlers(lastFocusedControl, nullptr);
} }
// When we gain focus, remove the bell indicator if it is active // When we gain focus, remove the bell indicator if it is active
if (_tabStatus.BellIndicator()) if (_tabStatus.BellIndicator())
@ -199,35 +257,19 @@ namespace winrt::TerminalApp::implementation
// Return Value: // Return Value:
// - nullopt if no children of this tab were the last control to be // - nullopt if no children of this tab were the last control to be
// focused, else the GUID of the profile of the last control to be focused // focused, else the GUID of the profile of the last control to be focused
std::optional<GUID> TerminalTab::GetFocusedProfile() const noexcept Profile TerminalTab::GetFocusedProfile() const noexcept
{ {
return _activePane->GetFocusedProfile(); return _activePane->GetFocusedProfile();
} }
// Method Description: // Method Description:
// - Called after construction of a Tab object to bind event handlers to its // - Attempts to update the settings that apply to this tab.
// associated Pane and TermControl object // - Panes are handled elsewhere, by somebody who can establish broader knowledge
// Arguments: // of the settings that apply to all tabs.
// - control: reference to the TermControl object to bind event to
// Return Value: // Return Value:
// - <none> // - <none>
void TerminalTab::_BindEventHandlers(const TermControl& control) noexcept void TerminalTab::UpdateSettings()
{ {
_AttachEventHandlersToPane(_rootPane);
_AttachEventHandlersToControl(control);
}
// Method Description:
// - Attempts to update the settings of this tab's tree of panes.
// Arguments:
// - settings: The new TerminalSettingsCreateResult to apply to any matching controls
// - profile: The GUID of the profile these settings should apply to.
// Return Value:
// - <none>
void TerminalTab::UpdateSettings(const TerminalSettingsCreateResult& settings, const GUID& profile)
{
_rootPane->UpdateSettings(settings, profile);
// The tabWidthMode may have changed, update the header control accordingly // The tabWidthMode may have changed, update the header control accordingly
_UpdateHeaderControlMaxWidth(); _UpdateHeaderControlMaxWidth();
} }
@ -406,7 +448,7 @@ namespace winrt::TerminalApp::implementation
// - <none> // - <none>
void TerminalTab::SplitPane(SplitState splitType, void TerminalTab::SplitPane(SplitState splitType,
const float splitSize, const float splitSize,
const GUID& profile, const Profile& profile,
TermControl& control) TermControl& control)
{ {
// Make sure to take the ID before calling Split() - Split() will clear out the active pane's ID // Make sure to take the ID before calling Split() - Split() will clear out the active pane's ID
@ -426,10 +468,10 @@ namespace winrt::TerminalApp::implementation
++_nextPaneId; ++_nextPaneId;
} }
_activePane = first; _activePane = first;
_AttachEventHandlersToControl(control);
// Add a event handlers to the new panes' GotFocus event. When the pane // Add a event handlers to the new panes' GotFocus event. When the pane
// gains focus, we'll mark it as the new active pane. // gains focus, we'll mark it as the new active pane.
_AttachEventHandlersToControl(second->Id().value(), control);
_AttachEventHandlersToPane(first); _AttachEventHandlersToPane(first);
_AttachEventHandlersToPane(second); _AttachEventHandlersToPane(second);
@ -440,6 +482,127 @@ namespace winrt::TerminalApp::implementation
_UpdateActivePane(second); _UpdateActivePane(second);
} }
// Method Description:
// - Removes the currently active pane from this tab. If that was the only
// remaining pane, then the entire tab is closed as well.
// Arguments:
// - <none>
// Return Value:
// - The removed pane, if the remove succeeded.
std::shared_ptr<Pane> TerminalTab::DetachPane()
{
// if we only have one pane, remove it entirely
// and close this tab
if (_rootPane == _activePane)
{
return DetachRoot();
}
// Attempt to remove the active pane from the tree
if (const auto pane = _rootPane->DetachPane(_activePane))
{
// Just make sure that the remaining pane is marked active
_UpdateActivePane(_rootPane->GetActivePane());
return pane;
}
return nullptr;
}
// Method Description:
// - Closes this tab and returns the root pane to be used elsewhere.
// Arguments:
// - <none>
// Return Value:
// - The root pane.
std::shared_ptr<Pane> TerminalTab::DetachRoot()
{
// remove the closed event handler since we are closing the tab
// manually.
_rootPane->Closed(_rootClosedToken);
auto p = _rootPane;
p->WalkTree([](auto pane) {
pane->_PaneDetachedHandlers(pane);
return false;
});
// Clean up references and close the tab
_rootPane = nullptr;
_activePane = nullptr;
Content(nullptr);
_ClosedHandlers(nullptr, nullptr);
return p;
}
// Method Description:
// - Add an arbitrary pane to this tab. This will be added as a split on the
// currently active pane.
// Arguments:
// - pane: The pane to add.
// Return Value:
// - <none>
void TerminalTab::AttachPane(std::shared_ptr<Pane> pane)
{
// Add the new event handlers to the new pane(s)
// and update their ids.
pane->WalkTree([&](auto p) {
_AttachEventHandlersToPane(p);
if (p->_IsLeaf())
{
p->Id(_nextPaneId);
_nextPaneId++;
}
if (auto control = p->GetTerminalControl())
{
_AttachEventHandlersToControl(p->Id().value(), control);
}
return false;
});
// pass the old id to the new child
const auto previousId = _activePane->Id();
// Add the new pane as an automatic split on the active pane.
auto first = _activePane->AttachPane(pane, SplitState::Automatic);
// under current assumptions this condition should always be true.
if (previousId)
{
first->Id(previousId.value());
}
else
{
first->Id(_nextPaneId);
++_nextPaneId;
}
// Update with event handlers on the new child.
_activePane = first;
_AttachEventHandlersToPane(first);
// Make sure that we have the right pane set as the active pane
pane->WalkTree([&](auto p) {
if (p->_lastActive)
{
_UpdateActivePane(p);
return true;
}
return false;
});
}
// Method Description:
// - Find the currently active pane, and then switch the split direction of
// its parent. E.g. switch from Horizontal to Vertical.
// Return Value:
// - <none>
void TerminalTab::ToggleSplitOrientation()
{
_rootPane->ToggleSplitOrientation();
}
// Method Description: // Method Description:
// - See Pane::CalcSnappedDimension // - See Pane::CalcSnappedDimension
float TerminalTab::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const float TerminalTab::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const
@ -481,22 +644,68 @@ namespace winrt::TerminalApp::implementation
// Arguments: // Arguments:
// - direction: The direction to move the focus in. // - direction: The direction to move the focus in.
// Return Value: // Return Value:
// - <none> // - Whether changing the focus succeeded. This allows a keychord to propagate
void TerminalTab::NavigateFocus(const FocusDirection& direction) // to the terminal when no other panes are present (GH#6219)
bool TerminalTab::NavigateFocus(const FocusDirection& direction)
{ {
if (direction == FocusDirection::Previous) if (direction == FocusDirection::Previous)
{ {
if (_mruPanes.size() < 2)
{
return false;
}
// To get to the previous pane, get the id of the previous pane and focus to that // To get to the previous pane, get the id of the previous pane and focus to that
_rootPane->FocusPane(_mruPanes.at(1)); return _rootPane->FocusPane(_mruPanes.at(1));
} }
else else
{ {
// NOTE: This _must_ be called on the root pane, so that it can propagate // NOTE: This _must_ be called on the root pane, so that it can propagate
// throughout the entire tree. // throughout the entire tree.
_rootPane->NavigateFocus(direction); if (auto newFocus = _rootPane->NavigateDirection(_activePane, direction))
{
return _rootPane->FocusPane(newFocus);
}
return false;
} }
} }
// Method Description:
// - Attempts to swap the location of the focused pane with another pane
// according to direction. When there are multiple adjacent panes it will
// select the first one (top-left-most).
// Arguments:
// - direction: The direction to move the pane in.
// Return Value:
// - true if two panes were swapped.
bool TerminalTab::SwapPane(const FocusDirection& direction)
{
if (direction == FocusDirection::Previous)
{
if (_mruPanes.size() < 2)
{
return false;
}
if (auto lastPane = _rootPane->FindPane(_mruPanes.at(1)))
{
return _rootPane->SwapPanes(_activePane, lastPane);
}
}
else
{
// NOTE: This _must_ be called on the root pane, so that it can propagate
// throughout the entire tree.
if (auto neighbor = _rootPane->NavigateDirection(_activePane, direction))
{
return _rootPane->SwapPanes(_activePane, neighbor);
}
return false;
}
return false;
}
bool TerminalTab::FocusPane(const uint32_t id) bool TerminalTab::FocusPane(const uint32_t id)
{ {
return _rootPane->FocusPane(id); return _rootPane->FocusPane(id);
@ -506,7 +715,10 @@ namespace winrt::TerminalApp::implementation
// - Prepares this tab for being removed from the UI hierarchy by shutting down all active connections. // - Prepares this tab for being removed from the UI hierarchy by shutting down all active connections.
void TerminalTab::Shutdown() void TerminalTab::Shutdown()
{ {
_rootPane->Shutdown(); if (_rootPane)
{
_rootPane->Shutdown();
}
} }
// Method Description: // Method Description:
@ -551,6 +763,34 @@ namespace winrt::TerminalApp::implementation
_headerControl.BeginRename(); _headerControl.BeginRename();
} }
// Method Description:
// - Removes any event handlers set by the tab on the given pane's control.
// The pane's ID is the most stable identifier for a given control, because
// the control itself doesn't have a particular ID and its pointer is
// unstable since it is moved when panes split.
// Arguments:
// - paneId: The ID of the pane that contains the given control.
// - control: the control to remove events from.
// Return Value:
// - <none>
void TerminalTab::_DetachEventHandlersFromControl(const uint32_t paneId, const TermControl& control)
{
auto it = _controlEvents.find(paneId);
if (it != _controlEvents.end())
{
auto& events = it->second;
control.TitleChanged(events.titleToken);
control.FontSizeChanged(events.fontToken);
control.TabColorChanged(events.colorToken);
control.SetTaskbarProgress(events.taskbarToken);
control.ReadOnlyChanged(events.readOnlyToken);
control.FocusFollowMouseRequested(events.focusToken);
_controlEvents.erase(paneId);
}
}
// Method Description: // Method Description:
// - Register any event handlers that we may need with the given TermControl. // - Register any event handlers that we may need with the given TermControl.
// This should be called on each and every TermControl that we add to the tree // This should be called on each and every TermControl that we add to the tree
@ -558,15 +798,17 @@ namespace winrt::TerminalApp::implementation
// * notify us when the control's title changed, so we can update our own // * notify us when the control's title changed, so we can update our own
// title (if necessary) // title (if necessary)
// Arguments: // Arguments:
// - paneId: the ID of the pane that this control belongs to.
// - control: the TermControl to add events to. // - control: the TermControl to add events to.
// Return Value: // Return Value:
// - <none> // - <none>
void TerminalTab::_AttachEventHandlersToControl(const TermControl& control) void TerminalTab::_AttachEventHandlersToControl(const uint32_t paneId, const TermControl& control)
{ {
auto weakThis{ get_weak() }; auto weakThis{ get_weak() };
auto dispatcher = TabViewItem().Dispatcher(); auto dispatcher = TabViewItem().Dispatcher();
ControlEventTokens events{};
control.TitleChanged([weakThis](auto&&, auto&&) { events.titleToken = control.TitleChanged([weakThis](auto&&, auto&&) {
// Check if Tab's lifetime has expired // Check if Tab's lifetime has expired
if (auto tab{ weakThis.get() }) if (auto tab{ weakThis.get() })
{ {
@ -581,16 +823,16 @@ namespace winrt::TerminalApp::implementation
// On the latter event, we tell the root pane to resize itself so that its descendants // On the latter event, we tell the root pane to resize itself so that its descendants
// (including ourself) can properly snap to character grids. In future, we may also // (including ourself) can properly snap to character grids. In future, we may also
// want to do that on regular font changes. // want to do that on regular font changes.
control.FontSizeChanged([this](const int /* fontWidth */, events.fontToken = control.FontSizeChanged([this](const int /* fontWidth */,
const int /* fontHeight */, const int /* fontHeight */,
const bool isInitialChange) { const bool isInitialChange) {
if (isInitialChange) if (isInitialChange)
{ {
_rootPane->Relayout(); _rootPane->Relayout();
} }
}); });
control.TabColorChanged([weakThis](auto&&, auto&&) { events.colorToken = control.TabColorChanged([weakThis](auto&&, auto&&) {
if (auto tab{ weakThis.get() }) if (auto tab{ weakThis.get() })
{ {
// The control's tabColor changed, but it is not necessarily the // The control's tabColor changed, but it is not necessarily the
@ -600,7 +842,7 @@ namespace winrt::TerminalApp::implementation
} }
}); });
control.SetTaskbarProgress([dispatcher, weakThis](auto&&, auto &&) -> winrt::fire_and_forget { events.taskbarToken = control.SetTaskbarProgress([dispatcher, weakThis](auto&&, auto &&) -> winrt::fire_and_forget {
co_await winrt::resume_foreground(dispatcher); co_await winrt::resume_foreground(dispatcher);
// Check if Tab's lifetime has expired // Check if Tab's lifetime has expired
if (auto tab{ weakThis.get() }) if (auto tab{ weakThis.get() })
@ -609,14 +851,14 @@ namespace winrt::TerminalApp::implementation
} }
}); });
control.ReadOnlyChanged([weakThis](auto&&, auto&&) { events.readOnlyToken = control.ReadOnlyChanged([weakThis](auto&&, auto&&) {
if (auto tab{ weakThis.get() }) if (auto tab{ weakThis.get() })
{ {
tab->_RecalculateAndApplyReadOnly(); tab->_RecalculateAndApplyReadOnly();
} }
}); });
control.FocusFollowMouseRequested([weakThis](auto&& sender, auto&&) { events.focusToken = control.FocusFollowMouseRequested([weakThis](auto&& sender, auto&&) {
if (const auto tab{ weakThis.get() }) if (const auto tab{ weakThis.get() })
{ {
if (tab->_focusState != FocusState::Unfocused) if (tab->_focusState != FocusState::Unfocused)
@ -628,6 +870,31 @@ namespace winrt::TerminalApp::implementation
} }
} }
}); });
_controlEvents[paneId] = events;
}
// Method Description:
// - Get the combined taskbar state for the tab. This is the combination of
// all the states of all our panes. Taskbar states are given a priority
// based on the rules in:
// https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist3-setprogressstate
// under "How the Taskbar Button Chooses the Progress Indicator for a
// Group"
// Arguments:
// - <none>
// Return Value:
// - A TaskbarState object representing the combined taskbar state and
// progress percentage of all our panes.
winrt::TerminalApp::TaskbarState TerminalTab::GetCombinedTaskbarState() const
{
std::vector<winrt::TerminalApp::TaskbarState> states;
if (_rootPane)
{
_rootPane->CollectTaskbarStates(states);
}
return states.empty() ? winrt::make<winrt::TerminalApp::implementation::TaskbarState>() :
*std::min_element(states.begin(), states.end(), TerminalApp::implementation::TaskbarState::ComparePriority);
} }
// Method Description: // Method Description:
@ -645,37 +912,39 @@ namespace winrt::TerminalApp::implementation
// - <none> // - <none>
void TerminalTab::_UpdateProgressState() void TerminalTab::_UpdateProgressState()
{ {
if (const auto& activeControl{ GetActiveTerminalControl() }) const auto state{ GetCombinedTaskbarState() };
{
const auto taskbarState = activeControl.TaskbarState();
// The progress of the control changed, but not necessarily the progress of the tab.
// Set the tab's progress ring to the active pane's progress
if (taskbarState > 0)
{
if (taskbarState == 3)
{
// 3 is the indeterminate state, set the progress ring as such
_tabStatus.IsProgressRingIndeterminate(true);
}
else
{
// any non-indeterminate state has a value, set the progress ring as such
_tabStatus.IsProgressRingIndeterminate(false);
const auto progressValue = gsl::narrow<uint32_t>(activeControl.TaskbarProgress()); const auto taskbarState = state.State();
_tabStatus.ProgressValue(progressValue); // The progress of the control changed, but not necessarily the progress of the tab.
} // Set the tab's progress ring to the active pane's progress
// Hide the tab icon (the progress ring is placed over it) if (taskbarState > 0)
HideIcon(true); {
_tabStatus.IsProgressRingActive(true); if (taskbarState == 3)
{
// 3 is the indeterminate state, set the progress ring as such
_tabStatus.IsProgressRingIndeterminate(true);
} }
else else
{ {
// Show the tab icon // any non-indeterminate state has a value, set the progress ring as such
HideIcon(false); _tabStatus.IsProgressRingIndeterminate(false);
_tabStatus.IsProgressRingActive(false);
const auto progressValue = gsl::narrow<uint32_t>(state.Progress());
_tabStatus.ProgressValue(progressValue);
} }
// Hide the tab icon (the progress ring is placed over it)
HideIcon(true);
_tabStatus.IsProgressRingActive(true);
} }
else
{
// Show the tab icon
HideIcon(false);
_tabStatus.IsProgressRingActive(false);
}
// fire an event signaling that our taskbar progress changed.
_TaskbarProgressChangedHandlers(nullptr, nullptr);
} }
// Method Description: // Method Description:
@ -732,7 +1001,7 @@ namespace winrt::TerminalApp::implementation
auto weakThis{ get_weak() }; auto weakThis{ get_weak() };
std::weak_ptr<Pane> weakPane{ pane }; std::weak_ptr<Pane> weakPane{ pane };
pane->GotFocus([weakThis](std::shared_ptr<Pane> sender) { auto gotFocusToken = pane->GotFocus([weakThis](std::shared_ptr<Pane> sender) {
// Do nothing if the Tab's lifetime is expired or pane isn't new. // Do nothing if the Tab's lifetime is expired or pane isn't new.
auto tab{ weakThis.get() }; auto tab{ weakThis.get() };
@ -752,7 +1021,7 @@ namespace winrt::TerminalApp::implementation
} }
}); });
pane->LostFocus([weakThis](std::shared_ptr<Pane> /*sender*/) { auto lostFocusToken = pane->LostFocus([weakThis](std::shared_ptr<Pane> /*sender*/) {
// Do nothing if the Tab's lifetime is expired or pane isn't new. // Do nothing if the Tab's lifetime is expired or pane isn't new.
auto tab{ weakThis.get() }; auto tab{ weakThis.get() };
@ -766,7 +1035,7 @@ namespace winrt::TerminalApp::implementation
// Add a Closed event handler to the Pane. If the pane closes out from // Add a Closed event handler to the Pane. If the pane closes out from
// underneath us, and it's zoomed, we want to be able to make sure to // underneath us, and it's zoomed, we want to be able to make sure to
// update our state accordingly to un-zoom that pane. See GH#7252. // update our state accordingly to un-zoom that pane. See GH#7252.
pane->Closed([weakThis, weakPane](auto&& /*s*/, auto && /*e*/) -> winrt::fire_and_forget { auto closedToken = pane->Closed([weakThis, weakPane](auto&& /*s*/, auto && /*e*/) -> winrt::fire_and_forget {
if (auto tab{ weakThis.get() }) if (auto tab{ weakThis.get() })
{ {
if (tab->_zoomedPane) if (tab->_zoomedPane)
@ -791,7 +1060,7 @@ namespace winrt::TerminalApp::implementation
}); });
// Add a PaneRaiseBell event handler to the Pane // Add a PaneRaiseBell event handler to the Pane
pane->PaneRaiseBell([weakThis](auto&& /*s*/, auto&& visual) { auto bellToken = pane->PaneRaiseBell([weakThis](auto&& /*s*/, auto&& visual) {
if (auto tab{ weakThis.get() }) if (auto tab{ weakThis.get() })
{ {
if (visual) if (visual)
@ -813,6 +1082,40 @@ namespace winrt::TerminalApp::implementation
} }
} }
}); });
// box the event token so that we can give a reference to it in the
// event handler.
auto detachedToken = std::make_shared<winrt::event_token>();
// Add a Detached event handler to the Pane to clean up tab state
// and other event handlers when a pane is removed from this tab.
*detachedToken = pane->Detached([weakThis, weakPane, gotFocusToken, lostFocusToken, closedToken, bellToken, detachedToken](std::shared_ptr<Pane> /*sender*/) {
// Make sure we do this at most once
if (auto pane{ weakPane.lock() })
{
pane->Detached(*detachedToken);
pane->GotFocus(gotFocusToken);
pane->LostFocus(lostFocusToken);
pane->Closed(closedToken);
pane->PaneRaiseBell(bellToken);
if (auto tab{ weakThis.get() })
{
if (auto control = pane->GetTerminalControl())
{
tab->_DetachEventHandlersFromControl(pane->Id().value(), control);
}
for (auto i = tab->_mruPanes.begin(); i != tab->_mruPanes.end(); ++i)
{
if (*i == pane->Id())
{
tab->_mruPanes.erase(i);
break;
}
}
}
}
});
} }
// Method Description: // Method Description:
@ -891,12 +1194,30 @@ namespace winrt::TerminalApp::implementation
duplicateTabMenuItem.Icon(duplicateTabSymbol); duplicateTabMenuItem.Icon(duplicateTabSymbol);
} }
Controls::MenuFlyoutItem splitTabMenuItem;
{
// "Split Tab"
Controls::FontIcon splitTabSymbol;
splitTabSymbol.FontFamily(Media::FontFamily{ L"Segoe MDL2 Assets" });
splitTabSymbol.Glyph(L"\xF246"); // ViewDashboard
splitTabMenuItem.Click([weakThis](auto&&, auto&&) {
if (auto tab{ weakThis.get() })
{
tab->_SplitTabRequestedHandlers();
}
});
splitTabMenuItem.Text(RS_(L"SplitTabText"));
splitTabMenuItem.Icon(splitTabSymbol);
}
// Build the menu // Build the menu
Controls::MenuFlyout contextMenuFlyout; Controls::MenuFlyout contextMenuFlyout;
Controls::MenuFlyoutSeparator menuSeparator; Controls::MenuFlyoutSeparator menuSeparator;
contextMenuFlyout.Items().Append(chooseColorMenuItem); contextMenuFlyout.Items().Append(chooseColorMenuItem);
contextMenuFlyout.Items().Append(renameTabMenuItem); contextMenuFlyout.Items().Append(renameTabMenuItem);
contextMenuFlyout.Items().Append(duplicateTabMenuItem); contextMenuFlyout.Items().Append(duplicateTabMenuItem);
contextMenuFlyout.Items().Append(splitTabMenuItem);
contextMenuFlyout.Items().Append(menuSeparator); contextMenuFlyout.Items().Append(menuSeparator);
// GH#5750 - When the context menu is dismissed with ESC, toss the focus // GH#5750 - When the context menu is dismissed with ESC, toss the focus
@ -1188,6 +1509,7 @@ namespace winrt::TerminalApp::implementation
EnterZoom(); EnterZoom();
} }
} }
void TerminalTab::EnterZoom() void TerminalTab::EnterZoom()
{ {
_zoomedPane = _activePane; _zoomedPane = _activePane;
@ -1269,4 +1591,5 @@ namespace winrt::TerminalApp::implementation
DEFINE_EVENT(TerminalTab, ColorCleared, _colorCleared, winrt::delegate<>); DEFINE_EVENT(TerminalTab, ColorCleared, _colorCleared, winrt::delegate<>);
DEFINE_EVENT(TerminalTab, TabRaiseVisualBell, _TabRaiseVisualBellHandlers, winrt::delegate<>); DEFINE_EVENT(TerminalTab, TabRaiseVisualBell, _TabRaiseVisualBellHandlers, winrt::delegate<>);
DEFINE_EVENT(TerminalTab, DuplicateRequested, _DuplicateRequestedHandlers, winrt::delegate<>); DEFINE_EVENT(TerminalTab, DuplicateRequested, _DuplicateRequestedHandlers, winrt::delegate<>);
DEFINE_EVENT(TerminalTab, SplitTabRequested, _SplitTabRequestedHandlers, winrt::delegate<>);
} }

View file

@ -21,23 +21,29 @@ namespace winrt::TerminalApp::implementation
struct TerminalTab : TerminalTabT<TerminalTab, TabBase> struct TerminalTab : TerminalTabT<TerminalTab, TabBase>
{ {
public: public:
TerminalTab(const GUID& profile, const winrt::Microsoft::Terminal::Control::TermControl& control); TerminalTab(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile, const winrt::Microsoft::Terminal::Control::TermControl& control);
TerminalTab(std::shared_ptr<Pane> rootPane);
// Called after construction to perform the necessary setup, which relies on weak_ptr // Called after construction to perform the necessary setup, which relies on weak_ptr
void Initialize(const winrt::Microsoft::Terminal::Control::TermControl& control); void Initialize();
winrt::Microsoft::Terminal::Control::TermControl GetActiveTerminalControl() const; winrt::Microsoft::Terminal::Control::TermControl GetActiveTerminalControl() const;
std::optional<GUID> GetFocusedProfile() const noexcept; winrt::Microsoft::Terminal::Settings::Model::Profile GetFocusedProfile() const noexcept;
void Focus(winrt::Windows::UI::Xaml::FocusState focusState) override; void Focus(winrt::Windows::UI::Xaml::FocusState focusState) override;
winrt::fire_and_forget Scroll(const int delta); winrt::fire_and_forget Scroll(const int delta);
std::shared_ptr<Pane> DetachRoot();
std::shared_ptr<Pane> DetachPane();
void AttachPane(std::shared_ptr<Pane> pane);
void SplitPane(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType, void SplitPane(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType,
const float splitSize, const float splitSize,
const GUID& profile, const winrt::Microsoft::Terminal::Settings::Model::Profile& profile,
winrt::Microsoft::Terminal::Control::TermControl& control); winrt::Microsoft::Terminal::Control::TermControl& control);
void ToggleSplitOrientation();
winrt::fire_and_forget UpdateIcon(const winrt::hstring iconPath); winrt::fire_and_forget UpdateIcon(const winrt::hstring iconPath);
winrt::fire_and_forget HideIcon(const bool hide); winrt::fire_and_forget HideIcon(const bool hide);
@ -52,10 +58,11 @@ namespace winrt::TerminalApp::implementation
void ResizeContent(const winrt::Windows::Foundation::Size& newSize); void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
void ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction); void ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
void NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction); bool NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool SwapPane(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool FocusPane(const uint32_t id); bool FocusPane(const uint32_t id);
void UpdateSettings(const Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, const GUID& profile); void UpdateSettings();
winrt::fire_and_forget UpdateTitle(); winrt::fire_and_forget UpdateTitle();
void Shutdown() override; void Shutdown() override;
@ -81,6 +88,9 @@ namespace winrt::TerminalApp::implementation
void TogglePaneReadOnly(); void TogglePaneReadOnly();
std::shared_ptr<Pane> GetActivePane() const; std::shared_ptr<Pane> GetActivePane() const;
winrt::TerminalApp::TaskbarState GetCombinedTaskbarState() const;
std::shared_ptr<Pane> GetRootPane() const { return _rootPane; }
winrt::TerminalApp::TerminalTabStatus TabStatus() winrt::TerminalApp::TerminalTabStatus TabStatus()
{ {
@ -92,12 +102,14 @@ namespace winrt::TerminalApp::implementation
DECLARE_EVENT(ColorCleared, _colorCleared, winrt::delegate<>); DECLARE_EVENT(ColorCleared, _colorCleared, winrt::delegate<>);
DECLARE_EVENT(TabRaiseVisualBell, _TabRaiseVisualBellHandlers, winrt::delegate<>); DECLARE_EVENT(TabRaiseVisualBell, _TabRaiseVisualBellHandlers, winrt::delegate<>);
DECLARE_EVENT(DuplicateRequested, _DuplicateRequestedHandlers, winrt::delegate<>); DECLARE_EVENT(DuplicateRequested, _DuplicateRequestedHandlers, winrt::delegate<>);
DECLARE_EVENT(SplitTabRequested, _SplitTabRequestedHandlers, winrt::delegate<>);
TYPED_EVENT(TaskbarProgressChanged, IInspectable, IInspectable); TYPED_EVENT(TaskbarProgressChanged, IInspectable, IInspectable);
private: private:
std::shared_ptr<Pane> _rootPane{ nullptr }; std::shared_ptr<Pane> _rootPane{ nullptr };
std::shared_ptr<Pane> _activePane{ nullptr }; std::shared_ptr<Pane> _activePane{ nullptr };
std::shared_ptr<Pane> _zoomedPane{ nullptr }; std::shared_ptr<Pane> _zoomedPane{ nullptr };
winrt::hstring _lastIconPath{}; winrt::hstring _lastIconPath{};
winrt::TerminalApp::ColorPickupFlyout _tabColorPickup{}; winrt::TerminalApp::ColorPickupFlyout _tabColorPickup{};
std::optional<winrt::Windows::UI::Color> _themeTabColor{}; std::optional<winrt::Windows::UI::Color> _themeTabColor{};
@ -105,6 +117,19 @@ namespace winrt::TerminalApp::implementation
winrt::TerminalApp::TabHeaderControl _headerControl{}; winrt::TerminalApp::TabHeaderControl _headerControl{};
winrt::TerminalApp::TerminalTabStatus _tabStatus{}; winrt::TerminalApp::TerminalTabStatus _tabStatus{};
struct ControlEventTokens
{
winrt::event_token titleToken;
winrt::event_token fontToken;
winrt::event_token colorToken;
winrt::event_token taskbarToken;
winrt::event_token readOnlyToken;
winrt::event_token focusToken;
};
std::unordered_map<uint32_t, ControlEventTokens> _controlEvents;
winrt::event_token _rootClosedToken{};
std::vector<uint32_t> _mruPanes; std::vector<uint32_t> _mruPanes;
uint32_t _nextPaneId{ 0 }; uint32_t _nextPaneId{ 0 };
@ -117,6 +142,8 @@ namespace winrt::TerminalApp::implementation
winrt::TerminalApp::ShortcutActionDispatch _dispatch; winrt::TerminalApp::ShortcutActionDispatch _dispatch;
void _Setup();
std::optional<Windows::UI::Xaml::DispatcherTimer> _bellIndicatorTimer; std::optional<Windows::UI::Xaml::DispatcherTimer> _bellIndicatorTimer;
void _BellIndicatorTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e); void _BellIndicatorTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
@ -129,9 +156,8 @@ namespace winrt::TerminalApp::implementation
void _RefreshVisualState(); void _RefreshVisualState();
void _BindEventHandlers(const winrt::Microsoft::Terminal::Control::TermControl& control) noexcept; void _DetachEventHandlersFromControl(const uint32_t paneId, const winrt::Microsoft::Terminal::Control::TermControl& control);
void _AttachEventHandlersToControl(const uint32_t paneId, const winrt::Microsoft::Terminal::Control::TermControl& control);
void _AttachEventHandlersToControl(const winrt::Microsoft::Terminal::Control::TermControl& control);
void _AttachEventHandlersToPane(std::shared_ptr<Pane> pane); void _AttachEventHandlersToPane(std::shared_ptr<Pane> pane);
void _UpdateActivePane(std::shared_ptr<Pane> pane); void _UpdateActivePane(std::shared_ptr<Pane> pane);

View file

@ -89,13 +89,13 @@
</ItemGroup> </ItemGroup>
<Import Project="$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" /> <Import Project="$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets')" />
<Import Project="$(OpenConsoleDir)packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('$(OpenConsoleDir)packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" /> <Import Project="$(OpenConsoleDir)packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('$(OpenConsoleDir)packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <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> <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> </PropertyGroup>
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets'))" /> <Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\build\native\Microsoft.UI.Xaml.targets'))" />
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" /> <Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.1.3\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
</Target> </Target>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.1.3" targetFramework="native" /> <package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.1.3" targetFramework="native" />
<package id="Microsoft.UI.Xaml" version="2.5.0-prerelease.201202003" targetFramework="native" /> <package id="Microsoft.UI.Xaml" version="2.6.2-prerelease.210818003" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.210309.3" targetFramework="native" /> <package id="Microsoft.Windows.CppWinRT" version="2.0.210309.3" targetFramework="native" />
</packages> </packages>

View file

@ -66,6 +66,7 @@ TRACELOGGING_DECLARE_PROVIDER(g_hTerminalAppProvider);
#include <telemetry/ProjectTelemetry.h> #include <telemetry/ProjectTelemetry.h>
#include <TraceLoggingActivity.h> #include <TraceLoggingActivity.h>
#include <msctf.h>
#include <shellapi.h> #include <shellapi.h>
#include <shobjidl_core.h> #include <shobjidl_core.h>

View file

@ -96,7 +96,11 @@ int wmain(int /*argc*/, wchar_t** /*argv*/)
const auto size = GetConsoleScreenSize(conOut); const auto size = GetConsoleScreenSize(conOut);
AzureConnection azureConn{ gsl::narrow_cast<uint32_t>(size.Y), gsl::narrow_cast<uint32_t>(size.X) }; AzureConnection azureConn{};
winrt::Windows::Foundation::Collections::ValueSet vs{};
vs.Insert(L"initialRows", winrt::Windows::Foundation::PropertyValue::CreateUInt32(gsl::narrow_cast<uint32_t>(size.Y)));
vs.Insert(L"initialCols", winrt::Windows::Foundation::PropertyValue::CreateUInt32(gsl::narrow_cast<uint32_t>(size.X)));
azureConn.Initialize(vs);
const auto state = RunConnectionToCompletion(azureConn, conOut, conIn); const auto state = RunConnectionToCompletion(azureConn, conOut, conIn);

View file

@ -33,6 +33,7 @@ Abstract:
#include <wil/cppwinrt.h> #include <wil/cppwinrt.h>
#include <winrt/Windows.system.h> #include <winrt/Windows.system.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h> #include <winrt/Windows.Foundation.Collections.h>
#include <wil/resource.h> #include <wil/resource.h>

View file

@ -71,11 +71,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
return (AzureClientID != L"0"); return (AzureClientID != L"0");
} }
AzureConnection::AzureConnection(const uint32_t initialRows, const uint32_t initialCols) : void AzureConnection::Initialize(const Windows::Foundation::Collections::ValueSet& settings)
_initialRows{ initialRows },
_initialCols{ initialCols },
_expiry{}
{ {
if (settings)
{
_initialRows = winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialRows").try_as<Windows::Foundation::IPropertyValue>(), _initialRows);
_initialCols = winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialCols").try_as<Windows::Foundation::IPropertyValue>(), _initialCols);
}
} }
// Method description: // Method description:

View file

@ -21,7 +21,9 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{ {
static winrt::guid ConnectionType() noexcept; static winrt::guid ConnectionType() noexcept;
static bool IsAzureConnectionAvailable() noexcept; static bool IsAzureConnectionAvailable() noexcept;
AzureConnection(const uint32_t rows, const uint32_t cols);
AzureConnection() = default;
void Initialize(const Windows::Foundation::Collections::ValueSet& settings);
void Start(); void Start();
void WriteInput(hstring const& data); void WriteInput(hstring const& data);

View file

@ -10,7 +10,7 @@ namespace Microsoft.Terminal.TerminalConnection
static Guid ConnectionType { get; }; static Guid ConnectionType { get; };
static Boolean IsAzureConnectionAvailable(); static Boolean IsAzureConnectionAvailable();
AzureConnection(UInt32 rows, UInt32 columns); AzureConnection();
}; };
} }

View file

@ -11,6 +11,8 @@ using namespace Microsoft::WRL;
static NewHandoffFunction _pfnHandoff = nullptr; static NewHandoffFunction _pfnHandoff = nullptr;
// The registration ID of the class object for clean up later // The registration ID of the class object for clean up later
static DWORD g_cTerminalHandoffRegistration = 0; static DWORD g_cTerminalHandoffRegistration = 0;
// Mutex so we only do start/stop/establish one at a time.
static std::shared_mutex _mtx;
// Routine Description: // Routine Description:
// - Starts listening for TerminalHandoff requests by registering // - Starts listening for TerminalHandoff requests by registering
@ -19,9 +21,11 @@ static DWORD g_cTerminalHandoffRegistration = 0;
// - pfnHandoff - Function to callback when a handoff is received // - pfnHandoff - Function to callback when a handoff is received
// Return Value: // Return Value:
// - S_OK, E_NOT_VALID_STATE (start called when already started) or relevant COM registration error. // - S_OK, E_NOT_VALID_STATE (start called when already started) or relevant COM registration error.
HRESULT CTerminalHandoff::s_StartListening(NewHandoffFunction pfnHandoff) noexcept HRESULT CTerminalHandoff::s_StartListening(NewHandoffFunction pfnHandoff)
try try
{ {
std::unique_lock lock{ _mtx };
RETURN_HR_IF(E_NOT_VALID_STATE, _pfnHandoff != nullptr); RETURN_HR_IF(E_NOT_VALID_STATE, _pfnHandoff != nullptr);
const auto classFactory = Make<SimpleClassFactory<CTerminalHandoff>>(); const auto classFactory = Make<SimpleClassFactory<CTerminalHandoff>>();
@ -31,7 +35,7 @@ try
ComPtr<IUnknown> unk; ComPtr<IUnknown> unk;
RETURN_IF_FAILED(classFactory.As(&unk)); RETURN_IF_FAILED(classFactory.As(&unk));
RETURN_IF_FAILED(CoRegisterClassObject(__uuidof(CTerminalHandoff), unk.Get(), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &g_cTerminalHandoffRegistration)); RETURN_IF_FAILED(CoRegisterClassObject(__uuidof(CTerminalHandoff), unk.Get(), CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE, &g_cTerminalHandoffRegistration));
_pfnHandoff = pfnHandoff; _pfnHandoff = pfnHandoff;
@ -46,8 +50,10 @@ CATCH_RETURN()
// - <none> // - <none>
// Return Value: // Return Value:
// - S_OK, E_NOT_VALID_STATE (stop called when not started), or relevant COM class revoke error // - S_OK, E_NOT_VALID_STATE (stop called when not started), or relevant COM class revoke error
HRESULT CTerminalHandoff::s_StopListening() noexcept HRESULT CTerminalHandoff::s_StopListening()
{ {
std::unique_lock lock{ _mtx };
RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _pfnHandoff); RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _pfnHandoff);
_pfnHandoff = nullptr; _pfnHandoff = nullptr;
@ -91,10 +97,19 @@ static HRESULT _duplicateHandle(const HANDLE in, HANDLE& out) noexcept
// - E_NOT_VALID_STATE if a event handler is not registered before calling. `::DuplicateHandle` // - E_NOT_VALID_STATE if a event handler is not registered before calling. `::DuplicateHandle`
// error codes if we cannot manage to make our own copy of handles to retain. Or S_OK/error // error codes if we cannot manage to make our own copy of handles to retain. Or S_OK/error
// from the registered handler event function. // from the registered handler event function.
HRESULT CTerminalHandoff::EstablishPtyHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client) noexcept HRESULT CTerminalHandoff::EstablishPtyHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client)
{ {
// Stash a local copy of _pfnHandoff before we stop listening.
auto localPfnHandoff = _pfnHandoff;
// Because we are REGCLS_SINGLEUSE... we need to `CoRevokeClassObject` after we handle this ONE call.
// COM does not automatically clean that up for us. We must do it.
s_StopListening();
std::unique_lock lock{ _mtx };
// Report an error if no one registered a handoff function before calling this. // Report an error if no one registered a handoff function before calling this.
RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _pfnHandoff); RETURN_HR_IF_NULL(E_NOT_VALID_STATE, localPfnHandoff);
// Duplicate the handles from what we received. // Duplicate the handles from what we received.
// The contract with COM specifies that any HANDLEs we receive from the caller belong // The contract with COM specifies that any HANDLEs we receive from the caller belong
@ -108,5 +123,5 @@ HRESULT CTerminalHandoff::EstablishPtyHandoff(HANDLE in, HANDLE out, HANDLE sign
RETURN_IF_FAILED(_duplicateHandle(client, client)); RETURN_IF_FAILED(_duplicateHandle(client, client));
// Call registered handler from when we started listening. // Call registered handler from when we started listening.
return _pfnHandoff(in, out, signal, ref, server, client); return localPfnHandoff(in, out, signal, ref, server, client);
} }

View file

@ -37,12 +37,12 @@ struct __declspec(uuid(__CLSID_CTerminalHandoff))
HANDLE signal, HANDLE signal,
HANDLE ref, HANDLE ref,
HANDLE server, HANDLE server,
HANDLE client) noexcept override; HANDLE client) override;
#pragma endregion #pragma endregion
static HRESULT s_StartListening(NewHandoffFunction pfnHandoff) noexcept; static HRESULT s_StartListening(NewHandoffFunction pfnHandoff);
static HRESULT s_StopListening() noexcept; static HRESULT s_StopListening();
}; };
// Disable warnings from the CoCreatableClass macro as the value it provides for // Disable warnings from the CoCreatableClass macro as the value it provides for

View file

@ -0,0 +1,66 @@
#include "pch.h"
#include "ConnectionInformation.h"
#include "ConnectionInformation.g.cpp"
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
ConnectionInformation::ConnectionInformation(hstring const& className,
const Windows::Foundation::Collections::ValueSet& settings) :
_ClassName{ className },
_Settings{ settings }
{
}
// Function Description:
// - Create an instance of the connection specified in the
// ConnectionInformation, and Initialize it.
// - This static method allows the content process to create a connection
// from information that lives in the window process.
// Arguments:
// - info: A ConnectionInformation object that possibly lives out-of-proc,
// containing the name of the WinRT class we should activate for this
// connection, and a bag of setting to use to initialize that object.
// Return Value:
// - <none>
TerminalConnection::ITerminalConnection ConnectionInformation::CreateConnection(TerminalConnection::ConnectionInformation info)
try
{
Windows::Foundation::IInspectable inspectable{};
const auto name = static_cast<HSTRING>(winrt::get_abi(info.ClassName()));
const auto pointer = winrt::put_abi(inspectable);
#pragma warning(push)
#pragma warning(disable : 26490)
// C++/WinRT just loves it's void**, nothing we can do here _except_ reinterpret_cast
::IInspectable** raw = reinterpret_cast<::IInspectable**>(pointer);
#pragma warning(pop)
// RoActivateInstance() will try to create an instance of the object,
// who's fully qualified name is the string in Name().
//
// The class has to be activatable. For the Terminal, this is easy
// enough - we're not hosting anything that's not already in our
// manifest, or living as a .dll & .winmd SxS.
//
// When we get to extensions (GH#4000), we may want to revisit.
if (LOG_IF_FAILED(RoActivateInstance(name, raw)))
{
return nullptr;
}
// Now that thing we made, make sure it's actually a ITerminalConnection
if (const auto connection{ inspectable.try_as<TerminalConnection::ITerminalConnection>() })
{
// Initialize it, and return it.
connection.Initialize(info.Settings());
return connection;
}
return nullptr;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return nullptr;
}
}

View file

@ -0,0 +1,43 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Class Name:
- ConnectionInformation.h
Abstract:
- This is a helper object for storing both the name of a type of connection, and
a bag of settings to use to initialize that connection.
- This helper is used primarily in cross-proc scenarios, to allow the window
process to tell the content process the name of the connection type it wants
created, and how to set that connection up. This is done so the connection can
live entirely in the content process, without having to go through the window
process at all.
--*/
#pragma once
#include "../inc/cppwinrt_utils.h"
#include "ConnectionInformation.g.h"
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
struct ConnectionInformation : ConnectionInformationT<ConnectionInformation>
{
ConnectionInformation(hstring const& className,
const Windows::Foundation::Collections::ValueSet& settings);
static TerminalConnection::ITerminalConnection CreateConnection(TerminalConnection::ConnectionInformation info);
winrt::hstring ClassName() const { return _ClassName; }
void ClassName(const winrt::hstring& value) { _ClassName = value; }
WINRT_PROPERTY(Windows::Foundation::Collections::ValueSet, Settings);
private:
winrt::hstring _ClassName{};
};
}
namespace winrt::Microsoft::Terminal::TerminalConnection::factory_implementation
{
BASIC_FACTORY(ConnectionInformation);
}

View file

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "ITerminalConnection.idl";
namespace Microsoft.Terminal.TerminalConnection
{
[default_interface] runtimeclass ConnectionInformation
{
ConnectionInformation(String className, Windows.Foundation.Collections.ValueSet settings);
String ClassName { get; };
Windows.Foundation.Collections.ValueSet Settings { get; };
static ITerminalConnection CreateConnection(ConnectionInformation info);
}
}

View file

@ -57,6 +57,72 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
return S_OK; return S_OK;
} }
// Function Description:
// - Promotes a starting directory provided to a WSL invocation to a commandline argument.
// This is necessary because WSL has some modicum of support for linux-side directories (!) which
// CreateProcess never will.
static std::tuple<std::wstring, std::wstring> _tryMangleStartingDirectoryForWSL(std::wstring_view commandLine, std::wstring_view startingDirectory)
{
do
{
if (startingDirectory.size() > 0 && commandLine.size() >= 3)
{ // "wsl" is three characters; this is a safe bet. no point in doing it if there's no starting directory though!
// Find the first space, quote or the end of the string -- we'll look for wsl before that.
const auto terminator{ commandLine.find_first_of(LR"(" )", 1) }; // look past the first character in case it starts with "
const auto start{ til::at(commandLine, 0) == L'"' ? 1 : 0 };
const std::filesystem::path executablePath{ commandLine.substr(start, terminator - start) };
const auto executableFilename{ executablePath.filename().wstring() };
if (executableFilename == L"wsl" || executableFilename == L"wsl.exe")
{
// We've got a WSL -- let's just make sure it's the right one.
if (executablePath.has_parent_path())
{
std::wstring systemDirectory{};
if (FAILED(wil::GetSystemDirectoryW(systemDirectory)))
{
break; // just bail out.
}
if (executablePath.parent_path().wstring() != systemDirectory)
{
break; // it wasn't in system32!
}
}
else
{
// assume that unqualified WSL is the one in system32 (minor danger)
}
const auto arguments{ terminator == std::wstring_view::npos ? std::wstring_view{} : commandLine.substr(terminator + 1) };
if (arguments.find(L"--cd") != std::wstring_view::npos)
{
break; // they've already got a --cd!
}
const auto tilde{ arguments.find_first_of(L'~') };
if (tilde != std::wstring_view::npos)
{
if (tilde + 1 == arguments.size() || til::at(arguments, tilde + 1) == L' ')
{
// We want to suppress --cd if they have added a bare ~ to their commandline (they conflict).
break;
}
// Tilde followed by non-space should be okay (like, wsl -d Debian ~/blah.sh)
}
return {
fmt::format(LR"("{}" --cd "{}" {})", executablePath.wstring(), startingDirectory, arguments),
std::wstring{}
};
}
}
} while (false);
return {
std::wstring{ commandLine },
std::wstring{ startingDirectory }
};
}
// Function Description: // Function Description:
// - launches the client application attached to the new pseudoconsole // - launches the client application attached to the new pseudoconsole
HRESULT ConptyConnection::_LaunchAttachedClient() noexcept HRESULT ConptyConnection::_LaunchAttachedClient() noexcept
@ -116,17 +182,23 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
// add additional WT env vars like WT_SETTINGS, WT_DEFAULTS and WT_PROFILE_ID // add additional WT env vars like WT_SETTINGS, WT_DEFAULTS and WT_PROFILE_ID
for (auto item : _environment) for (auto item : _environment)
{ {
auto key = item.Key(); try
auto value = item.Value();
// avoid clobbering WSLENV
if (std::wstring_view{ key } == L"WSLENV")
{ {
auto current = environment[L"WSLENV"]; auto key = item.Key();
value = current + L":" + value; // This will throw if the value isn't a string. If that
} // happens, then just skip this entry.
auto value = winrt::unbox_value<hstring>(item.Value());
environment.insert_or_assign(key.c_str(), value.c_str()); // avoid clobbering WSLENV
if (std::wstring_view{ key } == L"WSLENV")
{
auto current = environment[L"WSLENV"];
value = current + L":" + value;
}
environment.insert_or_assign(key.c_str(), value.c_str());
}
CATCH_LOG();
} }
} }
@ -157,11 +229,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
siEx.StartupInfo.lpTitle = mutableTitle.data(); siEx.StartupInfo.lpTitle = mutableTitle.data();
} }
const wchar_t* const startingDirectory = _startingDirectory.size() > 0 ? _startingDirectory.c_str() : nullptr; auto [newCommandLine, newStartingDirectory] = _tryMangleStartingDirectoryForWSL(cmdline, _startingDirectory);
const wchar_t* const startingDirectory = newStartingDirectory.size() > 0 ? newStartingDirectory.c_str() : nullptr;
RETURN_IF_WIN32_BOOL_FALSE(CreateProcessW( RETURN_IF_WIN32_BOOL_FALSE(CreateProcessW(
nullptr, nullptr,
cmdline.data(), newCommandLine.data(),
nullptr, // lpProcessAttributes nullptr, // lpProcessAttributes
nullptr, // lpThreadAttributes nullptr, // lpThreadAttributes
false, // bInheritHandles false, // bInheritHandles
@ -219,24 +292,54 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
_piClient.hProcess = hClientProcess; _piClient.hProcess = hClientProcess;
} }
ConptyConnection::ConptyConnection(const hstring& commandline, // Function Description:
const hstring& startingDirectory, // - Helper function for constructing a ValueSet that we can use to get our settings from.
const hstring& startingTitle, Windows::Foundation::Collections::ValueSet ConptyConnection::CreateSettings(const winrt::hstring& cmdline,
const Windows::Foundation::Collections::IMapView<hstring, hstring>& environment, const winrt::hstring& startingDirectory,
const uint32_t initialRows, const winrt::hstring& startingTitle,
const uint32_t initialCols, Windows::Foundation::Collections::IMapView<hstring, hstring> const& environment,
const guid& initialGuid) : uint32_t rows,
_initialRows{ initialRows }, uint32_t columns,
_initialCols{ initialCols }, winrt::guid const& guid)
_commandline{ commandline },
_startingDirectory{ startingDirectory },
_startingTitle{ startingTitle },
_environment{ environment },
_guid{ initialGuid },
_u8State{},
_u16Str{},
_buffer{}
{ {
Windows::Foundation::Collections::ValueSet vs{};
vs.Insert(L"commandline", Windows::Foundation::PropertyValue::CreateString(cmdline));
vs.Insert(L"startingDirectory", Windows::Foundation::PropertyValue::CreateString(startingDirectory));
vs.Insert(L"startingTitle", Windows::Foundation::PropertyValue::CreateString(startingTitle));
vs.Insert(L"initialRows", Windows::Foundation::PropertyValue::CreateUInt32(rows));
vs.Insert(L"initialCols", Windows::Foundation::PropertyValue::CreateUInt32(columns));
vs.Insert(L"guid", Windows::Foundation::PropertyValue::CreateGuid(guid));
if (environment)
{
Windows::Foundation::Collections::ValueSet env{};
for (const auto& [k, v] : environment)
{
env.Insert(k, Windows::Foundation::PropertyValue::CreateString(v));
}
vs.Insert(L"environment", env);
}
return vs;
}
void ConptyConnection::Initialize(const Windows::Foundation::Collections::ValueSet& settings)
{
if (settings)
{
// For the record, the following won't crash:
// auto bad = unbox_value_or<hstring>(settings.TryLookup(L"foo").try_as<IPropertyValue>(), nullptr);
// It'll just return null
_commandline = winrt::unbox_value_or<winrt::hstring>(settings.TryLookup(L"commandline").try_as<Windows::Foundation::IPropertyValue>(), _commandline);
_startingDirectory = winrt::unbox_value_or<winrt::hstring>(settings.TryLookup(L"startingDirectory").try_as<Windows::Foundation::IPropertyValue>(), _startingDirectory);
_startingTitle = winrt::unbox_value_or<winrt::hstring>(settings.TryLookup(L"startingTitle").try_as<Windows::Foundation::IPropertyValue>(), _startingTitle);
_initialRows = winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialRows").try_as<Windows::Foundation::IPropertyValue>(), _initialRows);
_initialCols = winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialCols").try_as<Windows::Foundation::IPropertyValue>(), _initialCols);
_guid = winrt::unbox_value_or<winrt::guid>(settings.TryLookup(L"guid").try_as<Windows::Foundation::IPropertyValue>(), _guid);
_environment = settings.TryLookup(L"environment").try_as<Windows::Foundation::Collections::ValueSet>();
}
if (_guid == guid{}) if (_guid == guid{})
{ {
_guid = Utils::CreateGuid(); _guid = Utils::CreateGuid();
@ -253,12 +356,21 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{ {
_transitionToState(ConnectionState::Connecting); _transitionToState(ConnectionState::Connecting);
const COORD dimensions{ gsl::narrow_cast<SHORT>(_initialCols), gsl::narrow_cast<SHORT>(_initialRows) };
// If we do not have pipes already, then this is a fresh connection... not an inbound one that is a received
// handoff from an already-started PTY process.
if (!_inPipe) if (!_inPipe)
{ {
const COORD dimensions{ gsl::narrow_cast<SHORT>(_initialCols), gsl::narrow_cast<SHORT>(_initialRows) };
THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(dimensions, PSEUDOCONSOLE_RESIZE_QUIRK | PSEUDOCONSOLE_WIN32_INPUT_MODE, &_inPipe, &_outPipe, &_hPC)); THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(dimensions, PSEUDOCONSOLE_RESIZE_QUIRK | PSEUDOCONSOLE_WIN32_INPUT_MODE, &_inPipe, &_outPipe, &_hPC));
THROW_IF_FAILED(_LaunchAttachedClient()); THROW_IF_FAILED(_LaunchAttachedClient());
} }
// But if it was an inbound handoff... attempt to synchronize the size of it with what our connection
// window is expecting it to be on the first layout.
else
{
THROW_IF_FAILED(ConptyResizePseudoConsole(_hPC.get(), dimensions));
}
_startTime = std::chrono::high_resolution_clock::now(); _startTime = std::chrono::high_resolution_clock::now();
@ -387,11 +499,14 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void ConptyConnection::Resize(uint32_t rows, uint32_t columns) void ConptyConnection::Resize(uint32_t rows, uint32_t columns)
{ {
if (!_hPC) // If we haven't started connecting at all, it's still fair to update
// the initial rows and columns before we set things up.
if (!_isStateAtOrBeyond(ConnectionState::Connecting))
{ {
_initialRows = rows; _initialRows = rows;
_initialCols = columns; _initialCols = columns;
} }
// Otherwise, we can really only dispatch a resize if we're already connected.
else if (_isConnected()) else if (_isConnected())
{ {
THROW_IF_FAILED(ConptyResizePseudoConsole(_hPC.get(), { Utils::ClampToShortMax(columns, 1), Utils::ClampToShortMax(rows, 1) })); THROW_IF_FAILED(ConptyResizePseudoConsole(_hPC.get(), { Utils::ClampToShortMax(columns, 1), Utils::ClampToShortMax(rows, 1) }));

View file

@ -26,14 +26,9 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
const HANDLE hServerProcess, const HANDLE hServerProcess,
const HANDLE hClientProcess); const HANDLE hClientProcess);
ConptyConnection( ConptyConnection() noexcept = default;
const hstring& cmdline, void Initialize(const Windows::Foundation::Collections::ValueSet& settings);
const hstring& startingDirectory,
const hstring& startingTitle,
const Windows::Foundation::Collections::IMapView<hstring, hstring>& environment,
const uint32_t rows,
const uint32_t cols,
const guid& guid);
static winrt::fire_and_forget final_release(std::unique_ptr<ConptyConnection> connection); static winrt::fire_and_forget final_release(std::unique_ptr<ConptyConnection> connection);
void Start(); void Start();
@ -49,6 +44,14 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
static winrt::event_token NewConnection(NewConnectionHandler const& handler); static winrt::event_token NewConnection(NewConnectionHandler const& handler);
static void NewConnection(winrt::event_token const& token); static void NewConnection(winrt::event_token const& token);
static Windows::Foundation::Collections::ValueSet CreateSettings(const winrt::hstring& cmdline,
const winrt::hstring& startingDirectory,
const winrt::hstring& startingTitle,
Windows::Foundation::Collections::IMapView<hstring, hstring> const& environment,
uint32_t rows,
uint32_t columns,
winrt::guid const& guid);
WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler); WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);
private: private:
@ -60,10 +63,10 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
uint32_t _initialRows{}; uint32_t _initialRows{};
uint32_t _initialCols{}; uint32_t _initialCols{};
hstring _commandline; hstring _commandline{};
hstring _startingDirectory; hstring _startingDirectory{};
hstring _startingTitle; hstring _startingTitle{};
Windows::Foundation::Collections::IMapView<hstring, hstring> _environment; Windows::Foundation::Collections::ValueSet _environment{ nullptr };
guid _guid{}; // A unique session identifier for connected client guid _guid{}; // A unique session identifier for connected client
hstring _clientName{}; // The name of the process hosted by this ConPTY connection (as of launch). hstring _clientName{}; // The name of the process hosted by this ConPTY connection (as of launch).
@ -77,9 +80,9 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
wil::unique_static_pseudoconsole_handle _hPC; wil::unique_static_pseudoconsole_handle _hPC;
wil::unique_threadpool_wait _clientExitWait; wil::unique_threadpool_wait _clientExitWait;
til::u8state _u8State; til::u8state _u8State{};
std::wstring _u16Str; std::wstring _u16Str{};
std::array<char, 4096> _buffer; std::array<char, 4096> _buffer{};
DWORD _OutputThread(); DWORD _OutputThread();
}; };

Some files were not shown because too many files have changed in this diff Show more