mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-15 10:41:02 +00:00
Compare commits
63 Commits
dev/migrie
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2b73d30da | ||
|
|
c62af79122 | ||
|
|
9c5d9971ff | ||
|
|
c6626bd4e8 | ||
|
|
c6621c8e3e | ||
|
|
103ec7ccea | ||
|
|
a15a35ec86 | ||
|
|
830e0be47a | ||
|
|
99ea9d74d8 | ||
|
|
7242613fde | ||
|
|
26e3703227 | ||
|
|
85371a10fe | ||
|
|
0521a97602 | ||
|
|
38a7c40ae3 | ||
|
|
f2222ecbef | ||
|
|
a5ebbe0c43 | ||
|
|
2bd376832f | ||
|
|
7b775684e8 | ||
|
|
2c32f5a0fb | ||
|
|
f78008663a | ||
|
|
f94b98b488 | ||
|
|
f59bde9343 | ||
|
|
fc2751e654 | ||
|
|
928c87509d | ||
|
|
f7e3d889ff | ||
|
|
874819dceb | ||
|
|
39d16ba94c | ||
|
|
603122f247 | ||
|
|
b1a21533fe | ||
|
|
b19f449895 | ||
|
|
f218b0dc76 | ||
|
|
f90572d5a1 | ||
|
|
616a71dd23 | ||
|
|
f012cb92fc | ||
|
|
a43cf4ac2a | ||
|
|
b310307128 | ||
|
|
4fa0dc7ffa | ||
|
|
e551184666 | ||
|
|
6363dbae69 | ||
|
|
7965b39d7b | ||
|
|
120bb8ef5e | ||
|
|
c5acc3585c | ||
|
|
b0cb46f68b | ||
|
|
0480597abb | ||
|
|
d9ff7d610d | ||
|
|
3052ff77e2 | ||
|
|
694c73013f | ||
|
|
b8b137f0c7 | ||
|
|
92437d718f | ||
|
|
817f598e20 | ||
|
|
2c5a35f1be | ||
|
|
ea58e4036b | ||
|
|
ee8800c739 | ||
|
|
f7b0f7444a | ||
|
|
1b6e6bd6dd | ||
|
|
7b6df26411 | ||
|
|
f3cc4c0328 | ||
|
|
f3a49fafe3 | ||
|
|
15c02b77a0 | ||
|
|
2c3368f766 | ||
|
|
b1131263cf | ||
|
|
c53fe1c2bf | ||
|
|
f9a844dbda |
@@ -21,7 +21,7 @@ Write-Host "Checking test results..."
|
||||
$queryUri = GetQueryTestRunsUri -CollectionUri $CollectionUri -TeamProject $TeamProject -BuildUri $BuildUri -IncludeRunDetails
|
||||
Write-Host "queryUri = $queryUri"
|
||||
|
||||
$testRuns = Invoke-RestMethod -Uri $queryUri -Method Get -Headers $azureDevOpsRestApiHeaders
|
||||
$testRuns = Invoke-RestMethodWithRetries $queryUri -Headers $azureDevOpsRestApiHeaders
|
||||
[System.Collections.Generic.List[string]]$failingTests = @()
|
||||
[System.Collections.Generic.List[string]]$unreliableTests = @()
|
||||
[System.Collections.Generic.List[string]]$unexpectedResultTest = @()
|
||||
@@ -50,7 +50,7 @@ foreach ($testRun in ($testRuns.value | Sort-Object -Property "completedDate" -D
|
||||
$totalTestsExecutedCount += $testRun.totalTests
|
||||
|
||||
$testRunResultsUri = "$($testRun.url)/results?api-version=5.0"
|
||||
$testResults = Invoke-RestMethod -Uri "$($testRun.url)/results?api-version=5.0" -Method Get -Headers $azureDevOpsRestApiHeaders
|
||||
$testResults = Invoke-RestMethodWithRetries "$($testRun.url)/results?api-version=5.0" -Headers $azureDevOpsRestApiHeaders
|
||||
|
||||
foreach ($testResult in $testResults.value)
|
||||
{
|
||||
|
||||
@@ -20,13 +20,31 @@ function Generate-File-Links
|
||||
Out-File -FilePath $helixLinkFile -Append -InputObject "<ul>"
|
||||
foreach($file in $files)
|
||||
{
|
||||
Out-File -FilePath $helixLinkFile -Append -InputObject "<li><a href=$($file.Link)>$($file.Name)</a></li>"
|
||||
$url = Append-HelixAccessTokenToUrl $file.Link "{Your-Helix-Access-Token-Here}"
|
||||
Out-File -FilePath $helixLinkFile -Append -InputObject "<li>$($url)</li>"
|
||||
}
|
||||
Out-File -FilePath $helixLinkFile -Append -InputObject "</ul>"
|
||||
Out-File -FilePath $helixLinkFile -Append -InputObject "</div>"
|
||||
}
|
||||
}
|
||||
|
||||
function Append-HelixAccessTokenToUrl
|
||||
{
|
||||
Param ([string]$url, [string]$token)
|
||||
if($token)
|
||||
{
|
||||
if($url.Contains("?"))
|
||||
{
|
||||
$url = "$($url)&access_token=$($token)"
|
||||
}
|
||||
else
|
||||
{
|
||||
$url = "$($url)?access_token=$($token)"
|
||||
}
|
||||
}
|
||||
return $url
|
||||
}
|
||||
|
||||
#Create output directory
|
||||
New-Item $OutputFolder -ItemType Directory
|
||||
|
||||
@@ -63,7 +81,8 @@ foreach ($testRun in $testRuns.value)
|
||||
if (-not $workItems.Contains($workItem))
|
||||
{
|
||||
$workItems.Add($workItem)
|
||||
$filesQueryUri = "https://helix.dot.net/api/2019-06-17/jobs/$helixJobId/workitems/$helixWorkItemName/files$accessTokenParam"
|
||||
$filesQueryUri = "https://helix.dot.net/api/2019-06-17/jobs/$helixJobId/workitems/$helixWorkItemName/files"
|
||||
$filesQueryUri = Append-HelixAccessTokenToUrl $filesQueryUri $helixAccessToken
|
||||
$files = Invoke-RestMethodWithRetries $filesQueryUri
|
||||
|
||||
$screenShots = $files | where { $_.Name.EndsWith(".jpg") }
|
||||
@@ -102,6 +121,7 @@ foreach ($testRun in $testRuns.value)
|
||||
|
||||
Write-Host "Downloading $link to $destination"
|
||||
|
||||
$link = Append-HelixAccessTokenToUrl $link $HelixAccessToken
|
||||
Download-FileWithRetries $link $destination
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ Write-Host "queryUri = $queryUri"
|
||||
# To account for unreliable tests, we'll iterate through all of the tests associated with this build, check to see any tests that were unreliable
|
||||
# (denoted by being marked as "skipped"), and if so, we'll instead mark those tests with a warning and enumerate all of the attempted runs
|
||||
# with their pass/fail states as well as any relevant error messages for failed attempts.
|
||||
$testRuns = Invoke-RestMethod -Uri $queryUri -Method Get -Headers $azureDevOpsRestApiHeaders
|
||||
$testRuns = Invoke-RestMethodWithRetries $queryUri -Headers $azureDevOpsRestApiHeaders
|
||||
|
||||
$timesSeenByRunName = @{}
|
||||
|
||||
@@ -32,10 +32,10 @@ foreach ($testRun in $testRuns.value)
|
||||
$testRunResultsUri = "$($testRun.url)/results?api-version=5.0"
|
||||
|
||||
Write-Host "Marking test run `"$($testRun.name)`" as in progress so we can change its results to account for unreliable tests."
|
||||
Invoke-RestMethod -Uri "$($testRun.url)?api-version=5.0" -Method Patch -Body (ConvertTo-Json @{ "state" = "InProgress" }) -Headers $azureDevOpsRestApiHeaders -ContentType "application/json" | Out-Null
|
||||
Invoke-RestMethod "$($testRun.url)?api-version=5.0" -Method Patch -Body (ConvertTo-Json @{ "state" = "InProgress" }) -Headers $azureDevOpsRestApiHeaders -ContentType "application/json" | Out-Null
|
||||
|
||||
Write-Host "Retrieving test results..."
|
||||
$testResults = Invoke-RestMethod -Uri $testRunResultsUri -Method Get -Headers $azureDevOpsRestApiHeaders
|
||||
$testResults = Invoke-RestMethodWithRetries $testRunResultsUri -Headers $azureDevOpsRestApiHeaders
|
||||
|
||||
foreach ($testResult in $testResults.value)
|
||||
{
|
||||
@@ -54,7 +54,8 @@ foreach ($testRun in $testRuns.value)
|
||||
Write-Host " Test $($testResult.testCaseTitle) was detected as unreliable. Updating..."
|
||||
|
||||
# The errorMessage field contains a link to the JSON-encoded rerun result data.
|
||||
$rerunResults = ConvertFrom-Json (New-Object System.Net.WebClient).DownloadString($testResult.errorMessage)
|
||||
$resultsJson = Download-StringWithRetries "Error results" $testResult.errorMessage
|
||||
$rerunResults = ConvertFrom-Json $resultsJson
|
||||
[System.Collections.Generic.List[System.Collections.Hashtable]]$rerunDataList = @()
|
||||
$attemptCount = 0
|
||||
$passCount = 0
|
||||
|
||||
@@ -96,6 +96,8 @@ jobs:
|
||||
selectOrConfig: config
|
||||
nugetConfigPath: NuGet.Config
|
||||
arguments: restore OpenConsole.sln -SolutionDirectory $(Build.SourcesDirectory)
|
||||
# Pull the Windows SDK for the developer tools like the debuggers so we can index sources later
|
||||
- template: .\templates\install-winsdk-steps.yml
|
||||
- task: UniversalPackages@0
|
||||
displayName: Download terminal-internal Universal Package
|
||||
inputs:
|
||||
@@ -152,7 +154,7 @@ jobs:
|
||||
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
|
||||
msbuildArgs: /p:WindowsTerminalOfficialBuild=true /p:WindowsTerminalBranding=${{ parameters.branding }};PGOBuildMode=${{ parameters.pgoBuildMode }} /t:Terminal\CascadiaPackage;Terminal\WindowsTerminalUniversal /p:WindowsTerminalReleaseBuild=true /bl:$(Build.SourcesDirectory)\msbuild.binlog
|
||||
platform: $(BuildPlatform)
|
||||
configuration: $(BuildConfiguration)
|
||||
clean: true
|
||||
@@ -194,7 +196,7 @@ jobs:
|
||||
inputs:
|
||||
solution: '**\OpenConsole.sln'
|
||||
vsVersion: 16.0
|
||||
msbuildArgs: /p:WindowsTerminalOfficialBuild=true /p:WindowsTerminalBranding=${{ parameters.branding }} /p:WindowsTerminalReleaseBuild=true /t:Terminal\wpf\PublicTerminalCore
|
||||
msbuildArgs: /p:WindowsTerminalOfficialBuild=true /p:WindowsTerminalBranding=${{ parameters.branding }};PGOBuildMode=${{ parameters.pgoBuildMode }} /p:WindowsTerminalReleaseBuild=true /t:Terminal\wpf\PublicTerminalCore
|
||||
platform: $(BuildPlatform)
|
||||
configuration: $(BuildConfiguration)
|
||||
- task: PowerShell@2
|
||||
@@ -261,7 +263,10 @@ jobs:
|
||||
displayName: Publish symbols path
|
||||
continueOnError: True
|
||||
inputs:
|
||||
SearchPattern: '**/*.pdb'
|
||||
SearchPattern: |
|
||||
$(Build.SourcesDirectory)/bin/**/*.pdb
|
||||
$(Build.SourcesDirectory)/bin/**/*.exe
|
||||
$(Build.SourcesDirectory)/bin/**/*.dll
|
||||
IndexSources: false
|
||||
SymbolServerType: TeamServices
|
||||
|
||||
@@ -402,7 +407,10 @@ jobs:
|
||||
displayName: Publish symbols path
|
||||
continueOnError: True
|
||||
inputs:
|
||||
SearchPattern: '**/*.pdb'
|
||||
SearchPattern: |
|
||||
$(Build.SourcesDirectory)/bin/**/*.pdb
|
||||
$(Build.SourcesDirectory)/bin/**/*.exe
|
||||
$(Build.SourcesDirectory)/bin/**/*.dll
|
||||
IndexSources: false
|
||||
SymbolServerType: TeamServices
|
||||
SymbolsArtifactName: Symbols_WPF_$(BuildConfiguration)
|
||||
|
||||
@@ -3,7 +3,7 @@ parameters:
|
||||
platform: ''
|
||||
additionalBuildArguments: ''
|
||||
minimumExpectedTestsExecutedCount: 1 # Sanity check for minimum expected tests to be reported
|
||||
rerunPassesRequiredToAvoidFailure: 0
|
||||
rerunPassesRequiredToAvoidFailure: 5
|
||||
|
||||
jobs:
|
||||
- job: Build${{ parameters.platform }}${{ parameters.configuration }}
|
||||
|
||||
@@ -22,6 +22,7 @@ jobs:
|
||||
condition: succeededOrFailed()
|
||||
env:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
HelixAccessToken: $(HelixApiAccessToken)
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: build\Helix\UpdateUnreliableTests.ps1
|
||||
@@ -32,6 +33,7 @@ jobs:
|
||||
condition: succeededOrFailed()
|
||||
env:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
HelixAccessToken: $(HelixApiAccessToken)
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: build\Helix\OutputTestResults.ps1
|
||||
|
||||
@@ -15,6 +15,7 @@ parameters:
|
||||
# if 'useBuildOutputFromBuildId' is set, we will default to using a build from this pipeline:
|
||||
useBuildOutputFromPipeline: $(System.DefinitionId)
|
||||
openHelixTargetQueues: 'windows.10.amd64.client19h1.open.xaml'
|
||||
closedHelixTargetQueues: 'windows.10.amd64.client19h1.xaml'
|
||||
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
@@ -29,11 +30,11 @@ jobs:
|
||||
buildConfiguration: ${{ parameters.configuration }}
|
||||
buildPlatform: ${{ parameters.platform }}
|
||||
openHelixTargetQueues: ${{ parameters.openHelixTargetQueues }}
|
||||
closedHelixTargetQueues: ${{ parameters.closedHelixTargetQueues }}
|
||||
artifactsDir: $(Build.SourcesDirectory)\Artifacts
|
||||
taefPath: $(Build.SourcesDirectory)\build\Helix\packages\Microsoft.Taef.10.60.210621002\build\Binaries\$(buildPlatform)
|
||||
helixCommonArgs: '/binaryLogger:$(Build.SourcesDirectory)/${{parameters.name}}.$(buildPlatform).$(buildConfiguration).binlog /p:HelixBuild=$(Build.BuildId).$(buildPlatform).$(buildConfiguration) /p:Platform=$(buildPlatform) /p:Configuration=$(buildConfiguration) /p:HelixType=${{parameters.helixType}} /p:TestSuite=${{parameters.testSuite}} /p:ProjFilesPath=$(Build.ArtifactStagingDirectory) /p:rerunPassesRequiredToAvoidFailure=${{parameters.rerunPassesRequiredToAvoidFailure}}'
|
||||
|
||||
|
||||
steps:
|
||||
- task: CmdLine@1
|
||||
displayName: 'Display build machine environment variables'
|
||||
@@ -140,6 +141,7 @@ jobs:
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Run tests in Helix (open queues)'
|
||||
condition: and(succeeded(),eq(variables['System.CollectionUri'],'https://dev.azure.com/ms/'))
|
||||
env:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
inputs:
|
||||
@@ -147,3 +149,15 @@ jobs:
|
||||
projects: build\Helix\RunTestsInHelix.proj
|
||||
custom: msbuild
|
||||
arguments: '$(helixCommonArgs) /p:IsExternal=true /p:Creator=Terminal /p:HelixTargetQueues=$(openHelixTargetQueues)'
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Run tests in Helix (closed queues)'
|
||||
condition: and(succeeded(),ne(variables['System.CollectionUri'],'https://dev.azure.com/ms/'))
|
||||
env:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
HelixAccessToken: $(HelixApiAccessToken)
|
||||
inputs:
|
||||
command: custom
|
||||
projects: build\Helix\RunTestsInHelix.proj
|
||||
custom: msbuild
|
||||
arguments: '$(helixCommonArgs) /p:HelixTargetQueues=$(closedHelixTargetQueues)'
|
||||
|
||||
9
build/pipelines/templates/install-winsdk-steps.yml
Normal file
9
build/pipelines/templates/install-winsdk-steps.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
parameters:
|
||||
sdkVersion: 18362
|
||||
steps:
|
||||
- task: powershell@2
|
||||
inputs:
|
||||
targetType: filePath
|
||||
filePath: build\scripts\Install-WindowsSdkISO.ps1
|
||||
arguments: ${{ parameters.sdkVersion }}
|
||||
displayName: 'Install Windows SDK (${{ parameters.sdkVersion }})'
|
||||
@@ -20,11 +20,15 @@ jobs:
|
||||
inputs:
|
||||
artifactName: ${{ parameters.pgoArtifact }}
|
||||
downloadPath: $(artifactsPath)
|
||||
|
||||
- task: NuGetToolInstaller@0
|
||||
displayName: 'Use NuGet 5.2.0'
|
||||
|
||||
- task: NuGetAuthenticate@0
|
||||
inputs:
|
||||
versionSpec: 5.2.0
|
||||
nuGetServiceConnections: 'Terminal Public Artifact Feed'
|
||||
|
||||
- task: NuGetToolInstaller@0
|
||||
displayName: 'Use NuGet 5.8.0'
|
||||
inputs:
|
||||
versionSpec: 5.8.0
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Copy pgd files to NuGet build directory'
|
||||
@@ -58,5 +62,11 @@ jobs:
|
||||
displayName: 'NuGet push'
|
||||
inputs:
|
||||
command: push
|
||||
publishVstsFeed: Terminal/TerminalDependencies
|
||||
packagesToPush: $(Build.ArtifactStagingDirectory)/*.nupkg
|
||||
nuGetFeedType: external
|
||||
packagesToPush: $(Build.ArtifactStagingDirectory)/*.nupkg
|
||||
# The actual URL and PAT for this feed is configured at
|
||||
# https://microsoft.visualstudio.com/Dart/_settings/adminservices
|
||||
# This is the name of that connection
|
||||
publishFeedCredentials: 'Terminal Public Artifact Feed'
|
||||
feedsToUse: config
|
||||
nugetConfigPath: '$(Build.SourcesDirectory)/NuGet.config'
|
||||
@@ -23,6 +23,7 @@ $mappedFiles = New-Object System.Collections.ArrayList
|
||||
|
||||
foreach ($file in (Get-ChildItem -r:$recursive "$SearchDir\*.pdb"))
|
||||
{
|
||||
$mappedFiles = New-Object System.Collections.ArrayList
|
||||
Write-Verbose "Found $file"
|
||||
|
||||
$ErrorActionPreference = "Continue" # Azure Pipelines defaults to "Stop", continue past errors in this script.
|
||||
@@ -50,7 +51,7 @@ foreach ($file in (Get-ChildItem -r:$recursive "$SearchDir\*.pdb"))
|
||||
if ($relative)
|
||||
{
|
||||
$mapping = $allFiles[$i] + "*$relative"
|
||||
$mappedFiles.Add($mapping)
|
||||
$ignore = $mappedFiles.Add($mapping)
|
||||
|
||||
Write-Verbose "Mapped path $($i): $mapping"
|
||||
}
|
||||
@@ -78,7 +79,26 @@ $($mappedFiles -join "`r`n")
|
||||
SRCSRV: end ------------------------------------------------
|
||||
"@ | Set-Content $pdbstrFile
|
||||
|
||||
Write-Host
|
||||
Write-Host
|
||||
Write-Host (Get-Content $pdbstrFile)
|
||||
Write-Host
|
||||
Write-Host
|
||||
|
||||
Write-Host "$pdbstrExe -p:""$file"" -w -s:srcsrv -i:$pdbstrFile"
|
||||
& $pdbstrExe -p:"$file" -w -s:srcsrv -i:$pdbstrFile
|
||||
Write-Host
|
||||
Write-Host
|
||||
|
||||
Write-Host "$pdbstrExe -p:""$file"" -r -s:srcsrv"
|
||||
& $pdbstrExe -p:"$file" -r -s:srcsrv
|
||||
Write-Host
|
||||
Write-Host
|
||||
|
||||
Write-Host "$srctoolExe $file"
|
||||
& $srctoolExe "$file"
|
||||
Write-Host
|
||||
Write-Host
|
||||
}
|
||||
|
||||
# Return with exit 0 to override any weird error code from other tools
|
||||
|
||||
346
build/scripts/Install-WindowsSdkISO.ps1
Normal file
346
build/scripts/Install-WindowsSdkISO.ps1
Normal file
@@ -0,0 +1,346 @@
|
||||
[CmdletBinding()]
|
||||
param([Parameter(Mandatory=$true, Position=0)]
|
||||
[string]$buildNumber)
|
||||
|
||||
# Ensure the error action preference is set to the default for PowerShell3, 'Stop'
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Constants
|
||||
$WindowsSDKOptions = @("OptionId.UWPCpp", "OptionId.DesktopCPPx64", "OptionId.DesktopCPPx86", "OptionId.DesktopCPPARM64", "OptionId.DesktopCPPARM", "OptionId.WindowsDesktopDebuggers")
|
||||
$WindowsSDKRegPath = "HKLM:\Software\WOW6432Node\Microsoft\Windows Kits\Installed Roots"
|
||||
$WindowsSDKRegRootKey = "KitsRoot10"
|
||||
$WindowsSDKVersion = "10.0.$buildNumber.0"
|
||||
$WindowsSDKInstalledRegPath = "$WindowsSDKRegPath\$WindowsSDKVersion\Installed Options"
|
||||
$StrongNameRegPath = "HKLM:\SOFTWARE\Microsoft\StrongName\Verification"
|
||||
$PublicKeyTokens = @("31bf3856ad364e35")
|
||||
|
||||
if ($buildNumber -notmatch "^\d{5,}$")
|
||||
{
|
||||
Write-Host "ERROR: '$buildNumber' doesn't look like a windows build number"
|
||||
Write-Host
|
||||
Exit 1
|
||||
}
|
||||
|
||||
function Download-File
|
||||
{
|
||||
param ([string] $outDir,
|
||||
[string] $downloadUrl,
|
||||
[string] $downloadName)
|
||||
|
||||
$downloadPath = Join-Path $outDir "$downloadName.download"
|
||||
$downloadDest = Join-Path $outDir $downloadName
|
||||
$downloadDestTemp = Join-Path $outDir "$downloadName.tmp"
|
||||
|
||||
Write-Host -NoNewline "Downloading $downloadName..."
|
||||
|
||||
$retries = 10
|
||||
$downloaded = $false
|
||||
while (-not $downloaded)
|
||||
{
|
||||
try
|
||||
{
|
||||
$webclient = new-object System.Net.WebClient
|
||||
$webclient.DownloadFile($downloadUrl, $downloadPath)
|
||||
$downloaded = $true
|
||||
}
|
||||
catch [System.Net.WebException]
|
||||
{
|
||||
Write-Host
|
||||
Write-Warning "Failed to fetch updated file from $downloadUrl : $($error[0])"
|
||||
if (!(Test-Path $downloadDest))
|
||||
{
|
||||
if ($retries -gt 0)
|
||||
{
|
||||
Write-Host "$retries retries left, trying download again"
|
||||
$retries--
|
||||
start-sleep -Seconds 10
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "$downloadName was not found at $downloadDest"
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Warning "$downloadName may be out of date"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Unblock-File $downloadPath
|
||||
|
||||
$downloadDestTemp = $downloadPath;
|
||||
|
||||
# Delete and rename to final dest
|
||||
Write-Host "testing $downloadDest"
|
||||
if (Test-Path $downloadDest)
|
||||
{
|
||||
Write-Host "Deleting: $downloadDest"
|
||||
Remove-Item $downloadDest -Force
|
||||
}
|
||||
|
||||
Move-Item -Force $downloadDestTemp $downloadDest
|
||||
Write-Host "Done"
|
||||
|
||||
return $downloadDest
|
||||
}
|
||||
|
||||
function Get-ISODriveLetter
|
||||
{
|
||||
param ([string] $isoPath)
|
||||
|
||||
$diskImage = Get-DiskImage -ImagePath $isoPath
|
||||
if ($diskImage)
|
||||
{
|
||||
$volume = Get-Volume -DiskImage $diskImage
|
||||
|
||||
if ($volume)
|
||||
{
|
||||
$driveLetter = $volume.DriveLetter
|
||||
if ($driveLetter)
|
||||
{
|
||||
$driveLetter += ":"
|
||||
return $driveLetter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Mount-ISO
|
||||
{
|
||||
param ([string] $isoPath)
|
||||
|
||||
# Check if image is already mounted
|
||||
$isoDrive = Get-ISODriveLetter $isoPath
|
||||
|
||||
if (!$isoDrive)
|
||||
{
|
||||
Mount-DiskImage -ImagePath $isoPath -StorageType ISO | Out-Null
|
||||
}
|
||||
|
||||
$isoDrive = Get-ISODriveLetter $isoPath
|
||||
Write-Verbose "$isoPath mounted to ${isoDrive}:"
|
||||
}
|
||||
|
||||
function Dismount-ISO
|
||||
{
|
||||
param ([string] $isoPath)
|
||||
|
||||
$isoDrive = (Get-DiskImage -ImagePath $isoPath | Get-Volume).DriveLetter
|
||||
|
||||
if ($isoDrive)
|
||||
{
|
||||
Write-Verbose "$isoPath dismounted"
|
||||
Dismount-DiskImage -ImagePath $isoPath | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
function Disable-StrongName
|
||||
{
|
||||
param ([string] $publicKeyToken = "*")
|
||||
|
||||
reg ADD "HKLM\SOFTWARE\Microsoft\StrongName\Verification\*,$publicKeyToken" /f | Out-Null
|
||||
if ($env:PROCESSOR_ARCHITECTURE -eq "AMD64")
|
||||
{
|
||||
reg ADD "HKLM\SOFTWARE\Wow6432Node\Microsoft\StrongName\Verification\*,$publicKeyToken" /f | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
function Test-Admin
|
||||
{
|
||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$principal = New-Object Security.Principal.WindowsPrincipal $identity
|
||||
$principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
}
|
||||
|
||||
function Test-RegistryPathAndValue
|
||||
{
|
||||
param (
|
||||
[parameter(Mandatory=$true)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string] $path,
|
||||
[parameter(Mandatory=$true)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string] $value)
|
||||
|
||||
try
|
||||
{
|
||||
if (Test-Path $path)
|
||||
{
|
||||
Get-ItemProperty -Path $path | Select-Object -ExpandProperty $value -ErrorAction Stop | Out-Null
|
||||
return $true
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
function Test-InstallWindowsSDK
|
||||
{
|
||||
$retval = $true
|
||||
|
||||
if (Test-RegistryPathAndValue -Path $WindowsSDKRegPath -Value $WindowsSDKRegRootKey)
|
||||
{
|
||||
# A Windows SDK is installed
|
||||
# Is an SDK of our version installed with the options we need?
|
||||
$allRequiredSdkOptionsInstalled = $true
|
||||
foreach($sdkOption in $WindowsSDKOptions)
|
||||
{
|
||||
if (!(Test-RegistryPathAndValue -Path $WindowsSDKInstalledRegPath -Value $sdkOption))
|
||||
{
|
||||
$allRequiredSdkOptionsInstalled = $false
|
||||
}
|
||||
}
|
||||
|
||||
if($allRequiredSdkOptionsInstalled)
|
||||
{
|
||||
# It appears we have what we need. Double check the disk
|
||||
$sdkRoot = Get-ItemProperty -Path $WindowsSDKRegPath | Select-Object -ExpandProperty $WindowsSDKRegRootKey
|
||||
if ($sdkRoot)
|
||||
{
|
||||
if (Test-Path $sdkRoot)
|
||||
{
|
||||
$refPath = Join-Path $sdkRoot "References\$WindowsSDKVersion"
|
||||
if (Test-Path $refPath)
|
||||
{
|
||||
$umdPath = Join-Path $sdkRoot "UnionMetadata\$WindowsSDKVersion"
|
||||
if (Test-Path $umdPath)
|
||||
{
|
||||
# Pretty sure we have what we need
|
||||
$retval = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $retval
|
||||
}
|
||||
|
||||
function Test-InstallStrongNameHijack
|
||||
{
|
||||
foreach($publicKeyToken in $PublicKeyTokens)
|
||||
{
|
||||
$key = "$StrongNameRegPath\*,$publicKeyToken"
|
||||
if (!(Test-Path $key))
|
||||
{
|
||||
return $true
|
||||
}
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
Write-Host -NoNewline "Checking for installed Windows SDK $WindowsSDKVersion..."
|
||||
$InstallWindowsSDK = Test-InstallWindowsSDK
|
||||
if ($InstallWindowsSDK)
|
||||
{
|
||||
Write-Host "Installation required"
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host "INSTALLED"
|
||||
}
|
||||
|
||||
$StrongNameHijack = Test-InstallStrongNameHijack
|
||||
Write-Host -NoNewline "Checking if StrongName bypass required..."
|
||||
|
||||
if ($StrongNameHijack)
|
||||
{
|
||||
Write-Host "REQUIRED"
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host "Done"
|
||||
}
|
||||
|
||||
if ($StrongNameHijack -or $InstallWindowsSDK)
|
||||
{
|
||||
if (!(Test-Admin))
|
||||
{
|
||||
Write-Host
|
||||
throw "ERROR: Elevation required"
|
||||
}
|
||||
}
|
||||
|
||||
if ($InstallWindowsSDK)
|
||||
{
|
||||
# Static(ish) link for Windows SDK
|
||||
# Note: there is a delay from Windows SDK announcements to availability via the static link
|
||||
$uri = "https://software-download.microsoft.com/download/sg/Windows_InsiderPreview_SDK_en-us_$($buildNumber)_1.iso";
|
||||
|
||||
if ($env:TEMP -eq $null)
|
||||
{
|
||||
$env:TEMP = Join-Path $env:SystemDrive 'temp'
|
||||
}
|
||||
|
||||
$winsdkTempDir = Join-Path (Join-Path $env:TEMP ([System.IO.Path]::GetRandomFileName())) "WindowsSDK"
|
||||
|
||||
if (![System.IO.Directory]::Exists($winsdkTempDir))
|
||||
{
|
||||
[void][System.IO.Directory]::CreateDirectory($winsdkTempDir)
|
||||
}
|
||||
|
||||
$file = "winsdk_$buildNumber.iso"
|
||||
|
||||
Write-Verbose "Getting WinSDK from $uri"
|
||||
$downloadFile = Download-File $winsdkTempDir $uri $file
|
||||
Write-Verbose "File is at $downloadFile"
|
||||
$downloadFileItem = Get-Item $downloadFile
|
||||
|
||||
# Check to make sure the file is at least 10 MB.
|
||||
if ($downloadFileItem.Length -lt 10*1024*1024)
|
||||
{
|
||||
Write-Host
|
||||
Write-Host "ERROR: Downloaded file doesn't look large enough to be an ISO. The requested version may not be on microsoft.com yet."
|
||||
Write-Host
|
||||
Exit 1
|
||||
}
|
||||
|
||||
# TODO Check if zip, exe, iso, etc.
|
||||
try
|
||||
{
|
||||
Write-Host -NoNewline "Mounting ISO $file..."
|
||||
Mount-ISO $downloadFile
|
||||
Write-Host "Done"
|
||||
|
||||
$isoDrive = Get-ISODriveLetter $downloadFile
|
||||
|
||||
if (Test-Path $isoDrive)
|
||||
{
|
||||
Write-Host -NoNewLine "Installing WinSDK..."
|
||||
|
||||
$setupPath = Join-Path "$isoDrive" "WinSDKSetup.exe"
|
||||
Start-Process -Wait $setupPath "/features $WindowsSDKOptions /q"
|
||||
Write-Host "Done"
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "Could not find mounted ISO at ${isoDrive}"
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Write-Host -NoNewline "Dismounting ISO $file..."
|
||||
Dismount-ISO $downloadFile
|
||||
Write-Host "Done"
|
||||
}
|
||||
}
|
||||
|
||||
if ($StrongNameHijack)
|
||||
{
|
||||
Write-Host -NoNewline "Disabling StrongName for Windows SDK..."
|
||||
|
||||
foreach($key in $PublicKeyTokens)
|
||||
{
|
||||
Disable-StrongName $key
|
||||
}
|
||||
|
||||
Write-Host "Done"
|
||||
}
|
||||
@@ -215,6 +215,22 @@
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
},
|
||||
"features": {
|
||||
"description": "Sets the DWrite font features for the given font. For example, { \"ss01\": 1, \"liga\":0 } will enable ss01 and disable ligatures.",
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^(([A-Za-z0-9]){4})$": { "type": "integer" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"axes": {
|
||||
"description": "Sets the DWrite font axes for the given font. For example, { \"wght\": 200 } will set the font weight to 200.",
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^([A-Za-z]{4})$": { "type": "number" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -299,7 +315,8 @@
|
||||
"down",
|
||||
"previous",
|
||||
"nextInOrder",
|
||||
"previousInOrder"
|
||||
"previousInOrder",
|
||||
"first"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -1215,14 +1232,14 @@
|
||||
"type": [ "integer", "string" ],
|
||||
"deprecated": true
|
||||
},
|
||||
"minimizeToTray": {
|
||||
"minimizeToNotificationArea": {
|
||||
"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.",
|
||||
"description": "When set to true, minimizing a Terminal window will no longer appear in the taskbar. Instead, a Terminal icon will appear in the notification area through which the user can access their windows.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"alwaysShowTrayIcon": {
|
||||
"alwaysShowNotificationIcon": {
|
||||
"default": "false",
|
||||
"description": "When set to true, the Terminal's tray icon will always be shown in the system tray.",
|
||||
"description": "When set to true, the Terminal's notification icon will always be shown in the notification area.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"actions": {
|
||||
|
||||
@@ -0,0 +1,619 @@
|
||||
---
|
||||
author: Mike Griese @zadjii-msft
|
||||
created on: 2020-11-20
|
||||
last updated: 2021-08-17
|
||||
issue id: #1032
|
||||
---
|
||||
|
||||
# Elevation Quality of Life Improvements
|
||||
|
||||
## Abstract
|
||||
|
||||
For a long time, we've been researching adding support to the Windows Terminal
|
||||
for running both unelevated and elevated (admin) tabs side-by-side, in the same
|
||||
window. However, after much research, we've determined that there isn't a safe
|
||||
way to do this without opening the Terminal up as a potential
|
||||
escalation-of-privilege vector.
|
||||
|
||||
Instead, we'll be adding a number of features to the Terminal to improve the
|
||||
user experience of working in elevated scenarios. These improvements include:
|
||||
|
||||
* A visible indicator that the Terminal window is elevated ([#1939])
|
||||
* Configuring the Terminal to always run elevated ([#632])
|
||||
* Configuring a specific profile to always open elevated ([#632])
|
||||
* Allowing new tabs, panes to be opened elevated directly from an unelevated
|
||||
window
|
||||
* Dynamic profile appearance that changes depending on if the Terminal is
|
||||
elevated or not. ([#1939], [#8311])
|
||||
|
||||
## Background
|
||||
|
||||
_This section was originally authored in the [Process Model 2.0 Spec]. Please
|
||||
refer to it there for its original context._
|
||||
|
||||
Let's presume that you're a user who wants to be able to open an elevated tab
|
||||
within an otherwise unelevated Terminal window. We call this scenario "mixed
|
||||
elevation" - the tabs within the Terminal can be running either unelevated _or_
|
||||
elevated client applications.
|
||||
|
||||
It wouldn't be terribly difficult for the unelevated Terminal to request the
|
||||
permission of the user to spawn an elevated client application. The user would
|
||||
see a UAC prompt, they'd accept, and then they'd be able to have an elevated
|
||||
shell alongside their unelevated tabs.
|
||||
|
||||
However, this creates an escalation of privilege vector. Now, there's an
|
||||
unelevated window which is connected directly to an elevated process. At this
|
||||
point, **any other unelevated application could send input to the Terminal's
|
||||
`HWND`**. This would make it possible for another unelevated process to "drive"
|
||||
the Terminal window, and send commands to the elevated client application.
|
||||
|
||||
It was initially theorized that the window/content model architecture would also
|
||||
help enable "mixed elevation". With mixed elevation, tabs could run at different
|
||||
integrity levels within the same terminal window. However, after investigation
|
||||
and research, it has become apparent that this scenario is not possible to do
|
||||
safely after all. There are numerous technical difficulties involved, and each
|
||||
with their own security risks. At the end of the day, the team wouldn't be
|
||||
comfortable shipping a mixed-elevation solution, because there's simply no way
|
||||
for us to be confident that we haven't introduced an escalation-of-privilege
|
||||
vector utilizing the Terminal. No matter how small the attack surface might be,
|
||||
we wouldn't be confident that there are _no_ vectors for an attack.
|
||||
|
||||
Some things we considered during this investigation:
|
||||
|
||||
* If a user requests a new elevated tab from an otherwise unelevated window, we
|
||||
could use UAC to create a new, elevated window process, and "move" all the
|
||||
current tabs to that window process, as well as the new elevated client. Now,
|
||||
the window process would be elevated, preventing it from input injection, and
|
||||
it would still contains all the previously existing tabs. The original window
|
||||
process could now be discarded, as the new elevated window process will
|
||||
pretend to be the original window.
|
||||
- However, it is unfortunately not possible with COM to have an elevated
|
||||
client attach to an unelevated server that's registered at runtime. Even in
|
||||
a packaged environment, the OS will reject the attempt to `CoCreateInstance`
|
||||
the content process object. this will prevent elevated windows from
|
||||
re-connecting to unelevated client processes.
|
||||
- We could theoretically build an RPC tunnel between content and window
|
||||
processes, and use the RPC connection to marshal the content process to the
|
||||
elevated window. However, then _we_ would need to be responsible for
|
||||
securing access the the RPC endpoint, and we feel even less confident doing
|
||||
that.
|
||||
- Attempts were also made to use a window-broker-content architecture, with
|
||||
the broker process having a static CLSID in the registry, and having the
|
||||
window and content processes at mixed elevation levels `CoCreateInstance`
|
||||
that broker. This however _also_ did not work across elevation levels. This
|
||||
may be due to a lack of Packaged COM support for mixed elevation levels.
|
||||
|
||||
It's also possible that the author forgot that packaged WinRT doesn't play
|
||||
nicely with creating objects in an elevated context. The Terminal has
|
||||
previously needed to manually manifest all its classes in a SxS manifest for
|
||||
Unpackaged WinRT to allow the classes to be activated, rather than relying
|
||||
on the packaged catalog. It's theoretically possible that doing that would
|
||||
have allowed the broker to be activated across integrity levels.
|
||||
|
||||
Even if this approach did end up working, we would still need to be
|
||||
responsible for securing the elevated windows so that an unelevated attacker
|
||||
couldn't hijack a content process and trigger unexpected code in the window
|
||||
process. We didn't feel confident that we could properly secure this channel
|
||||
either.
|
||||
|
||||
We also considered allowing mixed content in windows that were _originally_
|
||||
elevated. If the window is already elevated, then it can launch new unelevated
|
||||
processes. We could allow elevated windows to still create unelevated
|
||||
connections. However, we'd want to indicate per-pane what the elevation state
|
||||
of each connection is. The user would then need to keep track themselves of
|
||||
which terminal instances are elevated, and which are not.
|
||||
|
||||
This also marks a departure from the current behavior, where everything in an
|
||||
elevated window would be elevated by default. The user would need to specify for
|
||||
each thing in the elevated window that they'd want to create it elevated. Or the
|
||||
Terminal would need to provide some setting like
|
||||
`"autoElevateEverythingInAnElevatedWindow"`.
|
||||
|
||||
We cannot support mixed elevation when starting in a unelevated window.
|
||||
Therefore, it doesn't make a lot of UX sense to support it in the other
|
||||
direction. It's a cleaner UX story to just have everything in a single window at
|
||||
the same elevation level.
|
||||
|
||||
## Solution Design
|
||||
|
||||
Instead of supporting mixed elevation in the same window, we'll introduce the
|
||||
following features to the Terminal. These are meant as a way of improving the
|
||||
quality of life for users who work in mixed-elevation (or even just elevated)
|
||||
environments.
|
||||
|
||||
### Visible indicator for elevated windows
|
||||
|
||||
As requested in [#1939], it would be nice if it was easy to visibly identify if
|
||||
a Terminal window was elevated or not.
|
||||
|
||||
One easy way of doing this is by adding a simple UAC shield to the left of the
|
||||
tabs for elevated windows. This shield could be configured by the theme (see
|
||||
[#3327]). We could provide the following states:
|
||||
* Colored (the default)
|
||||
* Monochrome
|
||||
* Hidden, to hide the shield even on elevated windows. This is the current
|
||||
behavior.
|
||||
|
||||

|
||||
_figure 1: a monochrome UAC shield in the titlebar of the window, courtesy of @mdtauk_
|
||||
|
||||
We could also simplify this to only allow a boolean true/false for displaying
|
||||
the shield. As we do often with other enums, we could define `true` to be the
|
||||
same as the default appearance, and `false` to be the hidden option. As always,
|
||||
the development of the Terminal is an iterative process, where we can
|
||||
incrementally improve from no setting, to a boolean setting, to a enum-backed
|
||||
one.
|
||||
|
||||
### Configuring a profile to always run elevated
|
||||
|
||||
Oftentimes, users might have a particular tool chain that only works when
|
||||
running elevated. In these scenarios, it would be convenient for the user to be
|
||||
able to identify that the profile should _always_ run elevated. That way, they
|
||||
could open the profile from the dropdown menu of an otherwise unelevated window
|
||||
and have the elevated window open with the profile automatically.
|
||||
|
||||
We'll be adding the `"elevate": true|false` setting as a per-profile setting,
|
||||
with a default value of `false`. When set to `true`, we'll try to auto-elevate
|
||||
the profile whenever it's launched. We'll check to see if this window is
|
||||
elevated before creating the connection for this profile. If the window is not
|
||||
elevated, then we'll create a new window with the requested elevation level to
|
||||
handle the new connection.
|
||||
|
||||
`"elevate": false` will do nothing. If the window is already elevated, then the
|
||||
profile won't open an un-elevated window.
|
||||
|
||||
If the user tries to open an `"elevate": true` profile in a window that's
|
||||
already elevated, then a new tab/split will open in the existing window, rather
|
||||
than spawning an additional elevated window.
|
||||
|
||||
There are three situations where we're creating new terminal instances: new
|
||||
tabs, new splits, and new windows. Currently, these are all actions that are
|
||||
also exposed in the `wt` commandline as subcommands. We can convert from the
|
||||
commandline arguments into these actions already. Therefore, it shouldn't be too
|
||||
challenging to convert these actions back into the equal commandline arguments.
|
||||
|
||||
For the following examples, let's assume the user is currently in an unelevated
|
||||
Terminal window.
|
||||
|
||||
When the user tries to create a new elevated **tab**, we'll need to create a new
|
||||
process, elevated, with the following commandline:
|
||||
|
||||
```
|
||||
wt new-tab [args...]
|
||||
```
|
||||
|
||||
When we create this new `wt` instance, it will obey the glomming rules as
|
||||
specified in [Session Management Spec]. It might end up glomming to another
|
||||
existing window at that elevation level, or possibly create its own window.
|
||||
|
||||
Similarly, for a new elevated **window**, we can make sure to pass the `-w new`
|
||||
arguments to `wt`. These parameters indicate that we definitely want this
|
||||
command to run in a new window, regardless of the current glomming settings.
|
||||
|
||||
```
|
||||
wt -w new new-tab [args...]
|
||||
```
|
||||
|
||||
However, creating a new **pane** is a little trickier. Invoking the `wt
|
||||
split-pane [args...]` is straightforward enough.
|
||||
|
||||
<!-- Discussion notes follow:
|
||||
If the current window doesn't have the same elevation level as the
|
||||
requested profile, do we always want to just create a new split? If the command
|
||||
ends up glomming to an existing window, does that even make sense? That invoking
|
||||
an elevated split in an unelevated window would end up splitting the elevated
|
||||
window? It's very possible that the user wanted a split in the tab they're
|
||||
currently in, in the unelevated window, but they don't want a split in the
|
||||
elevated window.
|
||||
|
||||
What if there's not space in the elevated window to create the split (but there
|
||||
would be in the current window)? That would sure make it seem like nothing
|
||||
happened, silently.
|
||||
|
||||
We could alternatively have cross-elevation splits default to always opening a
|
||||
new tab. That might mitigate some of the odd behaviors. Until we actually have
|
||||
support for running commands in existing windows, we'll always need to make a
|
||||
new window when running elevated. We'll need to make the new window for new tabs
|
||||
and splits, because there's no way to invoke another existing window.
|
||||
|
||||
A third proposal is to pop a warning dialog at the user when they try to open an
|
||||
elevated split from and unelevated window. This dialog could be something like
|
||||
|
||||
> What you requested couldn't be completed. Do you want to:
|
||||
> A. Make me a new tab instead.
|
||||
> B. Forget it and cancel. I'll go fix my config.
|
||||
|
||||
I'm certainly leaning towards proposal 2 - always create a new tab. This is how
|
||||
it's implemented in [#8514]. In that PR, this seems to work sensibly.
|
||||
-->
|
||||
|
||||
After discussing with the team, we have decided that the most sensible approach
|
||||
for handling a cross-elevation `split-pane` is to just create a new tab in the
|
||||
elevated window. The user can always re-attach the pane as a split with the
|
||||
`move-pane` command once the new pane in the elevated window.
|
||||
|
||||
#### Configure the Terminal to _always_ run elevated
|
||||
|
||||
`elevate` is a per-profile property, not a global property. If a user
|
||||
wants to always have all instances of the Terminal run elevated, they
|
||||
could set `"elevate": true` in their profile defaults. That would cause _all_
|
||||
profiles they launch to always spawn as elevated windows.
|
||||
|
||||
#### `elevate` in Actions
|
||||
|
||||
Additionally, we'll add the `elevate` property to the `NewTerminalArgs` used in
|
||||
the `newTab`, `splitPane`, and `newWindow` actions. This is similar to how other
|
||||
properties of profiles can be overridden at launch time. This will allow
|
||||
windows, tabs and panes to all be created specifically as elevated windows.
|
||||
|
||||
In the `NewTerminalArgs`, `elevate` will be an optional boolean, with the
|
||||
following behavior:
|
||||
* `null` (_default_): Don't modify the `elevate` property for this profile
|
||||
* `true`: This launch should act like the profile had `"elevate": true` in its
|
||||
properties.
|
||||
* `false`: This launch should act like the profile had `"elevate": false` in its
|
||||
properties.
|
||||
|
||||
We'll also add an iterable command for opening a profile in an
|
||||
elevated tab, with the following json:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
// New elevated tab...
|
||||
"name": { "key": "NewElevatedTabParentCommandName", "icon": "UAC-Shield.png" },
|
||||
"commands": [
|
||||
{
|
||||
"iterateOn": "profiles",
|
||||
"icon": "${profile.icon}",
|
||||
"name": "${profile.name}",
|
||||
"command": { "action": "newTab", "profile": "${profile.name}", "elevated": true }
|
||||
}
|
||||
]
|
||||
},
|
||||
```
|
||||
|
||||
#### Elevation from the dropdown
|
||||
|
||||
Currently, the new tab dropdown supports opening a new pane by
|
||||
<kbd>Alt+click</kbd>ing on a profile. We could similarly add support to open a
|
||||
tab elevated with <kbd>Ctrl+click</kbd>. This is similar to the behavior of the
|
||||
Windows taskbar. It supports creating an elevated instance of a program by
|
||||
<kbd>Ctrl+click</kbd>ing on entries as well.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Starting an elevated process from an unelevated process
|
||||
|
||||
It seems that we're able to create an elevated process by passing the `"runas"`
|
||||
verb to
|
||||
[`ShellExecute`](https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutea).
|
||||
So we could use something like
|
||||
|
||||
```c++
|
||||
ShellExecute(nullptr,
|
||||
L"runas",
|
||||
L"wt.exe",
|
||||
L"-w new new-tab [args...]",
|
||||
nullptr,
|
||||
SW_SHOWNORMAL);
|
||||
```
|
||||
|
||||
This will ask the shell to perform a UAC prompt before spawning `wt.exe` as an
|
||||
elevated process.
|
||||
|
||||
> 👉 NOTE: This mechanism won't always work on non-Desktop SKUs of Windows. For
|
||||
> more discussion, see [Elevation on OneCore SKUs](#Elevation-on-OneCore-SKUs).
|
||||
|
||||
## Potential Issues
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><strong>Accessibility</strong></td>
|
||||
<td>
|
||||
|
||||
The set of changes proposed here are not expected to introduce any new
|
||||
accessibility issues. Users can already create elevated Terminal windows. Making
|
||||
it easier to create these windows doesn't really change our accessibility story.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Security</strong></td>
|
||||
<td>
|
||||
|
||||
We won't be doing anything especially unique, so there aren't expected to be any
|
||||
substantial security risks associated with these changes. Users can already
|
||||
create elevated Terminal windows, so we're not really introducing any new
|
||||
functionality, from a security perspective.
|
||||
|
||||
We're relying on the inherent security of the `runas` verb of `ShellExecute` to
|
||||
prevent any sort of unexpected escalation-of-privilege.
|
||||
|
||||
<hr>
|
||||
|
||||
One security concern is the fact that the `settings.json` file is currently a
|
||||
totally unsecured file. It's completely writable by any medium-IL process. That
|
||||
means it's totally possible for a malicious program to change the file. The
|
||||
malicious program could find a user's "Elevated PowerShell" profile, and change
|
||||
the commandline to `malicious.exe`. The user might then think that their
|
||||
"Elevated PowerShell" will run `powershell.exe` elevated, but will actually
|
||||
auto-elevate this attacker.
|
||||
|
||||
If all we expose to the user is the name of the profile in the UAC dialog, then
|
||||
there's no way for the user to be sure that the program that's about to be
|
||||
launched is actually what they expect.
|
||||
|
||||
To help mitigate this, we should _always_ pass the evaluated `commandline` as a
|
||||
part of the call to `ShellExecute`. the arguments that are passed to
|
||||
`ShellExecute` are visible to the user, though they need to click the "More
|
||||
Details" dropdown to reveal them.
|
||||
|
||||
We will need to mitigate this vulnerability regardless of adding support for the
|
||||
auto-elevation of individual terminal tabs/panes. If a user is launching the
|
||||
Terminal elevated (i.e. from the Win+X menu in Windows 11), then it's possible
|
||||
for a malicious program to overwrite the `commandline` of their default profile.
|
||||
The user may now unknowingly invoke this malicious program while thinking they
|
||||
are simply launching the Terminal.
|
||||
|
||||
To deal with this more broadly, we will display a dialog within the Terminal
|
||||
window before creating **any** elevated terminal instance. In that dialog, we'll
|
||||
display the commandline that will be executed, so the user can very easily
|
||||
confirm the commandline.
|
||||
|
||||
This will need to happen for all elevated terminal instances. For an elevated
|
||||
Windows Terminal window, this means _all_ connections made by the Terminal.
|
||||
Every time the user opens a new profile or a new commandline in a pane, we'll
|
||||
need to prompt them first to confirm the commandline. This dialog within the
|
||||
elevated window will also prevent an attacker from editing the `settings.json`
|
||||
file while the user already has an elevated Terminal window open and hijacking a
|
||||
profile.
|
||||
|
||||
The dialog options will certainly be annoying to users who don't want to be
|
||||
taken out of their flow to confirm the commandline that they wish to launch.
|
||||
There's precedent for a similar warning being implemented by VSCode, with their
|
||||
[Workspace Trust] feature. They too faced a similar backlash when the feature
|
||||
first shipped. However, in light of recent global cybersecurity attacks, this is
|
||||
seen as an acceptable UX degradation in the name of application trust. We don't
|
||||
want to provide an avenue that's too easy to abuse.
|
||||
|
||||
When the user confirms the commandline of this profile as something safe to run,
|
||||
we'll add it to an elevated-only version of `state.json`. (see [#7972] for more
|
||||
details). This elevated version of the file will only be accessible by the
|
||||
elevated Terminal, so an attacker cannot hijack the contents of the file. This
|
||||
will help mitigate the UX discomfort caused by prompting on every commandline
|
||||
launched. This should mean that the discomfort is only limited to the first
|
||||
elevated launch of a particular profile. Subsequent launches (without modifying
|
||||
the `commandline`) will work as they always have.
|
||||
|
||||
The dialog for confirming these commandlines should have a link to the docs for
|
||||
"Learn more...". Transparency in the face of this dialog should
|
||||
mitigate some dissatisfaction.
|
||||
|
||||
The dialog will _not_ appear if the user does not have a split token - if the
|
||||
user's PC does not have UAC enabled, then they're _already_ running as an
|
||||
Administrator. Everything they do is elevated, so they shouldn't be prompted in
|
||||
this way.
|
||||
|
||||
The Settings UI should also expose a way of viewing and removing these cached
|
||||
entries. This page should only be populated in the elevated version of the
|
||||
Terminal.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Reliability</strong></td>
|
||||
<td>
|
||||
|
||||
No changes to our reliability are expected as a part of this change.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Compatibility</strong></td>
|
||||
<td>
|
||||
|
||||
There are no serious compatibility concerns expected with this changelist. The
|
||||
new `elevate` property will be unset by default, so users will heed to opt-in
|
||||
to the new auto-elevating behavior.
|
||||
|
||||
There is one minor concern regarding introducing the UAC shield on the window.
|
||||
We're planning on using themes to configure the appearance of the shield. That
|
||||
means we'll need to ship themes before the user will be able to hide the shield
|
||||
again.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Performance, Power, and Efficiency</strong></td>
|
||||
<td>
|
||||
|
||||
No changes to our performance are expected as a part of this change.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### Centennial Applications
|
||||
|
||||
In the past, we've had a notoriously rough time with the Centennial app
|
||||
infrastructure and running the Terminal elevated. Notably, we've had to list all
|
||||
our WinRT classes in our SxS manifest so they could be activated using
|
||||
unpackaged WinRT while running elevated. Additionally, there are plenty of
|
||||
issues running the Terminal in an "over the shoulder" elevation (OTS) scenario.
|
||||
|
||||
Specifically, we're concerned with the following scenario:
|
||||
* the current user account has the Terminal installed,
|
||||
* but they aren't an Administrator,
|
||||
* the Administrator account doesn't have the Terminal installed.
|
||||
|
||||
In that scenario, the user can run into issues launching the Terminal in an
|
||||
elevated context (even after entering the Admin's credentials in the UAC
|
||||
prompt).
|
||||
|
||||
This spec proposes no new mitigations for dealing with these issues. It may in
|
||||
fact make them more prevalent, by making elevated contexts more easily
|
||||
accessible.
|
||||
|
||||
Unfortunately, these issues are OS bugs that are largely out of our own control.
|
||||
We will continue to apply pressure to the centennial app team internally as we
|
||||
encounter these issues. They are are team best equipped to resolve these issues.
|
||||
|
||||
### Default Terminal & auto-elevation
|
||||
|
||||
In the future, when we support setting the Terminal as the "default terminal
|
||||
emulator" on Windows. When that lands, we will use the `profiles.defaults`
|
||||
settings to create the tab where we'll be hosting the commandline client. If the user has
|
||||
`"elevate": true` in their `profiles.defaults`, we'd usually try to
|
||||
auto-elevate the profile. In this scenario, however, we can't do that. The
|
||||
Terminal is being invoked on behalf of the client app launching, instead of the
|
||||
Terminal invoking the client application.
|
||||
|
||||
**2021-08-17 edit**: Now that "defterm" has shipped, we're a little more aware
|
||||
of some of the limitations with packaged COM and elevation boundaries. Defterm
|
||||
cannot be used with elevated processes _at all_ currently (see [#10276]). When
|
||||
an elevated commandline application is launched, it will always just appear in
|
||||
`conhost.exe`. Furthermore, An unelevated peasant can't communicate with an
|
||||
elevated monarch so we can't toss the connection to the elevated monarch and
|
||||
have them handle it.
|
||||
|
||||
The simplest solution here is to just _always_ ignore the `elevate` property for
|
||||
incoming defterm connections. This is not an ideal solution, and one that we're
|
||||
willing to revisit if/when [#10276] is ever fixed.
|
||||
|
||||
### Elevation on OneCore SKUs
|
||||
|
||||
This spec proposes using `ShellExecute` to elevate the Terminal window. However,
|
||||
not all Windows SKUs have support for `ShellExecute`. Notably, the non-Desktop
|
||||
SKUs, which are often referred to as "OneCore" SKUs. On these platforms, we
|
||||
won't be able to use `ShellExecute` to elevate the Terminal. There might not
|
||||
even be the concept of multiple elevation levels, or different users, depending
|
||||
on the SKU.
|
||||
|
||||
Fortunately, this is a mostly hypothetical concern for the moment. Desktop is
|
||||
the only publicly supported SKU for the Terminal currently. If the Terminal ever
|
||||
does become available on those SKUs, we can use these proposals as mitigations.
|
||||
|
||||
* If elevation is supported, there must be some other way of elevating a
|
||||
process. We could always use that mechanism instead.
|
||||
* If elevation isn't supported (I'm thinking 10X is one of these), then we could
|
||||
instead display a warning dialog whenever a user tries to open an elevated
|
||||
profile.
|
||||
- We could take the warning a step further. We could add another settings
|
||||
validation step. This would warn the user if they try to mark any profiles
|
||||
or actions as `"elevate":true`
|
||||
|
||||
## Future considerations
|
||||
|
||||
* If we wanted to go even further down the visual differentiation route, we
|
||||
could consider allowing the user to set an entirely different theme ([#3327])
|
||||
based on the elevation state. Something like `elevatedTheme`, to pick another
|
||||
theme from the set of themes. This would allow them to force elevated windows
|
||||
to have a red titlebar, for example.
|
||||
* Over the course of discussion concerning appearance objects ([#8345]), it
|
||||
became clear that having separate "elevated" appearances defined for
|
||||
`profile`s was overly complicated. This is left as a consideration for a
|
||||
possible future extension that could handle this scenario in a cleaner way.
|
||||
* Similarly, we're going to leave [#3637] "different profiles when elevated vs
|
||||
unelevated" for the future. This also plays into the design of "configure the
|
||||
new tab dropdown" ([#1571]), and reconciling those two designs is out-of-scope
|
||||
for this particular release.
|
||||
* Tangentially, we may want to have a separate Terminal icon we ship with the
|
||||
UAC shield present on it. This would be especially useful for the tray icon.
|
||||
Since there will be different tray icon instances for elevated and unelevated
|
||||
windows, having unique icons may help users identify which is which.
|
||||
|
||||
### De-elevating a Terminal
|
||||
|
||||
the original version of this spec proposed that `"elevated":false` from an
|
||||
elevated Terminal window should create a new unelevated Terminal instance. The
|
||||
mechanism for doing this is described in [The Old New Thing: How can I launch an
|
||||
unelevated process from my elevated process, redux].
|
||||
|
||||
This works well when the Terminal is running unpackaged. However, de-elevating a
|
||||
process does not play well with packaged centennial applications. When asking
|
||||
the OS to run the packaged application from an elevated context, the system will
|
||||
still create the child process _elevated_. This means the packaged version of
|
||||
the Terminal won't be able to create a new unelevated Terminal instance.
|
||||
|
||||
From an internal mail thread:
|
||||
|
||||
> App model intercepts the `CreateProcess` call and redirects it to a COM
|
||||
> service. The parent of a packaged app is not the launching app, it’s some COM
|
||||
> service. So none of the parent process nonsense will work because the
|
||||
> parameters you passed to `CreateProcess` aren’t being used to create the
|
||||
> process.
|
||||
|
||||
If this is fixed in the future, we could theoretically re-introduce de-elevating
|
||||
a profile. The original spec proposed a `"elevated": bool?` setting, with the
|
||||
following behaviors:
|
||||
* `null` (_default_): Don't modify the elevation level when running this profile
|
||||
* `true`: If the current window is unelevated, try to create a new elevated
|
||||
window to host this connection.
|
||||
* `false`: If the current window is elevated, try to create a new unelevated
|
||||
window to host this connection.
|
||||
|
||||
We could always re-introduce this setting, to supercede `elevate`.
|
||||
|
||||
### Change profile appearance for elevated windows
|
||||
|
||||
In [#3062] and [#8345], we're planning on allowing users to set different
|
||||
appearances for a profile whether it's focused or not. We could do similar thing
|
||||
to enable a profile to have a different appearance when elevated. In the
|
||||
simplest case, this could allow the user to set `"background": "#ff0000"`. This
|
||||
would make a profile always appear to have a red background when in an elevated
|
||||
window.
|
||||
|
||||
The more specific details of this implementation are left to the spec
|
||||
[Configuration object for profiles].
|
||||
|
||||
In discussion of that spec, we decided that it would be far too complicated to
|
||||
try and overload the `unfocusedAppearance` machinery for differentiating between
|
||||
elevated and unelevated versions of the same profile. Already, that would lead
|
||||
to 4 states: [`appearance`, `unfocusedAppearance`, `elevatedAppearance`,
|
||||
`elevatedUnfocusedAppearance`]. This would lead to a combinatorial explosion if
|
||||
we decided in the future that there should also be other states for a profile.
|
||||
|
||||
This particular QoL improvement is currently being left as a future
|
||||
consideration, should someone come up with a clever way of defining
|
||||
elevated-specific settings.
|
||||
|
||||
<!--
|
||||
Brainstorming notes for future readers:
|
||||
|
||||
You could have a profile that layers on an existing profile, with elevated-specific settings:
|
||||
|
||||
{
|
||||
"name": "foo",
|
||||
"background": "#0000ff",
|
||||
"commandline": "cmd.exe /k echo I am unelevated"
|
||||
},
|
||||
{
|
||||
"inheritsFrom": "foo",
|
||||
"background": "#ff0000",
|
||||
"elevate": true,
|
||||
"commandline": "cmd.exe /k echo I am ELEVATED"
|
||||
}
|
||||
-->
|
||||
|
||||
<!-- Footnotes -->
|
||||
|
||||
[#632]: https://github.com/microsoft/terminal/issues/632
|
||||
[#1032]: https://github.com/microsoft/terminal/issues/1032
|
||||
[#1571]: https://github.com/microsoft/terminal/issues/1571
|
||||
[#1939]: https://github.com/microsoft/terminal/issues/1939
|
||||
[#3062]: https://github.com/microsoft/terminal/issues/3062
|
||||
[#3327]: https://github.com/microsoft/terminal/issues/3327
|
||||
[#3637]: https://github.com/microsoft/terminal/issues/3637
|
||||
[#4472]: https://github.com/microsoft/terminal/issues/4472
|
||||
[#5000]: https://github.com/microsoft/terminal/issues/5000
|
||||
[#7972]: https://github.com/microsoft/terminal/pull/7972
|
||||
[#8311]: https://github.com/microsoft/terminal/issues/8311
|
||||
[#8345]: https://github.com/microsoft/terminal/issues/8345
|
||||
[#8514]: https://github.com/microsoft/terminal/issues/8514
|
||||
[#10276]: https://github.com/microsoft/terminal/issues/10276
|
||||
|
||||
[Process Model 2.0 Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%235000%20-%20Process%20Model%202.0.md
|
||||
[Configuration object for profiles]: https://github.com/microsoft/terminal/blob/main/doc/specs/Configuration%20object%20for%20profiles.md
|
||||
[Session Management Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%234472%20-%20Windows%20Terminal%20Session%20Management.md
|
||||
[The Old New Thing: How can I launch an unelevated process from my elevated process, redux]: https://devblogs.microsoft.com/oldnewthing/20190425-00/?p=102443
|
||||
[Workspace Trust]: https://code.visualstudio.com/docs/editor/workspace-trust
|
||||
BIN
doc/specs/#5000 - Process Model 2.0/UAC-shield-in-titlebar.png
Normal file
BIN
doc/specs/#5000 - Process Model 2.0/UAC-shield-in-titlebar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 565 KiB |
BIN
doc/specs/#6900 - Actions Page/add-click.png
Normal file
BIN
doc/specs/#6900 - Actions Page/add-click.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 142 KiB |
BIN
doc/specs/#6900 - Actions Page/add-keys.png
Normal file
BIN
doc/specs/#6900 - Actions Page/add-keys.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 140 KiB |
BIN
doc/specs/#6900 - Actions Page/edit-click.png
Normal file
BIN
doc/specs/#6900 - Actions Page/edit-click.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 142 KiB |
BIN
doc/specs/#6900 - Actions Page/edit-keys.png
Normal file
BIN
doc/specs/#6900 - Actions Page/edit-keys.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 142 KiB |
130
doc/specs/#6900 - Actions Page/spec.md
Normal file
130
doc/specs/#6900 - Actions Page/spec.md
Normal file
@@ -0,0 +1,130 @@
|
||||
---
|
||||
author: Kayla Cinnamon - cinnamon-msft
|
||||
created on: 2021-03-04
|
||||
last updated: 2021-03-09
|
||||
issue id: 6900
|
||||
---
|
||||
|
||||
# Actions Page
|
||||
|
||||
## Abstract
|
||||
|
||||
We need to represent actions inside the settings UI. This spec goes through the possible use cases and reasoning for including specific features for actions inside the settings UI.
|
||||
|
||||
## Background
|
||||
|
||||
### Inspiration
|
||||
|
||||
It would be ideal if we could get the settings UI to have parity with the JSON file. This will take some design work if we want every feature possible in relation to actions. There is also the option of not having parity with the JSON file in order to present a simpler UX.
|
||||
|
||||
### User Stories
|
||||
|
||||
All of these features are possible with the JSON file. This spec will go into discussion of which (possibly all) of these user stories need to be handled by the settings UI.
|
||||
|
||||
- Add key bindings to an action that does not already have keys assigned
|
||||
- Edit key bindings for an action
|
||||
- Remove key bindings from an action
|
||||
- Add multiple key bindings for the same action
|
||||
- Create an iterable action
|
||||
- Create a nested action
|
||||
- Choose which actions appear inside the command palette
|
||||
- See all possible actions, regardless of keys
|
||||
|
||||
Commands with properties:
|
||||
- sendInput has "input"
|
||||
- closeOtherTabs has "index"
|
||||
- closeTabsAfter has "index"
|
||||
- renameTab has "title"*
|
||||
- setTabColor has "color"*
|
||||
- newWindow has "commandline", "startingDirectory", "tabTitle", "index", "profile"
|
||||
- splitPane has "split", "commandline", "startingDirectory", "tabTitle", "index", "profile", "splitMode", "size"
|
||||
- copy has "singleLine", "copyFormatting"
|
||||
- scrollUp has "rowsToScroll"
|
||||
- scrollDown has "rowsToScroll"
|
||||
- setColorScheme has "colorScheme"
|
||||
|
||||
Majority of these commands listed above are intended for the command palette, so they wouldn't make much sense with keys assigned to them anyway.
|
||||
|
||||
### Future Considerations
|
||||
|
||||
One day we'll have actions that can be invoked by items in the dropdown menu. This setting will have to live somewhere. Also, once we get a status bar, people may want to invoke actions from there.
|
||||
|
||||
## Solution Design
|
||||
|
||||
### Proposal 1: Keyboard and Command Palette pages
|
||||
|
||||
Implement a Keyboard page in place of the Actions page. Also plan for a Command Palette page in the future if it's something that's heavily requested. The Command Palette page would cover the missing use cases listed below.
|
||||
|
||||
When users want to add a new key binding, the dropdown will list every action, regardless if it already has keys assigned. This page should show every key binding assigned to an action, even if there are multiple bindings to the same action.
|
||||
|
||||
Users will be able to view every possible action from the command palette if they'd like.
|
||||
|
||||
Use cases covered:
|
||||
- Add key bindings to an action that does not already have keys assigned
|
||||
- Edit key bindings for an action
|
||||
- Remove key bindings from an action
|
||||
- Add multiple key bindings for the same action
|
||||
- See all actions that have keys assigned
|
||||
|
||||
Use cases missing:
|
||||
- Create an iterable action
|
||||
- Create a nested action
|
||||
- Choose which actions appear inside the command palette
|
||||
- See all possible actions, regardless of keys
|
||||
|
||||
* **Pros**:
|
||||
- This allows people to edit their actions in most of their scenarios.
|
||||
- This gives us some wiggle room to cover majority of the use cases we need and seeing if people want the other use cases that are missing.
|
||||
|
||||
* **Cons**:
|
||||
- Unfortunately we couldn't cover every single use case with this design.
|
||||
- You can't edit the properties that are on some commands, however the default commands from the command palette include options with properties anyway. For example "decrease font size" has the `delta` property already included.
|
||||
|
||||
### Proposal 2: Have everything on one Actions page
|
||||
|
||||
Implement an Actions page that allows you to create actions designed for the command palette as well as actions with keys.
|
||||
|
||||
Use cases covered:
|
||||
- Add key bindings to an action that does not already have keys assigned
|
||||
- Edit key bindings for an action
|
||||
- Remove key bindings from an action
|
||||
- Add multiple key bindings for the same action
|
||||
- See all actions that have keys assigned
|
||||
- Create an iterable action
|
||||
- Create a nested action
|
||||
- Choose which actions appear inside the command palette
|
||||
- See all possible actions, regardless of keys
|
||||
|
||||
I could not come up with a UX design that wasn't too complicated or confusing for this scenario.
|
||||
|
||||
**Pros**:
|
||||
- There is full parity with the JSON file.
|
||||
|
||||
**Cons**:
|
||||
- Could not come up with a simplistic design to represent all of the use cases (which makes the settings UI not as enticing since it promotes ease of use).
|
||||
|
||||
## Conclusion
|
||||
|
||||
We considered Proposal 2, however the design became cluttered very quickly and we agreed to create two pages and start off with Proposal 1.
|
||||
|
||||
## UI/UX Design
|
||||
|
||||

|
||||
|
||||
The Add new button is using the secondary color, to align with the button on the Color schemes page.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Potential Issues
|
||||
|
||||
This design is not 1:1 with the JSON file, so actions that don't have keys will not appear on this page. Additionally, you can't add a new action without keys with this current design.
|
||||
|
||||
You also cannot specify properties on commands (like the `newTab` command) and these will have to be added through the JSON file. Considering there are only a few of these and we're planning to iterate on this and add a Command Palette page, we were okay with this decision.
|
||||
|
||||
## Resources
|
||||
|
||||
### Footnotes
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -8,5 +8,5 @@ Please consult the [license](https://raw.githubusercontent.com/microsoft/cascadi
|
||||
|
||||
### Fonts Included
|
||||
|
||||
* Cascadia Code, Cascadia Mono (2106.17)
|
||||
* from microsoft/cascadia-code@fb0bce69c1c12f6c298b8bc1c1d181868f5daa9a
|
||||
* Cascadia Code, Cascadia Mono (2111.01)
|
||||
* from microsoft/cascadia-code@de36d62e777d34d3bed92a7e23988e5d61e0ba02
|
||||
|
||||
@@ -140,12 +140,12 @@
|
||||
<!-- **END VC LIBS HACK** -->
|
||||
|
||||
<!-- This is required to get the package dependency in the AppXManifest. -->
|
||||
<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.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')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.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.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'))" />
|
||||
</Target>
|
||||
|
||||
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include "MyPage.h"
|
||||
#include "MySettings.h"
|
||||
#include <LibraryResources.h>
|
||||
#include "MyPage.g.cpp"
|
||||
#include "..\..\..\src\cascadia\UnitTests_Control\MockControlSettings.h"
|
||||
#include "..\..\..\src\types\inc\utils.hpp"
|
||||
#include "MySettings.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using namespace winrt::Microsoft::Terminal;
|
||||
@@ -28,7 +26,7 @@ namespace winrt::SampleApp::implementation
|
||||
|
||||
void MyPage::Create()
|
||||
{
|
||||
auto settings = winrt::make_self<MySettings>();
|
||||
auto settings = winrt::make_self<implementation::MySettings>();
|
||||
|
||||
auto connectionSettings{ TerminalConnection::ConptyConnection::CreateSettings(L"cmd.exe /k echo This TermControl is hosted in-proc...",
|
||||
winrt::hstring{},
|
||||
@@ -46,212 +44,6 @@ namespace winrt::SampleApp::implementation
|
||||
Control::TermControl control{ *settings, conn };
|
||||
|
||||
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...");
|
||||
});
|
||||
}
|
||||
|
||||
static wil::unique_process_information _createHostClassProcess(const winrt::guid& g)
|
||||
{
|
||||
auto guidStr{ ::Microsoft::Console::Utils::GuidToString(g) };
|
||||
|
||||
// Create an event that the content process will use to signal it is
|
||||
// ready to go. We won't need the event after this function, so the
|
||||
// unique_event will clean up our handle when we leave this scope. The
|
||||
// ContentProcess is responsible for cleaning up its own handle.
|
||||
wil::unique_event ev{ CreateEvent(nullptr, true, false, L"contentProcessStarted") };
|
||||
// Make sure to mark this handle as inheritable! Even with
|
||||
// bInheritHandles=true, this is only inherited when it's explicitly
|
||||
// allowed to be.
|
||||
SetHandleInformation(ev.get(), HANDLE_FLAG_INHERIT, 1);
|
||||
|
||||
// god bless, fmt::format will format a HANDLE like `0xa80`
|
||||
std::wstring commandline{
|
||||
fmt::format(L"windowsterminal.exe --content {} --signal {}", guidStr, ev.get())
|
||||
};
|
||||
|
||||
STARTUPINFO siOne{ 0 };
|
||||
siOne.cb = sizeof(STARTUPINFOW);
|
||||
wil::unique_process_information piOne;
|
||||
auto succeeded = CreateProcessW(
|
||||
nullptr,
|
||||
commandline.data(),
|
||||
nullptr, // lpProcessAttributes
|
||||
nullptr, // lpThreadAttributes
|
||||
true, // bInheritHandles
|
||||
CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags
|
||||
nullptr, // lpEnvironment
|
||||
nullptr, // startingDirectory
|
||||
&siOne, // lpStartupInfo
|
||||
&piOne // lpProcessInformation
|
||||
);
|
||||
THROW_IF_WIN32_BOOL_FALSE(succeeded);
|
||||
|
||||
// Wait for the child process to signal that they're ready.
|
||||
WaitForSingleObject(ev.get(), INFINITE);
|
||||
|
||||
return std::move(piOne);
|
||||
}
|
||||
|
||||
winrt::fire_and_forget MyPage::_writeToLog(std::wstring_view str)
|
||||
{
|
||||
winrt::hstring copy{ str };
|
||||
// Switch back to the UI thread.
|
||||
co_await resume_foreground(Dispatcher());
|
||||
winrt::WUX::Controls::TextBlock block;
|
||||
block.Text(copy);
|
||||
Log().Children().Append(block);
|
||||
}
|
||||
|
||||
winrt::fire_and_forget MyPage::CreateClicked(const IInspectable& sender,
|
||||
const WUX::Input::TappedRoutedEventArgs& eventArgs)
|
||||
{
|
||||
auto guidString = GuidInput().Text();
|
||||
|
||||
// Capture calling context.
|
||||
winrt::apartment_context ui_thread;
|
||||
|
||||
auto canConvert = guidString.size() == 38 &&
|
||||
guidString.front() == '{' &&
|
||||
guidString.back() == '}';
|
||||
bool tryingToAttach = false;
|
||||
winrt::guid contentGuid{ ::Microsoft::Console::Utils::CreateGuid() };
|
||||
|
||||
if (canConvert)
|
||||
{
|
||||
GUID result{};
|
||||
if (SUCCEEDED(IIDFromString(guidString.c_str(), &result)))
|
||||
{
|
||||
contentGuid = result;
|
||||
tryingToAttach = true;
|
||||
}
|
||||
}
|
||||
_writeToLog(tryingToAttach ? L"Attaching to existing content process" : L"Creating new content process");
|
||||
|
||||
co_await winrt::resume_background();
|
||||
if (!tryingToAttach)
|
||||
{
|
||||
// Spawn a wt.exe, with the guid on the commandline
|
||||
piContentProcess = std::move(_createHostClassProcess(contentGuid));
|
||||
}
|
||||
|
||||
// THIS MUST TAKE PLACE AFTER _createHostClassProcess.
|
||||
// * If we're creating a new OOP control, _createHostClassProcess will
|
||||
// spawn the process that will actually host the ContentProcess
|
||||
// object.
|
||||
// * If we're attaching, then that process already exists.
|
||||
Control::ContentProcess content{nullptr};
|
||||
try
|
||||
{
|
||||
content = create_instance<Control::ContentProcess>(contentGuid, CLSCTX_LOCAL_SERVER);
|
||||
}
|
||||
catch (winrt::hresult_error hr)
|
||||
{
|
||||
_writeToLog(L"CreateInstance the ContentProces object");
|
||||
_writeToLog(fmt::format(L" HR ({}): {}", hr.code(), hr.message().c_str()));
|
||||
co_return; // be sure to co_return or we'll fall through to the part where we clear the log
|
||||
}
|
||||
|
||||
if (content == nullptr)
|
||||
{
|
||||
_writeToLog(L"Failed to connect to the ContentProces object. It may not have been started fast enough.");
|
||||
co_return; // be sure to co_return or we'll fall through to the part where we clear the log
|
||||
}
|
||||
|
||||
TerminalConnection::ConnectionInformation connectInfo{ nullptr };
|
||||
Control::IControlSettings settings{ *winrt::make_self<implementation::MySettings>() };
|
||||
|
||||
// When creating a terminal for the first time, pass it a connection
|
||||
// info
|
||||
//
|
||||
// otherwise, when attaching to an existing one, just pass null, because
|
||||
// we don't need the connection info.
|
||||
if (!tryingToAttach)
|
||||
{
|
||||
auto connectionSettings{ TerminalConnection::ConptyConnection::CreateSettings(L"cmd.exe /k echo This TermControl is hosted out-of-proc...",
|
||||
winrt::hstring{},
|
||||
L"",
|
||||
nullptr,
|
||||
32,
|
||||
80,
|
||||
winrt::guid()) };
|
||||
|
||||
// "Microsoft.Terminal.TerminalConnection.ConptyConnection"
|
||||
winrt::hstring myClass{ winrt::name_of<TerminalConnection::ConptyConnection>() };
|
||||
connectInfo = TerminalConnection::ConnectionInformation(myClass, connectionSettings);
|
||||
|
||||
if (!content.Initialize(settings, connectInfo))
|
||||
{
|
||||
_writeToLog(L"Failed to Initialize the ContentProces object.");
|
||||
co_return; // be sure to co_return or we'll fall through to the part where we clear the log
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we're attaching, we don't really need to do anything special.
|
||||
}
|
||||
|
||||
// Switch back to the UI thread.
|
||||
co_await ui_thread;
|
||||
|
||||
// Create the XAML control that will be attached to the content process.
|
||||
// We're not passing in a connection, because the contentGuid will be used instead.
|
||||
Control::TermControl control{ contentGuid, settings, nullptr };
|
||||
control.RaiseNotice([this](auto&&, auto& args) {
|
||||
_writeToLog(L"Content process died, probably.");
|
||||
_writeToLog(args.Message());
|
||||
OutOfProcContent().Children().Clear();
|
||||
GuidInput().Text(L"");
|
||||
if (piContentProcess.hProcess)
|
||||
{
|
||||
piContentProcess.reset();
|
||||
}
|
||||
});
|
||||
control.ConnectionStateChanged([this, control](auto&&, auto&) {
|
||||
const auto newConnectionState = control.ConnectionState();
|
||||
if (newConnectionState == TerminalConnection::ConnectionState::Closed)
|
||||
{
|
||||
_writeToLog(L"Connection was closed");
|
||||
OutOfProcContent().Children().Clear();
|
||||
GuidInput().Text(L"");
|
||||
if (piContentProcess.hProcess)
|
||||
{
|
||||
piContentProcess.reset();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Log().Children().Clear();
|
||||
OutOfProcContent().Children().Append(control);
|
||||
|
||||
if (!tryingToAttach)
|
||||
{
|
||||
auto guidStr{ ::Microsoft::Console::Utils::GuidToString(contentGuid) };
|
||||
GuidInput().Text(guidStr);
|
||||
}
|
||||
}
|
||||
|
||||
void MyPage::CloseClicked(const IInspectable& /*sender*/,
|
||||
const WUX::Input::TappedRoutedEventArgs& /*eventArgs*/)
|
||||
{
|
||||
OutOfProcContent().Children().Clear();
|
||||
GuidInput().Text(L"");
|
||||
if (piContentProcess.hProcess)
|
||||
{
|
||||
piContentProcess.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void MyPage::KillClicked(const IInspectable& /*sender*/,
|
||||
const WUX::Input::TappedRoutedEventArgs& /*eventArgs*/)
|
||||
{
|
||||
if (piContentProcess.hProcess)
|
||||
{
|
||||
TerminateProcess(piContentProcess.hProcess, (UINT)-1);
|
||||
piContentProcess.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -14,18 +14,11 @@ namespace winrt::SampleApp::implementation
|
||||
MyPage();
|
||||
|
||||
void Create();
|
||||
hstring Title();
|
||||
|
||||
winrt::fire_and_forget CreateClicked(const IInspectable& sender, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& eventArgs);
|
||||
void CloseClicked(const IInspectable& sender, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& eventArgs);
|
||||
void KillClicked(const IInspectable& sender, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& eventArgs);
|
||||
hstring Title();
|
||||
|
||||
private:
|
||||
friend struct MyPageT<MyPage>; // for Xaml to bind events
|
||||
|
||||
wil::unique_process_information piContentProcess;
|
||||
|
||||
winrt::fire_and_forget _writeToLog(std::wstring_view str);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -23,23 +23,9 @@
|
||||
<TextBox x:Name="GuidInput"
|
||||
Width="400"
|
||||
PlaceholderText="{}{guid here}" />
|
||||
<Button x:Name="CreateOopControl"
|
||||
Grid.Row="0"
|
||||
Tapped="CreateClicked">
|
||||
<Button Grid.Row="0">
|
||||
Create
|
||||
</Button>
|
||||
<Button x:Name="CloseOopControl"
|
||||
Grid.Row="0"
|
||||
Margin="4,0,0,0"
|
||||
Tapped="CloseClicked">
|
||||
Close
|
||||
</Button>
|
||||
<Button x:Name="KillOopControl"
|
||||
Grid.Row="0"
|
||||
Margin="4,0,0,0"
|
||||
Tapped="KillClicked">
|
||||
Kill
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
@@ -60,26 +46,14 @@
|
||||
VerticalAlignment="Stretch"
|
||||
Background="#ff0000" />
|
||||
|
||||
<Grid Grid.Column="1"
|
||||
<Grid x:Name="OutOfProcContent"
|
||||
Grid.Column="1"
|
||||
Padding="16"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
VerticalAlignment="Stretch"
|
||||
Background="#0000ff" />
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel x:Name="Log"
|
||||
Grid.Row="0"
|
||||
Orientation="Vertical" />
|
||||
|
||||
<Grid x:Name="OutOfProcContent"
|
||||
Grid.Row="1"
|
||||
Padding="16"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="#0000ff" />
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -10,9 +10,6 @@ Licensed under the MIT license.
|
||||
#include <conattrs.hpp>
|
||||
#include "MySettings.g.h"
|
||||
|
||||
using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, uint32_t>;
|
||||
using IFontAxesMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, float>;
|
||||
|
||||
namespace winrt::SampleApp::implementation
|
||||
{
|
||||
struct MySettings : MySettingsT<MySettings>
|
||||
@@ -44,8 +41,6 @@ namespace winrt::SampleApp::implementation
|
||||
winrt::Microsoft::Terminal::Core::ICoreAppearance UnfocusedAppearance() { return {}; };
|
||||
|
||||
WINRT_PROPERTY(bool, TrimBlockSelection, false);
|
||||
WINRT_PROPERTY(bool, DetectURLs, true);
|
||||
WINRT_PROPERTY(bool, IntenseIsBright, true);
|
||||
// ------------------------ End of Core Settings -----------------------
|
||||
|
||||
WINRT_PROPERTY(winrt::hstring, ProfileName);
|
||||
@@ -83,10 +78,7 @@ namespace winrt::SampleApp::implementation
|
||||
|
||||
WINRT_PROPERTY(winrt::hstring, PixelShaderPath);
|
||||
|
||||
WINRT_PROPERTY(IFontFeatureMap, FontFeatures);
|
||||
WINRT_PROPERTY(IFontAxesMap, FontAxes);
|
||||
|
||||
WINRT_PROPERTY(bool, IntenseIsBold, true);
|
||||
WINRT_PROPERTY(bool, DetectURLs, true);
|
||||
|
||||
private:
|
||||
std::array<winrt::Microsoft::Terminal::Core::Color, COLOR_TABLE_SIZE> _ColorTable;
|
||||
|
||||
@@ -147,13 +147,13 @@
|
||||
<!-- ========================= Globals ======================== -->
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
|
||||
|
||||
<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.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.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">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('$(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.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.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>
|
||||
|
||||
|
||||
@@ -80,13 +80,13 @@
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<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.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.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">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('$(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.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.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>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.1.3" targetFramework="native" />
|
||||
<package id="Microsoft.UI.Xaml" version="2.6.2-prerelease.210818003" targetFramework="native" />
|
||||
<package id="Microsoft.UI.Xaml" version="2.5.0-prerelease.201202003" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.210309.3" targetFramework="native" />
|
||||
</packages>
|
||||
|
||||
@@ -120,14 +120,14 @@
|
||||
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
|
||||
|
||||
<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.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.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')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.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.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.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.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'))" />
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
<packages>
|
||||
<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.UI.Xaml" version="2.6.2-prerelease.210818003" targetFramework="native" />
|
||||
<package id="Microsoft.UI.Xaml" version="2.5.0-prerelease.201202003" targetFramework="native" />
|
||||
<package id="Microsoft.VCRTForwarders.140" version="1.0.4" targetFramework="native" />
|
||||
</packages>
|
||||
|
||||
@@ -12,6 +12,16 @@
|
||||
<EventProvider Id="EventProvider_TerminalWin32Host" Name="56c06166-2e2e-5f4d-7ff3-74f4b78c87d6" />
|
||||
<EventProvider Id="EventProvider_TerminalRemoting" Name="d6f04aad-629f-539a-77c1-73f5c3e4aa7b" />
|
||||
<EventProvider Id="EventProvider_TerminalDirectX" Name="c93e739e-ae50-5a14-78e7-f171e947535d" />
|
||||
<EventProvider Id="EventProvider_TerminalUIA" Name="e7ebce59-2161-572d-b263-2f16a6afb9e5"/>
|
||||
<!-- Console providers here -->
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Launcher" Name="770aa552-671a-5e97-579b-151709ec0dbd"/>
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Host" Name="fe1ff234-1f09-50a8-d38d-c44fab43e818"/>
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Server" Name="1A541C01-589A-496E-85A7-A9E02170166D"/>
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Console.VirtualTerminal.Parser" Name="c9ba2a84-d3ca-5e19-2bd6-776a0910cb9d"/>
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Render.VtEngine" Name="c9ba2a95-d3ca-5e19-2bd6-776a0910cb9d"/>
|
||||
<EventProvider Id="EventProvider-Microsoft.Windows.Console.UIA" Name="e7ebce59-2161-572d-b263-2f16a6afb9e5"/>
|
||||
|
||||
<!-- Profile for General Terminal logging -->
|
||||
<Profile Id="Terminal.Verbose.File" Name="Terminal" Description="Terminal" LoggingMode="File" DetailLevel="Verbose">
|
||||
<Collectors>
|
||||
<EventCollectorId Value="EventCollector_Terminal">
|
||||
@@ -23,6 +33,7 @@
|
||||
<EventProviderId Value="EventProvider_TerminalWin32Host" />
|
||||
<EventProviderId Value="EventProvider_TerminalRemoting" />
|
||||
<EventProviderId Value="EventProvider_TerminalDirectX" />
|
||||
<EventProviderId Value="EventProvider_TerminalUIA" />
|
||||
</EventProviders>
|
||||
</EventCollectorId>
|
||||
</Collectors>
|
||||
@@ -30,5 +41,27 @@
|
||||
<Profile Id="Terminal.Light.File" Name="Terminal" Description="Terminal" Base="Terminal.Verbose.File" LoggingMode="File" DetailLevel="Light" />
|
||||
<Profile Id="Terminal.Verbose.Memory" Name="Terminal" Description="Terminal" Base="Terminal.Verbose.File" LoggingMode="Memory" DetailLevel="Verbose" />
|
||||
<Profile Id="Terminal.Light.Memory" Name="Terminal" Description="Terminal" Base="Terminal.Verbose.File" LoggingMode="Memory" DetailLevel="Light" />
|
||||
|
||||
<!-- Profile for DefTerm logging. Includes some conhost logging. -->
|
||||
<Profile Id="DefTerm.Verbose.File" Name="DefTerm" Description="DefTerm" LoggingMode="File" DetailLevel="Verbose">
|
||||
<Collectors>
|
||||
<EventCollectorId Value="EventCollector_Terminal">
|
||||
<EventProviders>
|
||||
<EventProviderId Value="EventProvider_TerminalControl" />
|
||||
<EventProviderId Value="EventProvider_TerminalConnection" />
|
||||
<EventProviderId Value="EventProvider_TerminalSettingsModel" />
|
||||
<EventProviderId Value="EventProvider_TerminalApp" />
|
||||
<EventProviderId Value="EventProvider_TerminalWin32Host" />
|
||||
<EventProviderId Value="EventProvider_TerminalRemoting" />
|
||||
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.Launcher" />
|
||||
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.Host" />
|
||||
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.Server" />
|
||||
</EventProviders>
|
||||
</EventCollectorId>
|
||||
</Collectors>
|
||||
</Profile>
|
||||
<Profile Id="DefTerm.Light.File" Name="DefTerm" Description="DefTerm" Base="DefTerm.Verbose.File" LoggingMode="File" DetailLevel="Light" />
|
||||
<Profile Id="DefTerm.Verbose.Memory" Name="DefTerm" Description="DefTerm" Base="DefTerm.Verbose.File" LoggingMode="Memory" DetailLevel="Verbose" />
|
||||
<Profile Id="DefTerm.Light.Memory" Name="DefTerm" Description="DefTerm" Base="DefTerm.Verbose.File" LoggingMode="Memory" DetailLevel="Light" />
|
||||
</Profiles>
|
||||
</WindowsPerformanceRecorder>
|
||||
</WindowsPerformanceRecorder>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "../types/inc/utils.hpp"
|
||||
#include "../types/inc/convert.hpp"
|
||||
#include "../../types/inc/Utf16Parser.hpp"
|
||||
#include "../../types/inc/GlyphWidth.hpp"
|
||||
|
||||
#pragma hdrstop
|
||||
@@ -2535,16 +2536,17 @@ PointTree TextBuffer::GetPatterns(const size_t firstRow, const size_t lastRow) c
|
||||
// match and the previous match, so we use the size of the prefix
|
||||
// along with the size of the match to determine the locations
|
||||
size_t prefixSize = 0;
|
||||
|
||||
for (const auto ch : i->prefix().str())
|
||||
for (const std::vector<wchar_t> parsedGlyph : Utf16Parser::Parse(i->prefix().str()))
|
||||
{
|
||||
prefixSize += IsGlyphFullWidth(ch) ? 2 : 1;
|
||||
const std::wstring_view glyph{ parsedGlyph.data(), parsedGlyph.size() };
|
||||
prefixSize += IsGlyphFullWidth(glyph) ? 2 : 1;
|
||||
}
|
||||
const auto start = lenUpToThis + prefixSize;
|
||||
size_t matchSize = 0;
|
||||
for (const auto ch : i->str())
|
||||
for (const std::vector<wchar_t> parsedGlyph : Utf16Parser::Parse(i->str()))
|
||||
{
|
||||
matchSize += IsGlyphFullWidth(ch) ? 2 : 1;
|
||||
const std::wstring_view glyph{ parsedGlyph.data(), parsedGlyph.size() };
|
||||
matchSize += IsGlyphFullWidth(glyph) ? 2 : 1;
|
||||
}
|
||||
const auto end = start + matchSize;
|
||||
lenUpToThis = end;
|
||||
|
||||
@@ -146,12 +146,12 @@
|
||||
<!-- **END VC LIBS HACK** -->
|
||||
|
||||
<!-- This is required to get the package dependency in the AppXManifest. -->
|
||||
<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.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')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.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.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'))" />
|
||||
</Target>
|
||||
|
||||
<Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" />
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
Enabled="false"
|
||||
DisplayName="ms-resource:AppName" />
|
||||
</uap5:Extension>
|
||||
<!-- <uap3:Extension Category="windows.appExtension">
|
||||
<uap3:Extension Category="windows.appExtension">
|
||||
<uap3:AppExtension Name="com.microsoft.windows.console.host"
|
||||
Id="OpenConsole"
|
||||
DisplayName="OpenConsole"
|
||||
@@ -102,15 +102,15 @@
|
||||
<com:Interface Id="E686C757-9A35-4A1C-B3CE-0BCC8B5C69F4" ProxyStubClsid="3171DE52-6EFA-4AEF-8A9F-D02BD67E7A4F"/>
|
||||
<com:Interface Id="59D55CCE-FC8A-48B4-ACE8-0A9286C6557F" ProxyStubClsid="3171DE52-6EFA-4AEF-8A9F-D02BD67E7A4F"/>
|
||||
</com:ComInterface>
|
||||
</com:Extension> -->
|
||||
</com:Extension>
|
||||
<com:Extension Category="windows.comServer">
|
||||
<com:ComServer>
|
||||
<!-- <com:ExeServer DisplayName="OpenConsole" Executable="OpenConsole.exe">
|
||||
<com:ExeServer DisplayName="OpenConsole" Executable="OpenConsole.exe">
|
||||
<com:Class Id="2EACA947-7F5F-4CFA-BA87-8F7FBEEFBE69"/>
|
||||
</com:ExeServer>
|
||||
<com:ExeServer DisplayName="WindowsTerminal" Executable="WindowsTerminal.exe">
|
||||
<com:Class Id="E12CFF52-A866-4C77-9A90-F570A7AA2C6B"/>
|
||||
</com:ExeServer> -->
|
||||
</com:ExeServer>
|
||||
<com:SurrogateServer DisplayName="WindowsTerminalShellExt">
|
||||
<com:Class Id="9f156763-7844-4dc4-b2b1-901f640f5155" Path="WindowsTerminalShellExt.dll" ThreadingModel="STA"/>
|
||||
</com:SurrogateServer>
|
||||
|
||||
@@ -256,10 +256,14 @@ namespace SettingsModelLocalTests
|
||||
])" };
|
||||
|
||||
// complex command with key chords
|
||||
const std::string actionsString4{ R"([
|
||||
const std::string actionsString4A{ R"([
|
||||
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+c" },
|
||||
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+d" }
|
||||
])" };
|
||||
const std::string actionsString4B{ R"([
|
||||
{ "command": { "action": "findMatch", "direction": "next" }, "keys": "ctrl+shift+s" },
|
||||
{ "command": { "action": "findMatch", "direction": "prev" }, "keys": "ctrl+shift+r" }
|
||||
])" };
|
||||
|
||||
// command with name and icon and multiple key chords
|
||||
const std::string actionsString5{ R"([
|
||||
@@ -372,7 +376,8 @@ namespace SettingsModelLocalTests
|
||||
RoundtripTest<implementation::ActionMap>(actionsString3);
|
||||
|
||||
Log::Comment(L"complex commands with key chords");
|
||||
RoundtripTest<implementation::ActionMap>(actionsString4);
|
||||
RoundtripTest<implementation::ActionMap>(actionsString4A);
|
||||
RoundtripTest<implementation::ActionMap>(actionsString4B);
|
||||
|
||||
Log::Comment(L"command with name and icon and multiple key chords");
|
||||
RoundtripTest<implementation::ActionMap>(actionsString5);
|
||||
|
||||
@@ -98,10 +98,10 @@
|
||||
<!-- From Microsoft.UI.Xaml.targets -->
|
||||
<Native-Platform Condition="'$(Platform)' == 'Win32'">x86</Native-Platform>
|
||||
<Native-Platform Condition="'$(Platform)' != 'Win32'">$(Platform)</Native-Platform>
|
||||
<_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\runtimes\win10-$(Native-Platform)\native\"</_MUXBinRoot>
|
||||
<_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\runtimes\win10-$(Native-Platform)\native\"</_MUXBinRoot>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- We actually can just straight up reference MUX here, it's fine -->
|
||||
<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.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')" />
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -42,6 +42,8 @@ namespace SettingsModelLocalTests
|
||||
|
||||
TEST_METHOD(TestLayerProfileOnColorScheme);
|
||||
|
||||
TEST_METHOD(TestCommandlineToTitlePromotion);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
return true;
|
||||
@@ -63,7 +65,7 @@ namespace SettingsModelLocalTests
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
"profiles": { "list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
@@ -82,6 +84,9 @@ namespace SettingsModelLocalTests
|
||||
"commandline": "wsl.exe"
|
||||
}
|
||||
],
|
||||
"defaults": {
|
||||
"historySize": 29
|
||||
} },
|
||||
"keybindings": [
|
||||
{ "keys": ["ctrl+a"], "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "keys": ["ctrl+b"], "command": { "action": "splitPane", "split": "vertical", "profile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}" } },
|
||||
@@ -217,9 +222,18 @@ namespace SettingsModelLocalTests
|
||||
const auto profile{ settings.GetProfileForArgs(realArgs.TerminalArgs()) };
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr) };
|
||||
const auto termSettings = settingsStruct.DefaultSettings();
|
||||
VERIFY_ARE_EQUAL(guid0, profile.Guid());
|
||||
if constexpr (Feature_ShowProfileDefaultsInSettings::IsEnabled())
|
||||
{
|
||||
// This action specified a command but no profile; it gets reassigned to the base profile
|
||||
VERIFY_ARE_EQUAL(settings.ProfileDefaults(), profile);
|
||||
VERIFY_ARE_EQUAL(29, termSettings.HistorySize());
|
||||
}
|
||||
else
|
||||
{
|
||||
VERIFY_ARE_EQUAL(guid0, profile.Guid());
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline());
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, false, static_cast<int32_t>('F'), 0 };
|
||||
@@ -554,4 +568,85 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 0x45, 0x67, 0x89), terminalSettings4->CursorColor()); // from profile (no color scheme)
|
||||
VERIFY_ARE_EQUAL(DEFAULT_CURSOR_COLOR, terminalSettings5->CursorColor()); // default
|
||||
}
|
||||
|
||||
void TerminalSettingsTests::TestCommandlineToTitlePromotion()
|
||||
{
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": { "list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1,
|
||||
"commandline": "cmd.exe"
|
||||
},
|
||||
],
|
||||
"defaults": {
|
||||
"historySize": 29
|
||||
} }
|
||||
})" };
|
||||
|
||||
const winrt::guid guid0{ ::Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}") };
|
||||
|
||||
CascadiaSettings settings{ til::u8u16(settingsJson) };
|
||||
|
||||
{ // just a profile (profile wins)
|
||||
NewTerminalArgs args{};
|
||||
args.Profile(L"profile0");
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) };
|
||||
VERIFY_ARE_EQUAL(L"profile0", settingsStruct.DefaultSettings().StartingTitle());
|
||||
}
|
||||
{ // profile and command line -> no promotion (profile wins)
|
||||
NewTerminalArgs args{};
|
||||
args.Profile(L"profile0");
|
||||
args.Commandline(L"foo.exe");
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) };
|
||||
VERIFY_ARE_EQUAL(L"profile0", settingsStruct.DefaultSettings().StartingTitle());
|
||||
}
|
||||
{ // just a title -> it is propagated
|
||||
NewTerminalArgs args{};
|
||||
args.TabTitle(L"Analog Kid");
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) };
|
||||
VERIFY_ARE_EQUAL(L"Analog Kid", settingsStruct.DefaultSettings().StartingTitle());
|
||||
}
|
||||
{ // title and command line -> no promotion
|
||||
NewTerminalArgs args{};
|
||||
args.TabTitle(L"Digital Man");
|
||||
args.Commandline(L"foo.exe");
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) };
|
||||
VERIFY_ARE_EQUAL(L"Digital Man", settingsStruct.DefaultSettings().StartingTitle());
|
||||
}
|
||||
{ // just a commandline -> promotion
|
||||
NewTerminalArgs args{};
|
||||
args.Commandline(L"foo.exe");
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) };
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", settingsStruct.DefaultSettings().StartingTitle());
|
||||
}
|
||||
// various typesof commandline follow
|
||||
{
|
||||
NewTerminalArgs args{};
|
||||
args.Commandline(L"foo.exe bar");
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) };
|
||||
VERIFY_ARE_EQUAL(L"foo.exe", settingsStruct.DefaultSettings().StartingTitle());
|
||||
}
|
||||
{
|
||||
NewTerminalArgs args{};
|
||||
args.Commandline(L"\"foo exe.exe\" bar");
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) };
|
||||
VERIFY_ARE_EQUAL(L"foo exe.exe", settingsStruct.DefaultSettings().StartingTitle());
|
||||
}
|
||||
{
|
||||
NewTerminalArgs args{};
|
||||
args.Commandline(L"\"\" grand designs");
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) };
|
||||
VERIFY_ARE_EQUAL(L"", settingsStruct.DefaultSettings().StartingTitle());
|
||||
}
|
||||
{
|
||||
NewTerminalArgs args{};
|
||||
args.Commandline(L" imagine a man");
|
||||
const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(settings, args, nullptr) };
|
||||
VERIFY_ARE_EQUAL(L"", settingsStruct.DefaultSettings().StartingTitle());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,11 +92,11 @@
|
||||
<!-- From Microsoft.UI.Xaml.targets -->
|
||||
<Native-Platform Condition="'$(Platform)' == 'Win32'">x86</Native-Platform>
|
||||
<Native-Platform Condition="'$(Platform)' != 'Win32'">$(Platform)</Native-Platform>
|
||||
<_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.6.2-prerelease.210818003\runtimes\win10-$(Native-Platform)\native\"</_MUXBinRoot>
|
||||
<_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\runtimes\win10-$(Native-Platform)\native\"</_MUXBinRoot>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- We actually can just straight up reference MUX here, it's fine -->
|
||||
<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.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.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>
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<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.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)\src\common.build.post.props" />
|
||||
|
||||
|
||||
@@ -80,8 +80,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
peasant.IdentifyWindowsRequested({ this, &Monarch::_identifyWindows });
|
||||
peasant.RenameRequested({ this, &Monarch::_renameRequested });
|
||||
|
||||
peasant.ShowTrayIconRequested([this](auto&&, auto&&) { _ShowTrayIconRequestedHandlers(*this, nullptr); });
|
||||
peasant.HideTrayIconRequested([this](auto&&, auto&&) { _HideTrayIconRequestedHandlers(*this, nullptr); });
|
||||
peasant.ShowNotificationIconRequested([this](auto&&, auto&&) { _ShowNotificationIconRequestedHandlers(*this, nullptr); });
|
||||
peasant.HideNotificationIconRequested([this](auto&&, auto&&) { _HideNotificationIconRequestedHandlers(*this, nullptr); });
|
||||
|
||||
_peasants[newPeasantsId] = peasant;
|
||||
|
||||
|
||||
@@ -56,8 +56,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> GetPeasantNames();
|
||||
|
||||
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);
|
||||
TYPED_EVENT(ShowNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(HideNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
|
||||
private:
|
||||
uint64_t _ourPID;
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace Microsoft.Terminal.Remoting
|
||||
Windows.Foundation.Collections.IMapView<UInt64, String> GetPeasantNames { get; };
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> HideTrayIconRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> ShowNotificationIconRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> HideNotificationIconRequested;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -226,34 +226,34 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
|
||||
void Peasant::RequestShowTrayIcon()
|
||||
void Peasant::RequestShowNotificationIcon()
|
||||
{
|
||||
try
|
||||
{
|
||||
_ShowTrayIconRequestedHandlers(*this, nullptr);
|
||||
_ShowNotificationIconRequestedHandlers(*this, nullptr);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
}
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Peasant_RequestShowTrayIcon",
|
||||
"Peasant_RequestShowNotificationIcon",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
|
||||
void Peasant::RequestHideTrayIcon()
|
||||
void Peasant::RequestHideNotificationIcon()
|
||||
{
|
||||
try
|
||||
{
|
||||
_HideTrayIconRequestedHandlers(*this, nullptr);
|
||||
_HideNotificationIconRequestedHandlers(*this, nullptr);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
}
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Peasant_RequestHideTrayIcon",
|
||||
"Peasant_RequestHideNotificationIcon",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
void RequestIdentifyWindows();
|
||||
void DisplayWindowId();
|
||||
void RequestRename(const winrt::Microsoft::Terminal::Remoting::RenameRequestArgs& args);
|
||||
void RequestShowTrayIcon();
|
||||
void RequestHideTrayIcon();
|
||||
void RequestShowNotificationIcon();
|
||||
void RequestHideNotificationIcon();
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs GetLastActivatedArgs();
|
||||
|
||||
@@ -42,8 +42,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
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(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);
|
||||
TYPED_EVENT(ShowNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(HideNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
|
||||
private:
|
||||
Peasant(const uint64_t testPID);
|
||||
|
||||
@@ -64,8 +64,8 @@ namespace Microsoft.Terminal.Remoting
|
||||
void RequestIdentifyWindows(); // Tells us to raise a IdentifyWindowsRequested
|
||||
void RequestRename(RenameRequestArgs args); // Tells us to raise a RenameRequested
|
||||
void Summon(SummonWindowBehavior behavior);
|
||||
void RequestShowTrayIcon();
|
||||
void RequestHideTrayIcon();
|
||||
void RequestShowNotificationIcon();
|
||||
void RequestHideNotificationIcon();
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, WindowActivatedArgs> WindowActivated;
|
||||
event Windows.Foundation.TypedEventHandler<Object, CommandlineArgs> ExecuteCommandlineRequested;
|
||||
@@ -73,8 +73,8 @@ namespace Microsoft.Terminal.Remoting
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> DisplayWindowIdRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, RenameRequestArgs> RenameRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, SummonWindowBehavior> SummonRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> HideTrayIconRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> ShowNotificationIconRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> HideNotificationIconRequested;
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass Peasant : IPeasant
|
||||
|
||||
@@ -254,8 +254,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
// window, and when the current monarch dies.
|
||||
|
||||
_monarch.FindTargetWindowRequested({ this, &WindowManager::_raiseFindTargetWindowRequested });
|
||||
_monarch.ShowTrayIconRequested([this](auto&&, auto&&) { _ShowTrayIconRequestedHandlers(*this, nullptr); });
|
||||
_monarch.HideTrayIconRequested([this](auto&&, auto&&) { _HideTrayIconRequestedHandlers(*this, nullptr); });
|
||||
_monarch.ShowNotificationIconRequested([this](auto&&, auto&&) { _ShowNotificationIconRequestedHandlers(*this, nullptr); });
|
||||
_monarch.HideNotificationIconRequested([this](auto&&, auto&&) { _HideNotificationIconRequestedHandlers(*this, nullptr); });
|
||||
|
||||
_BecameMonarchHandlers(*this, nullptr);
|
||||
}
|
||||
@@ -513,7 +513,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
|
||||
void WindowManager::SummonAllWindows()
|
||||
{
|
||||
if constexpr (Feature_TrayIcon::IsEnabled())
|
||||
if constexpr (Feature_NotificationIcon::IsEnabled())
|
||||
{
|
||||
_monarch.SummonAllWindows();
|
||||
}
|
||||
@@ -527,28 +527,28 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Ask the monarch to show a tray icon.
|
||||
// - Ask the monarch to show a notification icon.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
winrt::fire_and_forget WindowManager::RequestShowTrayIcon()
|
||||
winrt::fire_and_forget WindowManager::RequestShowNotificationIcon()
|
||||
{
|
||||
co_await winrt::resume_background();
|
||||
_peasant.RequestShowTrayIcon();
|
||||
_peasant.RequestShowNotificationIcon();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Ask the monarch to hide its tray icon.
|
||||
// - Ask the monarch to hide its notification icon.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
winrt::fire_and_forget WindowManager::RequestHideTrayIcon()
|
||||
winrt::fire_and_forget WindowManager::RequestHideNotificationIcon()
|
||||
{
|
||||
auto strongThis{ get_strong() };
|
||||
co_await winrt::resume_background();
|
||||
_peasant.RequestHideTrayIcon();
|
||||
_peasant.RequestHideNotificationIcon();
|
||||
}
|
||||
|
||||
bool WindowManager::DoesQuakeWindowExist()
|
||||
|
||||
@@ -43,14 +43,14 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
void SummonAllWindows();
|
||||
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> GetPeasantNames();
|
||||
|
||||
winrt::fire_and_forget RequestShowTrayIcon();
|
||||
winrt::fire_and_forget RequestHideTrayIcon();
|
||||
winrt::fire_and_forget RequestShowNotificationIcon();
|
||||
winrt::fire_and_forget RequestHideNotificationIcon();
|
||||
bool DoesQuakeWindowExist();
|
||||
|
||||
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(ShowTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(HideTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(ShowNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(HideNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
|
||||
private:
|
||||
bool _shouldCreateWindow{ false };
|
||||
|
||||
@@ -13,13 +13,13 @@ namespace Microsoft.Terminal.Remoting
|
||||
Boolean IsMonarch { get; };
|
||||
void SummonWindow(SummonWindowSelectionArgs args);
|
||||
void SummonAllWindows();
|
||||
void RequestShowTrayIcon();
|
||||
void RequestHideTrayIcon();
|
||||
void RequestShowNotificationIcon();
|
||||
void RequestHideNotificationIcon();
|
||||
Boolean DoesQuakeWindowExist();
|
||||
Windows.Foundation.Collections.IMapView<UInt64, String> GetPeasantNames();
|
||||
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> BecameMonarch;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> HideTrayIconRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> ShowNotificationIconRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> HideNotificationIconRequested;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<!-- Include the MUX Controls resources -->
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls"
|
||||
ControlsResourcesVersion="Version1" />
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<ResourceDictionary>
|
||||
|
||||
<!--
|
||||
|
||||
@@ -556,7 +556,7 @@ namespace winrt::TerminalApp::implementation
|
||||
auto actions = winrt::single_threaded_vector<ActionAndArgs>(std::move(
|
||||
TerminalPage::ConvertExecuteCommandlineToActions(realArgs)));
|
||||
|
||||
if (_startupActions.Size() != 0)
|
||||
if (actions.Size() != 0)
|
||||
{
|
||||
actionArgs.Handled(true);
|
||||
ProcessStartupActions(actions, false);
|
||||
|
||||
@@ -399,8 +399,10 @@ static const std::map<std::string, FocusDirection> focusDirectionMap = {
|
||||
{ "right", FocusDirection::Right },
|
||||
{ "up", FocusDirection::Up },
|
||||
{ "down", FocusDirection::Down },
|
||||
{ "previous", FocusDirection::Previous },
|
||||
{ "nextInOrder", FocusDirection::NextInOrder },
|
||||
{ "previousInOrder", FocusDirection::PreviousInOrder },
|
||||
{ "first", FocusDirection::First },
|
||||
};
|
||||
|
||||
// Method Description:
|
||||
@@ -603,13 +605,6 @@ NewTerminalArgs AppCommandlineArgs::_getNewTerminalArgs(AppCommandlineArgs::NewT
|
||||
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)
|
||||
{
|
||||
args.StartingDirectory(winrt::to_hstring(_startingDirectory));
|
||||
|
||||
@@ -353,6 +353,8 @@ namespace winrt::TerminalApp::implementation
|
||||
co_return ContentDialogResult::None;
|
||||
}
|
||||
|
||||
_dialog = dialog;
|
||||
|
||||
// IMPORTANT: This is necessary as documented in the ContentDialog MSDN docs.
|
||||
// Since we're hosting the dialog in a Xaml island, we need to connect it to the
|
||||
// xaml tree somehow.
|
||||
@@ -390,6 +392,16 @@ namespace winrt::TerminalApp::implementation
|
||||
// be released so another can be shown
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Dismiss the (only) visible ContentDialog
|
||||
void AppLogic::DismissDialog()
|
||||
{
|
||||
if (auto localDialog = std::exchange(_dialog, nullptr))
|
||||
{
|
||||
localDialog.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Displays a dialog for errors found while loading or validating the
|
||||
// settings. Uses the resources under the provided title and content keys
|
||||
@@ -1442,9 +1454,14 @@ namespace winrt::TerminalApp::implementation
|
||||
return _root->IsQuakeWindow();
|
||||
}
|
||||
|
||||
bool AppLogic::GetMinimizeToTray()
|
||||
void AppLogic::RequestExitFullscreen()
|
||||
{
|
||||
if constexpr (Feature_TrayIcon::IsEnabled())
|
||||
_root->SetFullscreen(false);
|
||||
}
|
||||
|
||||
bool AppLogic::GetMinimizeToNotificationArea()
|
||||
{
|
||||
if constexpr (Feature_NotificationIcon::IsEnabled())
|
||||
{
|
||||
if (!_loadedInitialSettings)
|
||||
{
|
||||
@@ -1452,7 +1469,7 @@ namespace winrt::TerminalApp::implementation
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
return _settings.GlobalSettings().MinimizeToTray();
|
||||
return _settings.GlobalSettings().MinimizeToNotificationArea();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1460,9 +1477,9 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
bool AppLogic::GetAlwaysShowTrayIcon()
|
||||
bool AppLogic::GetAlwaysShowNotificationIcon()
|
||||
{
|
||||
if constexpr (Feature_TrayIcon::IsEnabled())
|
||||
if constexpr (Feature_NotificationIcon::IsEnabled())
|
||||
{
|
||||
if (!_loadedInitialSettings)
|
||||
{
|
||||
@@ -1470,7 +1487,7 @@ namespace winrt::TerminalApp::implementation
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
return _settings.GlobalSettings().AlwaysShowTrayIcon();
|
||||
return _settings.GlobalSettings().AlwaysShowNotificationIcon();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -70,6 +70,7 @@ namespace winrt::TerminalApp::implementation
|
||||
uint64_t WindowId();
|
||||
void WindowId(const uint64_t& id);
|
||||
bool IsQuakeWindow() const noexcept;
|
||||
void RequestExitFullscreen();
|
||||
|
||||
Windows::Foundation::Size GetLaunchDimensions(uint32_t dpi);
|
||||
bool CenterOnLaunch();
|
||||
@@ -92,10 +93,11 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
winrt::TerminalApp::TaskbarState TaskbarState();
|
||||
|
||||
bool GetMinimizeToTray();
|
||||
bool GetAlwaysShowTrayIcon();
|
||||
bool GetMinimizeToNotificationArea();
|
||||
bool GetAlwaysShowNotificationIcon();
|
||||
|
||||
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog);
|
||||
void DismissDialog();
|
||||
|
||||
Windows::Foundation::Collections::IMapView<Microsoft::Terminal::Control::KeyChord, Microsoft::Terminal::Settings::Model::Command> GlobalHotkeys();
|
||||
|
||||
@@ -120,6 +122,7 @@ namespace winrt::TerminalApp::implementation
|
||||
bool _loadedInitialSettings = false;
|
||||
|
||||
std::shared_mutex _dialogLock;
|
||||
winrt::Windows::UI::Xaml::Controls::ContentDialog _dialog;
|
||||
|
||||
::TerminalApp::AppCommandlineArgs _appArgs;
|
||||
::TerminalApp::AppCommandlineArgs _settingsAppArgs;
|
||||
|
||||
@@ -54,6 +54,7 @@ namespace TerminalApp
|
||||
String WindowName;
|
||||
UInt64 WindowId;
|
||||
void RenameFailed();
|
||||
void RequestExitFullscreen();
|
||||
Boolean IsQuakeWindow();
|
||||
|
||||
Windows.Foundation.Size GetLaunchDimensions(UInt32 dpi);
|
||||
@@ -70,8 +71,8 @@ namespace TerminalApp
|
||||
|
||||
TaskbarState TaskbarState{ get; };
|
||||
|
||||
Boolean GetMinimizeToTray();
|
||||
Boolean GetAlwaysShowTrayIcon();
|
||||
Boolean GetMinimizeToNotificationArea();
|
||||
Boolean GetAlwaysShowNotificationIcon();
|
||||
|
||||
FindTargetWindowResult FindTargetWindow(String[] args);
|
||||
|
||||
@@ -80,6 +81,7 @@ namespace TerminalApp
|
||||
// See IDialogPresenter and TerminalPage's DialogPresenter for more
|
||||
// information.
|
||||
Windows.Foundation.IAsyncOperation<Windows.UI.Xaml.Controls.ContentDialogResult> ShowDialog(Windows.UI.Xaml.Controls.ContentDialog dialog);
|
||||
void DismissDialog();
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.UIElement> SetTitleBarContent;
|
||||
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
|
||||
|
||||
@@ -58,8 +58,8 @@ Pane::Pane(const Profile& profile, const TermControl& control, const bool lastFo
|
||||
_root.Background(s_unfocusedBorderBrush);
|
||||
|
||||
// Register an event with the control to have it inform us when it gains focus.
|
||||
_gotFocusRevoker = control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler });
|
||||
_lostFocusRevoker = control.LostFocus(winrt::auto_revoke, { this, &Pane::_ControlLostFocusHandler });
|
||||
_gotFocusRevoker = _control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler });
|
||||
_lostFocusRevoker = _control.LostFocus(winrt::auto_revoke, { this, &Pane::_ControlLostFocusHandler });
|
||||
|
||||
// When our border is tapped, make sure to transfer focus to our control.
|
||||
// LOAD-BEARING: This will NOT work if the border's BorderBrush is set to
|
||||
@@ -251,6 +251,27 @@ std::shared_ptr<Pane> Pane::NavigateDirection(const std::shared_ptr<Pane> source
|
||||
return PreviousPane(sourcePane);
|
||||
}
|
||||
|
||||
if (direction == FocusDirection::First)
|
||||
{
|
||||
std::shared_ptr<Pane> firstPane = nullptr;
|
||||
WalkTree([&](auto p) {
|
||||
if (p->_IsLeaf())
|
||||
{
|
||||
firstPane = p;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
// Don't need to do any movement if we are the source and target pane.
|
||||
if (firstPane == sourcePane)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return firstPane;
|
||||
}
|
||||
|
||||
// We are left with directional traversal now
|
||||
// If the focus direction does not match the split direction, the source pane
|
||||
// and its neighbor must necessarily be contained within the same child.
|
||||
@@ -266,7 +287,10 @@ std::shared_ptr<Pane> Pane::NavigateDirection(const std::shared_ptr<Pane> source
|
||||
// Since the direction is the same as our split, it is possible that we must
|
||||
// move focus from from one child to another child.
|
||||
// We now must keep track of state while we recurse.
|
||||
const auto paneNeighborPair = _FindPaneAndNeighbor(sourcePane, direction, { 0, 0 });
|
||||
// If we have it, get the size of this pane.
|
||||
const auto scaleX = _root.ActualWidth() > 0 ? gsl::narrow_cast<float>(_root.ActualWidth()) : 1.f;
|
||||
const auto scaleY = _root.ActualHeight() > 0 ? gsl::narrow_cast<float>(_root.ActualHeight()) : 1.f;
|
||||
const auto paneNeighborPair = _FindPaneAndNeighbor(sourcePane, direction, { 0, 0, scaleX, scaleY });
|
||||
|
||||
if (paneNeighborPair.source && paneNeighborPair.neighbor)
|
||||
{
|
||||
@@ -525,9 +549,9 @@ bool Pane::SwapPanes(std::shared_ptr<Pane> first, std::shared_ptr<Pane> second)
|
||||
// Return Value:
|
||||
// - true if the two panes are adjacent.
|
||||
bool Pane::_IsAdjacent(const std::shared_ptr<Pane> first,
|
||||
const Pane::PanePoint firstOffset,
|
||||
const PanePoint firstOffset,
|
||||
const std::shared_ptr<Pane> second,
|
||||
const Pane::PanePoint secondOffset,
|
||||
const PanePoint secondOffset,
|
||||
const FocusDirection& direction) const
|
||||
{
|
||||
// Since float equality is tricky (arithmetic is non-associative, commutative),
|
||||
@@ -536,14 +560,36 @@ bool Pane::_IsAdjacent(const std::shared_ptr<Pane> first,
|
||||
return abs(left - right) < 1e-4F;
|
||||
};
|
||||
|
||||
auto getXMax = [](PanePoint offset, std::shared_ptr<Pane> pane) {
|
||||
// If we are past startup panes should have real dimensions
|
||||
if (pane->GetRootElement().ActualWidth() > 0)
|
||||
{
|
||||
return offset.x + gsl::narrow_cast<float>(pane->GetRootElement().ActualWidth());
|
||||
}
|
||||
|
||||
// If we have simulated dimensions we rely on the calculated scale
|
||||
return offset.x + offset.scaleX;
|
||||
};
|
||||
|
||||
auto getYMax = [](PanePoint offset, std::shared_ptr<Pane> pane) {
|
||||
// If we are past startup panes should have real dimensions
|
||||
if (pane->GetRootElement().ActualHeight() > 0)
|
||||
{
|
||||
return offset.y + gsl::narrow_cast<float>(pane->GetRootElement().ActualHeight());
|
||||
}
|
||||
|
||||
// If we have simulated dimensions we rely on the calculated scale
|
||||
return offset.y + offset.scaleY;
|
||||
};
|
||||
|
||||
// When checking containment in a range, the range is half-closed, i.e. [x, x+w).
|
||||
// If the direction is left test that the left side of the first element is
|
||||
// next to the right side of the second element, and that the top left
|
||||
// corner of the first element is within the second element's height
|
||||
if (direction == FocusDirection::Left)
|
||||
{
|
||||
auto sharesBorders = floatEqual(firstOffset.x, secondOffset.x + gsl::narrow_cast<float>(second->GetRootElement().ActualWidth()));
|
||||
auto withinHeight = (firstOffset.y >= secondOffset.y) && (firstOffset.y < secondOffset.y + gsl::narrow_cast<float>(second->GetRootElement().ActualHeight()));
|
||||
auto sharesBorders = floatEqual(firstOffset.x, getXMax(secondOffset, second));
|
||||
auto withinHeight = (firstOffset.y >= secondOffset.y) && (firstOffset.y < getYMax(secondOffset, second));
|
||||
|
||||
return sharesBorders && withinHeight;
|
||||
}
|
||||
@@ -552,8 +598,8 @@ bool Pane::_IsAdjacent(const std::shared_ptr<Pane> first,
|
||||
// corner of the first element is within the second element's height
|
||||
else if (direction == FocusDirection::Right)
|
||||
{
|
||||
auto sharesBorders = floatEqual(firstOffset.x + gsl::narrow_cast<float>(first->GetRootElement().ActualWidth()), secondOffset.x);
|
||||
auto withinHeight = (firstOffset.y >= secondOffset.y) && (firstOffset.y < secondOffset.y + gsl::narrow_cast<float>(second->GetRootElement().ActualHeight()));
|
||||
auto sharesBorders = floatEqual(getXMax(firstOffset, first), secondOffset.x);
|
||||
auto withinHeight = (firstOffset.y >= secondOffset.y) && (firstOffset.y < getYMax(secondOffset, second));
|
||||
|
||||
return sharesBorders && withinHeight;
|
||||
}
|
||||
@@ -562,8 +608,8 @@ bool Pane::_IsAdjacent(const std::shared_ptr<Pane> first,
|
||||
// corner of the first element is within the second element's width
|
||||
else if (direction == FocusDirection::Up)
|
||||
{
|
||||
auto sharesBorders = floatEqual(firstOffset.y, secondOffset.y + gsl::narrow_cast<float>(second->GetRootElement().ActualHeight()));
|
||||
auto withinWidth = (firstOffset.x >= secondOffset.x) && (firstOffset.x < secondOffset.x + gsl::narrow_cast<float>(second->GetRootElement().ActualWidth()));
|
||||
auto sharesBorders = floatEqual(firstOffset.y, getYMax(secondOffset, second));
|
||||
auto withinWidth = (firstOffset.x >= secondOffset.x) && (firstOffset.x < getXMax(secondOffset, second));
|
||||
|
||||
return sharesBorders && withinWidth;
|
||||
}
|
||||
@@ -572,14 +618,78 @@ bool Pane::_IsAdjacent(const std::shared_ptr<Pane> first,
|
||||
// corner of the first element is within the second element's width
|
||||
else if (direction == FocusDirection::Down)
|
||||
{
|
||||
auto sharesBorders = floatEqual(firstOffset.y + gsl::narrow_cast<float>(first->GetRootElement().ActualHeight()), secondOffset.y);
|
||||
auto withinWidth = (firstOffset.x >= secondOffset.x) && (firstOffset.x < secondOffset.x + gsl::narrow_cast<float>(second->GetRootElement().ActualWidth()));
|
||||
auto sharesBorders = floatEqual(getYMax(firstOffset, first), secondOffset.y);
|
||||
auto withinWidth = (firstOffset.x >= secondOffset.x) && (firstOffset.x < getXMax(secondOffset, second));
|
||||
|
||||
return sharesBorders && withinWidth;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Gets the offsets for the two children of this parent pane
|
||||
// - If real dimensions are not available, simulated ones based on the split size
|
||||
// will be used instead.
|
||||
// Arguments:
|
||||
// - parentOffset the location and scale information of this pane.
|
||||
// Return Value:
|
||||
// - the two location/scale points for the children panes.
|
||||
std::pair<Pane::PanePoint, Pane::PanePoint> Pane::_GetOffsetsForPane(const PanePoint parentOffset) const
|
||||
{
|
||||
assert(!_IsLeaf());
|
||||
auto firstOffset = parentOffset;
|
||||
auto secondOffset = parentOffset;
|
||||
|
||||
// When panes are initialized they don't have dimensions yet.
|
||||
if (_firstChild->GetRootElement().ActualHeight() > 0)
|
||||
{
|
||||
// The second child has an offset depending on the split
|
||||
if (_splitState == SplitState::Horizontal)
|
||||
{
|
||||
const auto diff = gsl::narrow_cast<float>(_firstChild->GetRootElement().ActualHeight());
|
||||
secondOffset.y += diff;
|
||||
// However, if a command is run in an existing window that opens multiple new panes
|
||||
// the parent will have a size (triggering this) and then the children will go
|
||||
// to the other branch.
|
||||
firstOffset.scaleY = diff;
|
||||
secondOffset.scaleY = parentOffset.scaleY - diff;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto diff = gsl::narrow_cast<float>(_firstChild->GetRootElement().ActualWidth());
|
||||
secondOffset.x += diff;
|
||||
firstOffset.scaleX = diff;
|
||||
secondOffset.scaleX = parentOffset.scaleX - diff;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Since we don't have real dimensions make up fake ones using an
|
||||
// exponential layout. Basically create the tree layout on the fly by
|
||||
// partitioning [0,1].
|
||||
// This could run into issues if the tree depth is >127 (or other
|
||||
// degenerate splits) as a float's mantissa only has so many bits of
|
||||
// precision.
|
||||
|
||||
// In theory this could always be used, but there might be edge cases
|
||||
// where using the actual sizing information provides a better result.
|
||||
if (_splitState == SplitState::Horizontal)
|
||||
{
|
||||
secondOffset.y += (1 - _desiredSplitPosition) * parentOffset.scaleY;
|
||||
firstOffset.scaleY *= _desiredSplitPosition;
|
||||
secondOffset.scaleY *= (1 - _desiredSplitPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
secondOffset.x += (1 - _desiredSplitPosition) * parentOffset.scaleX;
|
||||
firstOffset.scaleX *= _desiredSplitPosition;
|
||||
secondOffset.scaleX *= (1 - _desiredSplitPosition);
|
||||
}
|
||||
}
|
||||
|
||||
return { firstOffset, secondOffset };
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Given the source pane, and its relative position in the tree, attempt to
|
||||
// find its visual neighbor within the current pane's tree.
|
||||
@@ -618,17 +728,7 @@ Pane::PaneNeighborSearch Pane::_FindNeighborForPane(const FocusDirection& direct
|
||||
return searchResult;
|
||||
}
|
||||
|
||||
auto firstOffset = offset;
|
||||
auto secondOffset = offset;
|
||||
// The second child has an offset depending on the split
|
||||
if (_splitState == SplitState::Horizontal)
|
||||
{
|
||||
secondOffset.y += gsl::narrow_cast<float>(_firstChild->GetRootElement().ActualHeight());
|
||||
}
|
||||
else
|
||||
{
|
||||
secondOffset.x += gsl::narrow_cast<float>(_firstChild->GetRootElement().ActualWidth());
|
||||
}
|
||||
auto [firstOffset, secondOffset] = _GetOffsetsForPane(offset);
|
||||
auto sourceNeighborSearch = _firstChild->_FindNeighborForPane(direction, searchResult, sourceIsSecondSide, firstOffset);
|
||||
if (sourceNeighborSearch.neighbor)
|
||||
{
|
||||
@@ -661,18 +761,7 @@ Pane::PaneNeighborSearch Pane::_FindPaneAndNeighbor(const std::shared_ptr<Pane>
|
||||
return { nullptr, nullptr, offset };
|
||||
}
|
||||
|
||||
// Search the first child, which has no offset from the parent pane
|
||||
auto firstOffset = offset;
|
||||
auto secondOffset = offset;
|
||||
// The second child has an offset depending on the split
|
||||
if (_splitState == SplitState::Horizontal)
|
||||
{
|
||||
secondOffset.y += gsl::narrow_cast<float>(_firstChild->GetRootElement().ActualHeight());
|
||||
}
|
||||
else
|
||||
{
|
||||
secondOffset.x += gsl::narrow_cast<float>(_firstChild->GetRootElement().ActualWidth());
|
||||
}
|
||||
auto [firstOffset, secondOffset] = _GetOffsetsForPane(offset);
|
||||
|
||||
auto sourceNeighborSearch = _firstChild->_FindPaneAndNeighbor(sourcePane, direction, firstOffset);
|
||||
// If we have both the focus element and its neighbor, we are done
|
||||
@@ -1134,7 +1223,7 @@ std::shared_ptr<Pane> Pane::DetachPane(std::shared_ptr<Pane> pane)
|
||||
auto detached = isFirstChild ? _firstChild : _secondChild;
|
||||
// Remove the child from the tree, replace the current node with the
|
||||
// other child.
|
||||
_CloseChild(isFirstChild);
|
||||
_CloseChild(isFirstChild, true);
|
||||
|
||||
detached->_borders = Borders::None;
|
||||
detached->_UpdateBorders();
|
||||
@@ -1162,9 +1251,12 @@ std::shared_ptr<Pane> Pane::DetachPane(std::shared_ptr<Pane> pane)
|
||||
// Arguments:
|
||||
// - closeFirst: if true, the first child should be closed, and the second
|
||||
// should be preserved, and vice-versa for false.
|
||||
// - isDetaching: if true, then the pane event handlers for the closed child
|
||||
// should be kept, this way they don't have to be recreated when it is later
|
||||
// reattached to a tree somewhere as the control moves with the pane.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Pane::_CloseChild(const bool closeFirst)
|
||||
void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
|
||||
{
|
||||
// Lock the create/close lock so that another operation won't concurrently
|
||||
// modify our tree
|
||||
@@ -1182,6 +1274,8 @@ void Pane::_CloseChild(const bool closeFirst)
|
||||
|
||||
auto closedChild = closeFirst ? _firstChild : _secondChild;
|
||||
auto remainingChild = closeFirst ? _secondChild : _firstChild;
|
||||
auto closedChildClosedToken = closeFirst ? _firstClosedToken : _secondClosedToken;
|
||||
auto remainingChildClosedToken = closeFirst ? _secondClosedToken : _firstClosedToken;
|
||||
|
||||
// If the only child left is a leaf, that means we're a leaf now.
|
||||
if (remainingChild->_IsLeaf())
|
||||
@@ -1207,11 +1301,18 @@ void Pane::_CloseChild(const bool closeFirst)
|
||||
// themselves closing, and remove their handlers for their controls
|
||||
// closing. At this point, if the remaining child's control is closed,
|
||||
// they'll trigger only our event handler for the control's close.
|
||||
_firstChild->Closed(_firstClosedToken);
|
||||
_secondChild->Closed(_secondClosedToken);
|
||||
closedChild->_control.ConnectionStateChanged(closedChild->_connectionStateChangedToken);
|
||||
|
||||
// However, if we are detaching the pane we want to keep its control
|
||||
// handlers since it is just getting moved.
|
||||
if (!isDetaching)
|
||||
{
|
||||
closedChild->_control.ConnectionStateChanged(closedChild->_connectionStateChangedToken);
|
||||
closedChild->_control.WarningBell(closedChild->_warningBellToken);
|
||||
}
|
||||
|
||||
closedChild->Closed(closedChildClosedToken);
|
||||
remainingChild->Closed(remainingChildClosedToken);
|
||||
remainingChild->_control.ConnectionStateChanged(remainingChild->_connectionStateChangedToken);
|
||||
closedChild->_control.WarningBell(closedChild->_warningBellToken);
|
||||
remainingChild->_control.WarningBell(remainingChild->_warningBellToken);
|
||||
|
||||
// If either of our children was focused, we want to take that focus from
|
||||
@@ -1271,12 +1372,6 @@ void Pane::_CloseChild(const bool closeFirst)
|
||||
// Find what borders need to persist after we close the child
|
||||
auto remainingBorders = _GetCommonBorders();
|
||||
|
||||
// First stash away references to the old panes and their tokens
|
||||
const auto oldFirstToken = _firstClosedToken;
|
||||
const auto oldSecondToken = _secondClosedToken;
|
||||
const auto oldFirst = _firstChild;
|
||||
const auto oldSecond = _secondChild;
|
||||
|
||||
// Steal all the state from our child
|
||||
_splitState = remainingChild->_splitState;
|
||||
_firstChild = remainingChild->_firstChild;
|
||||
@@ -1289,11 +1384,14 @@ void Pane::_CloseChild(const bool closeFirst)
|
||||
_firstChild->Closed(remainingChild->_firstClosedToken);
|
||||
_secondChild->Closed(remainingChild->_secondClosedToken);
|
||||
|
||||
// Revoke event handlers on old panes and controls
|
||||
oldFirst->Closed(oldFirstToken);
|
||||
oldSecond->Closed(oldSecondToken);
|
||||
closedChild->_control.ConnectionStateChanged(closedChild->_connectionStateChangedToken);
|
||||
closedChild->_control.WarningBell(closedChild->_warningBellToken);
|
||||
// Remove the event handlers on the old children
|
||||
remainingChild->Closed(remainingChildClosedToken);
|
||||
closedChild->Closed(closedChildClosedToken);
|
||||
if (!isDetaching)
|
||||
{
|
||||
closedChild->_control.ConnectionStateChanged(closedChild->_connectionStateChangedToken);
|
||||
closedChild->_control.WarningBell(closedChild->_warningBellToken);
|
||||
}
|
||||
|
||||
// Reset our UI:
|
||||
_root.Children().Clear();
|
||||
@@ -1364,7 +1462,7 @@ winrt::fire_and_forget Pane::_CloseChildRoutine(const bool closeFirst)
|
||||
// this one doesn't seem to.
|
||||
if (!animationsEnabledInOS || !animationsEnabledInApp || eitherChildZoomed)
|
||||
{
|
||||
pane->_CloseChild(closeFirst);
|
||||
pane->_CloseChild(closeFirst, false);
|
||||
co_return;
|
||||
}
|
||||
|
||||
@@ -1471,7 +1569,7 @@ winrt::fire_and_forget Pane::_CloseChildRoutine(const bool closeFirst)
|
||||
{
|
||||
// We don't need to manually undo any of the above trickiness.
|
||||
// We're going to re-parent the child's content into us anyways
|
||||
pane->_CloseChild(closeFirst);
|
||||
pane->_CloseChild(closeFirst, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -200,6 +200,7 @@ private:
|
||||
bool _Resize(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
|
||||
|
||||
std::shared_ptr<Pane> _FindParentOfPane(const std::shared_ptr<Pane> pane);
|
||||
std::pair<PanePoint, PanePoint> _GetOffsetsForPane(const PanePoint parentOffset) const;
|
||||
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,
|
||||
@@ -209,7 +210,7 @@ private:
|
||||
const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction,
|
||||
const PanePoint offset);
|
||||
|
||||
void _CloseChild(const bool closeFirst);
|
||||
void _CloseChild(const bool closeFirst, const bool isDetaching);
|
||||
winrt::fire_and_forget _CloseChildRoutine(const bool closeFirst);
|
||||
|
||||
void _FocusFirstChild();
|
||||
@@ -225,7 +226,6 @@ private:
|
||||
SnapChildrenSizeResult _CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const;
|
||||
SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
|
||||
void _AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const;
|
||||
|
||||
winrt::Windows::Foundation::Size _GetMinSize() const;
|
||||
LayoutSizeNode _CreateMinSizeTree(const bool widthOrHeight) const;
|
||||
float _ClampSplitPosition(const bool widthOrHeight, const float requestedValue, const float totalSize) const;
|
||||
@@ -274,6 +274,8 @@ private:
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
float scaleX;
|
||||
float scaleY;
|
||||
};
|
||||
|
||||
struct PaneNeighborSearch
|
||||
|
||||
@@ -653,11 +653,11 @@
|
||||
<data name="CommandPaletteMenuItem" xml:space="preserve">
|
||||
<value>Command Palette</value>
|
||||
</data>
|
||||
<data name="TrayIconFocusTerminal" xml:space="preserve">
|
||||
<data name="NotificationIconFocusTerminal" 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">
|
||||
<data name="NotificationIconWindowSubmenu" 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>
|
||||
@@ -685,4 +685,14 @@
|
||||
<data name="DropPathTabSplit.Text" xml:space="preserve">
|
||||
<value>Split the window and start in given directory</value>
|
||||
</data>
|
||||
<data name="SetAsDefaultInfoBar.Message" xml:space="preserve">
|
||||
<value>Windows Terminal can be set as the default terminal application in your settings.</value>
|
||||
</data>
|
||||
<data name="InfoBarDismissButton.Content" xml:space="preserve">
|
||||
<value>Don't show again</value>
|
||||
</data>
|
||||
<data name="SetAsDefaultTip_OpenSettingsLink.Content" xml:space="preserve">
|
||||
<value>Open Settings</value>
|
||||
<comment>This is a call-to-action hyperlink; it will open the settings.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -56,42 +56,39 @@ namespace winrt::TerminalApp::implementation
|
||||
// - existingConnection: An optional connection that is already established to a PTY
|
||||
// for this tab to host instead of creating one.
|
||||
// If not defined, the tab will create the connection.
|
||||
HRESULT TerminalPage::_OpenNewTab(const NewTerminalArgs& newTerminalArgs,
|
||||
TerminalConnection::ITerminalConnection /*existingConnection*/)
|
||||
HRESULT TerminalPage::_OpenNewTab(const NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection)
|
||||
try
|
||||
{
|
||||
const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) };
|
||||
const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings) };
|
||||
|
||||
// TODO! Handle defterm connections here (and above)
|
||||
// _CreateNewTabWithProfileAndSettings(profile, settings, existingConnection);
|
||||
_CreateTabWithContent(profile, settings, nullptr);
|
||||
_CreateNewTabWithProfileAndSettings(profile, settings, existingConnection);
|
||||
|
||||
// const uint32_t tabCount = _tabs.Size();
|
||||
// const bool usedManualProfile = (newTerminalArgs != nullptr) &&
|
||||
// (newTerminalArgs.ProfileIndex() != nullptr ||
|
||||
// newTerminalArgs.Profile().empty());
|
||||
const uint32_t tabCount = _tabs.Size();
|
||||
const bool usedManualProfile = (newTerminalArgs != nullptr) &&
|
||||
(newTerminalArgs.ProfileIndex() != nullptr ||
|
||||
newTerminalArgs.Profile().empty());
|
||||
|
||||
// // Lookup the name of the color scheme used by this profile.
|
||||
// const auto scheme = _settings.GetColorSchemeForProfile(profile);
|
||||
// // If they explicitly specified `null` as the scheme (indicating _no_ scheme), log
|
||||
// // that as the empty string.
|
||||
// const auto schemeName = scheme ? scheme.Name() : L"\0";
|
||||
// Lookup the name of the color scheme used by this profile.
|
||||
const auto scheme = _settings.GetColorSchemeForProfile(profile);
|
||||
// If they explicitly specified `null` as the scheme (indicating _no_ scheme), log
|
||||
// that as the empty string.
|
||||
const auto schemeName = scheme ? scheme.Name() : L"\0";
|
||||
|
||||
// TraceLoggingWrite(
|
||||
// g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
|
||||
// "TabInformation",
|
||||
// TraceLoggingDescription("Event emitted upon new tab creation in TerminalApp"),
|
||||
// TraceLoggingUInt32(1u, "EventVer", "Version of this event"),
|
||||
// TraceLoggingUInt32(tabCount, "TabCount", "Count of tabs currently opened in TerminalApp"),
|
||||
// TraceLoggingBool(usedManualProfile, "ProfileSpecified", "Whether the new tab specified a profile explicitly"),
|
||||
// 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"),
|
||||
// TraceLoggingFloat64(settings.DefaultSettings().TintOpacity(), "TintOpacity", "Opacity preference from 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"),
|
||||
// TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
// TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
|
||||
"TabInformation",
|
||||
TraceLoggingDescription("Event emitted upon new tab creation in TerminalApp"),
|
||||
TraceLoggingUInt32(1u, "EventVer", "Version of this event"),
|
||||
TraceLoggingUInt32(tabCount, "TabCount", "Count of tabs currently opened in TerminalApp"),
|
||||
TraceLoggingBool(usedManualProfile, "ProfileSpecified", "Whether the new tab specified a profile explicitly"),
|
||||
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"),
|
||||
TraceLoggingFloat64(settings.DefaultSettings().TintOpacity(), "TintOpacity", "Opacity preference from 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"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
@@ -231,129 +228,6 @@ namespace winrt::TerminalApp::implementation
|
||||
_InitializeTab(newTabImpl);
|
||||
}
|
||||
|
||||
static wil::unique_process_information _createHostClassProcess(const winrt::guid& g)
|
||||
{
|
||||
auto guidStr{ ::Microsoft::Console::Utils::GuidToString(g) };
|
||||
|
||||
// Create an event that the content process will use to signal it is
|
||||
// ready to go. We won't need the event after this function, so the
|
||||
// unique_event will clean up our handle when we leave this scope. The
|
||||
// ContentProcess is responsible for cleaning up its own handle.
|
||||
wil::unique_event ev{ CreateEvent(nullptr, true, false, nullptr /*L"contentProcessStarted"*/) };
|
||||
// Make sure to mark this handle as inheritable! Even with
|
||||
// bInheritHandles=true, this is only inherited when it's explicitly
|
||||
// allowed to be.
|
||||
SetHandleInformation(ev.get(), HANDLE_FLAG_INHERIT, 1);
|
||||
|
||||
// god bless, fmt::format will format a HANDLE like `0xa80`
|
||||
std::wstring commandline{
|
||||
fmt::format(L"windowsterminal.exe --content {} --signal {}", guidStr, ev.get())
|
||||
};
|
||||
|
||||
STARTUPINFO siOne{ 0 };
|
||||
siOne.cb = sizeof(STARTUPINFOW);
|
||||
wil::unique_process_information piOne;
|
||||
auto succeeded = CreateProcessW(
|
||||
nullptr,
|
||||
commandline.data(),
|
||||
nullptr, // lpProcessAttributes
|
||||
nullptr, // lpThreadAttributes
|
||||
true, // bInheritHandles
|
||||
CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags
|
||||
nullptr, // lpEnvironment
|
||||
nullptr, // startingDirectory
|
||||
&siOne, // lpStartupInfo
|
||||
&piOne // lpProcessInformation
|
||||
);
|
||||
THROW_IF_WIN32_BOOL_FALSE(succeeded);
|
||||
|
||||
// Wait for the child process to signal that they're ready.
|
||||
WaitForSingleObject(ev.get(), INFINITE);
|
||||
|
||||
return std::move(piOne);
|
||||
}
|
||||
|
||||
Windows::Foundation::IAsyncOperation<ContentProcess> TerminalPage::_CreateNewContentProcess(Profile profile,
|
||||
TerminalSettingsCreateResult settings)
|
||||
{
|
||||
co_await winrt::resume_background();
|
||||
winrt::guid contentGuid{ ::Microsoft::Console::Utils::CreateGuid() };
|
||||
// Spawn a wt.exe, with the guid on the commandline
|
||||
auto piContentProcess{ _createHostClassProcess(contentGuid) };
|
||||
// THIS MUST TAKE PLACE AFTER _createHostClassProcess.
|
||||
// * If we're creating a new OOP control, _createHostClassProcess will
|
||||
// spawn the process that will actually host the ContentProcess
|
||||
// object.
|
||||
// * If we're attaching, then that process already exists.
|
||||
ContentProcess content{ nullptr };
|
||||
try
|
||||
{
|
||||
content = create_instance<ContentProcess>(contentGuid, CLSCTX_LOCAL_SERVER);
|
||||
}
|
||||
catch (winrt::hresult_error hr)
|
||||
{
|
||||
// _writeToLog(L"CreateInstance the ContentProces object");
|
||||
// _writeToLog(fmt::format(L" HR ({}): {}", hr.code(), hr.message().c_str()));
|
||||
co_return nullptr; // be sure to co_return or we'll fall through to the part where we clear the log
|
||||
}
|
||||
|
||||
if (content == nullptr)
|
||||
{
|
||||
// _writeToLog(L"Failed to connect to the ContentProces object. It may not have been started fast enough.");
|
||||
co_return nullptr; // be sure to co_return or we'll fall through to the part where we clear the log
|
||||
}
|
||||
|
||||
TerminalConnection::ConnectionInformation connectInfo{ _CreateConnectionInfoFromSettings(profile, settings.DefaultSettings()) };
|
||||
// TODO! how do we init the content proc with the focused/unfocused pair correctly?
|
||||
if (!content.Initialize(settings.DefaultSettings(), connectInfo))
|
||||
{
|
||||
// _writeToLog(L"Failed to Initialize the ContentProces object.");
|
||||
co_return nullptr; // be sure to co_return or we'll fall through to the part where we clear the log
|
||||
}
|
||||
|
||||
co_return content;
|
||||
}
|
||||
|
||||
ContentProcess TerminalPage::_AttachToContentProcess(const winrt::guid contentGuid)
|
||||
{
|
||||
ContentProcess content{ nullptr };
|
||||
try
|
||||
{
|
||||
content = create_instance<ContentProcess>(contentGuid, CLSCTX_LOCAL_SERVER);
|
||||
}
|
||||
catch (winrt::hresult_error hr)
|
||||
{
|
||||
// _writeToLog(L"CreateInstance the ContentProces object");
|
||||
// _writeToLog(fmt::format(L" HR ({}): {}", hr.code(), hr.message().c_str()));
|
||||
// return nullptr; // be sure to co_return or we'll fall through to the part where we clear the log
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
winrt::fire_and_forget TerminalPage::_CreateTabWithContent(Profile profile,
|
||||
TerminalSettingsCreateResult settings,
|
||||
ContentProcess existingContentProc)
|
||||
{
|
||||
// Capture calling context.
|
||||
winrt::apartment_context ui_thread;
|
||||
co_await winrt::resume_background();
|
||||
auto content = existingContentProc ? existingContentProc : _CreateNewContentProcess(profile, settings).get();
|
||||
if (!content)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
|
||||
// Switch back to the UI thread.
|
||||
co_await ui_thread;
|
||||
// Create the XAML control that will be attached to the content process.
|
||||
// We're not passing in a connection, because the contentGuid will be used instead.
|
||||
auto term = _InitControl(settings, content.Guid());
|
||||
auto newTabImpl = winrt::make_self<TerminalTab>(profile, term);
|
||||
_RegisterTerminalEvents(term);
|
||||
_InitializeTab(newTabImpl);
|
||||
co_return;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Creates a new tab with the given settings. If the tab bar is not being
|
||||
// currently displayed, it will be shown.
|
||||
@@ -361,9 +235,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// - 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)
|
||||
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.
|
||||
@@ -478,8 +350,10 @@ namespace winrt::TerminalApp::implementation
|
||||
// current control's live settings (which will include changes
|
||||
// made through VT).
|
||||
|
||||
if (const auto profile = tab.GetFocusedProfile())
|
||||
if (auto profile = tab.GetFocusedProfile())
|
||||
{
|
||||
// TODO GH#5047 If we cache the NewTerminalArgs, we no longer need to do this.
|
||||
profile = GetClosestProfileForDuplicationOfProfile(profile);
|
||||
const auto settingsCreateResult{ TerminalSettings::CreateWithProfile(_settings, profile, *_bindings) };
|
||||
const auto workingDirectory = tab.GetActiveTerminalControl().WorkingDirectory();
|
||||
const auto validWorkingDirectory = !workingDirectory.empty();
|
||||
@@ -488,20 +362,16 @@ namespace winrt::TerminalApp::implementation
|
||||
settingsCreateResult.DefaultSettings().StartingDirectory(workingDirectory);
|
||||
}
|
||||
|
||||
_CreateTabWithContent(profile, settingsCreateResult, nullptr);
|
||||
_CreateNewTabWithProfileAndSettings(profile, settingsCreateResult);
|
||||
|
||||
// TODO! duplicating a tab should dup the tab title over, but
|
||||
// _CreateTabWithContent will return before the tab actually
|
||||
// exists.
|
||||
//
|
||||
// const auto runtimeTabText{ tab.GetTabText() };
|
||||
// if (!runtimeTabText.empty())
|
||||
// {
|
||||
// if (auto newTab{ _GetFocusedTabImpl() })
|
||||
// {
|
||||
// newTab->SetTabText(runtimeTabText);
|
||||
// }
|
||||
// }
|
||||
const auto runtimeTabText{ tab.GetTabText() };
|
||||
if (!runtimeTabText.empty())
|
||||
{
|
||||
if (auto newTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
newTab->SetTabText(runtimeTabText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
@@ -380,13 +380,13 @@
|
||||
<!-- ========================= Globals ======================== -->
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
|
||||
|
||||
<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.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.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">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('$(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.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.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>
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "../inc/WindowingBehavior.h"
|
||||
|
||||
#include <til/latch.h>
|
||||
#include <TerminalCore/ControlKeyStates.hpp>
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Windows::Foundation::Collections;
|
||||
@@ -35,6 +36,7 @@ using namespace winrt::Microsoft::Terminal::TerminalConnection;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace ::TerminalApp;
|
||||
using namespace ::Microsoft::Console;
|
||||
using namespace ::Microsoft::Terminal::Core;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
#define HOOKUP_ACTION(action) _actionDispatch->action({ this, &TerminalPage::_Handle##action });
|
||||
@@ -274,6 +276,8 @@ namespace winrt::TerminalApp::implementation
|
||||
_defaultPointerCursor = CoreWindow::GetForCurrentThread().PointerCursor();
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
ShowSetAsDefaultInfoBar();
|
||||
}
|
||||
|
||||
winrt::fire_and_forget TerminalPage::NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e)
|
||||
@@ -461,11 +465,9 @@ namespace winrt::TerminalApp::implementation
|
||||
// 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.
|
||||
if (auto activeControl{ _GetActiveControl() })
|
||||
if (const auto control = _GetActiveControl())
|
||||
{
|
||||
// TODO! this doesn't work during startup anymore now that
|
||||
// tabs are made on a BG thread
|
||||
activeControl.Focus(FocusState::Programmatic);
|
||||
control.Focus(FocusState::Programmatic);
|
||||
}
|
||||
}
|
||||
if (initial)
|
||||
@@ -816,12 +818,6 @@ namespace winrt::TerminalApp::implementation
|
||||
TerminalConnection::ITerminalConnection TerminalPage::_CreateConnectionFromSettings(Profile profile,
|
||||
TerminalSettings settings)
|
||||
{
|
||||
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 };
|
||||
|
||||
winrt::guid connectionType = profile.ConnectionType();
|
||||
@@ -903,103 +899,6 @@ namespace winrt::TerminalApp::implementation
|
||||
return connection;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Creates a new connection based on the profile settings
|
||||
// Arguments:
|
||||
// - the profile we want the settings from
|
||||
// - the terminal settings
|
||||
// Return value:
|
||||
// - the desired connection
|
||||
TerminalConnection::ConnectionInformation TerminalPage::_CreateConnectionInfoFromSettings(Profile profile,
|
||||
const TerminalSettings& settings)
|
||||
{
|
||||
if (!profile)
|
||||
{
|
||||
// Use the default profile if we didn't get one as an argument.
|
||||
profile = _settings.FindProfile(_settings.GlobalSettings().DefaultProfile());
|
||||
}
|
||||
|
||||
winrt::guid connectionType = profile.ConnectionType();
|
||||
winrt::guid sessionGuid{};
|
||||
|
||||
Windows::Foundation::Collections::ValueSet connectionSettings{ nullptr };
|
||||
winrt::hstring className;
|
||||
|
||||
if (connectionType == TerminalConnection::AzureConnection::ConnectionType() &&
|
||||
TerminalConnection::AzureConnection::IsAzureConnectionAvailable())
|
||||
{
|
||||
// TODO GH#4661: Replace this with directly using the AzCon when our VT is better
|
||||
std::filesystem::path azBridgePath{ wil::GetModuleFileNameW<std::wstring>(nullptr) };
|
||||
azBridgePath.replace_filename(L"TerminalAzBridge.exe");
|
||||
className = winrt::name_of<TerminalConnection::ConptyConnection>();
|
||||
connectionSettings = TerminalConnection::ConptyConnection::CreateSettings(azBridgePath.wstring(),
|
||||
L".",
|
||||
L"Azure",
|
||||
nullptr,
|
||||
::base::saturated_cast<uint32_t>(settings.InitialRows()),
|
||||
::base::saturated_cast<uint32_t>(settings.InitialCols()),
|
||||
winrt::guid());
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// profile is guaranteed to exist here
|
||||
std::wstring guidWString = Utils::GuidToString(profile.Guid());
|
||||
|
||||
StringMap envMap{};
|
||||
envMap.Insert(L"WT_PROFILE_ID", guidWString);
|
||||
envMap.Insert(L"WSLENV", L"WT_PROFILE_ID");
|
||||
|
||||
// Update the path to be relative to whatever our CWD is.
|
||||
//
|
||||
// Refer to the examples in
|
||||
// https://en.cppreference.com/w/cpp/filesystem/path/append
|
||||
//
|
||||
// We need to do this here, to ensure we tell the ConptyConnection
|
||||
// the correct starting path. If we're being invoked from another
|
||||
// terminal instance (e.g. wt -w 0 -d .), then we have switched our
|
||||
// CWD to the provided path. We should treat the StartingDirectory
|
||||
// as relative to the current CWD.
|
||||
//
|
||||
// The connection must be informed of the current CWD on
|
||||
// construction, because the connection might not spawn the child
|
||||
// process until later, on another thread, after we've already
|
||||
// restored the CWD to it's original value.
|
||||
winrt::hstring newWorkingDirectory{ settings.StartingDirectory() };
|
||||
if (newWorkingDirectory.size() <= 1 ||
|
||||
!(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() };
|
||||
}
|
||||
|
||||
className = winrt::name_of<TerminalConnection::ConptyConnection>();
|
||||
connectionSettings = TerminalConnection::ConptyConnection::CreateSettings(settings.Commandline(),
|
||||
newWorkingDirectory,
|
||||
settings.StartingTitle(),
|
||||
envMap.GetView(),
|
||||
::base::saturated_cast<uint32_t>(settings.InitialRows()),
|
||||
::base::saturated_cast<uint32_t>(settings.InitialCols()),
|
||||
winrt::guid());
|
||||
|
||||
// sessionGuid = conhostConn.Guid();
|
||||
}
|
||||
|
||||
// TraceLoggingWrite(
|
||||
// g_hTerminalAppProvider,
|
||||
// "ConnectionCreated",
|
||||
// TraceLoggingDescription("Event emitted upon the creation of a connection"),
|
||||
// TraceLoggingGuid(connectionType, "ConnectionTypeGuid", "The type of the connection"),
|
||||
// TraceLoggingGuid(profile.Guid(), "ProfileGuid", "The profile's GUID"),
|
||||
// TraceLoggingGuid(sessionGuid, "SessionGuid", "The WT_SESSION's GUID"),
|
||||
// TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
// TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
|
||||
return TerminalConnection::ConnectionInformation(className, connectionSettings);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Called when the settings button is clicked. Launches a background
|
||||
// thread to open the settings file in the default JSON editor.
|
||||
@@ -1060,29 +959,153 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// Called when the users pressed keyBindings while CommandPalette is open.
|
||||
// - Called when the users pressed keyBindings while CommandPalette is open.
|
||||
// - This method is effectively an extract of TermControl::_KeyHandler and TermControl::_TryHandleKeyBinding.
|
||||
// Arguments:
|
||||
// - e: the KeyRoutedEventArgs containing info about the keystroke.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TerminalPage::_KeyDownHandler(Windows::Foundation::IInspectable const& /*sender*/, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
|
||||
{
|
||||
const auto key = e.OriginalKey();
|
||||
const auto scanCode = e.KeyStatus().ScanCode;
|
||||
const auto coreWindow = CoreWindow::GetForCurrentThread();
|
||||
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);
|
||||
const auto keyStatus = e.KeyStatus();
|
||||
const auto vkey = gsl::narrow_cast<WORD>(e.OriginalKey());
|
||||
const auto scanCode = gsl::narrow_cast<WORD>(keyStatus.ScanCode);
|
||||
const auto modifiers = _GetPressedModifierKeys();
|
||||
|
||||
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) })
|
||||
// GH#11076:
|
||||
// For some weird reason we sometimes receive a WM_KEYDOWN
|
||||
// message without vkey or scanCode if a user drags a tab.
|
||||
// The KeyChord constructor has a debug assertion ensuring that all KeyChord
|
||||
// either have a valid vkey/scanCode. This is important, because this prevents
|
||||
// accidential insertion of invalid KeyChords into classes like ActionMap.
|
||||
if (!vkey && !scanCode)
|
||||
{
|
||||
if (CommandPalette().Visibility() == Visibility::Visible && cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette)
|
||||
return;
|
||||
}
|
||||
|
||||
// Alt-Numpad# input will send us a character once the user releases
|
||||
// Alt, so we should be ignoring the individual keydowns. The character
|
||||
// will be sent through the TSFInputControl. See GH#1401 for more
|
||||
// details
|
||||
if (modifiers.IsAltPressed() && (vkey >= VK_NUMPAD0 && vkey <= VK_NUMPAD9))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// GH#2235: Terminal::Settings hasn't been modified to differentiate
|
||||
// between AltGr and Ctrl+Alt yet.
|
||||
// -> Don't check for key bindings if this is an AltGr key combination.
|
||||
if (modifiers.IsAltGrPressed())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto actionMap = _settings.ActionMap();
|
||||
if (!actionMap)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto cmd = actionMap.GetActionByKeyChord({
|
||||
modifiers.IsCtrlPressed(),
|
||||
modifiers.IsAltPressed(),
|
||||
modifiers.IsShiftPressed(),
|
||||
modifiers.IsWinPressed(),
|
||||
vkey,
|
||||
scanCode,
|
||||
});
|
||||
if (!cmd)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_actionDispatch->DoAction(cmd.ActionAndArgs()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto p = CommandPalette(); p.Visibility() == Visibility::Visible && cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette)
|
||||
{
|
||||
p.Visibility(Visibility::Collapsed);
|
||||
}
|
||||
|
||||
// Let's assume the user has bound the dead key "^" to a sendInput command that sends "b".
|
||||
// If the user presses the two keys "^a" it'll produce "bâ", despite us marking the key event as handled.
|
||||
// The following is used to manually "consume" such dead keys and clear them from the keyboard state.
|
||||
_ClearKeyboardState(vkey, scanCode);
|
||||
e.Handled(true);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the modifier keys that are currently pressed. This can be used to
|
||||
// find out which modifiers (ctrl, alt, shift) are pressed in events that
|
||||
// don't necessarily include that state.
|
||||
// - This is a copy of TermControl::_GetPressedModifierKeys.
|
||||
// Return Value:
|
||||
// - The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states.
|
||||
ControlKeyStates TerminalPage::_GetPressedModifierKeys() noexcept
|
||||
{
|
||||
const CoreWindow window = CoreWindow::GetForCurrentThread();
|
||||
// DONT USE
|
||||
// != CoreVirtualKeyStates::None
|
||||
// OR
|
||||
// == CoreVirtualKeyStates::Down
|
||||
// Sometimes with the key down, the state is Down | Locked.
|
||||
// Sometimes with the key up, the state is Locked.
|
||||
// IsFlagSet(Down) is the only correct solution.
|
||||
|
||||
struct KeyModifier
|
||||
{
|
||||
VirtualKey vkey;
|
||||
ControlKeyStates flags;
|
||||
};
|
||||
|
||||
constexpr std::array<KeyModifier, 7> modifiers{ {
|
||||
{ VirtualKey::RightMenu, ControlKeyStates::RightAltPressed },
|
||||
{ VirtualKey::LeftMenu, ControlKeyStates::LeftAltPressed },
|
||||
{ VirtualKey::RightControl, ControlKeyStates::RightCtrlPressed },
|
||||
{ VirtualKey::LeftControl, ControlKeyStates::LeftCtrlPressed },
|
||||
{ VirtualKey::Shift, ControlKeyStates::ShiftPressed },
|
||||
{ VirtualKey::RightWindows, ControlKeyStates::RightWinPressed },
|
||||
{ VirtualKey::LeftWindows, ControlKeyStates::LeftWinPressed },
|
||||
} };
|
||||
|
||||
ControlKeyStates flags;
|
||||
|
||||
for (const auto& mod : modifiers)
|
||||
{
|
||||
const auto state = window.GetKeyState(mod.vkey);
|
||||
const auto isDown = WI_IsFlagSet(state, CoreVirtualKeyStates::Down);
|
||||
|
||||
if (isDown)
|
||||
{
|
||||
CommandPalette().Visibility(Visibility::Collapsed);
|
||||
flags |= mod.flags;
|
||||
}
|
||||
_actionDispatch->DoAction(cmd.ActionAndArgs());
|
||||
e.Handled(true);
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Discards currently pressed dead keys.
|
||||
// - This is a copy of TermControl::_ClearKeyboardState.
|
||||
// Arguments:
|
||||
// - vkey: The vkey of the key pressed.
|
||||
// - scanCode: The scan code of the key pressed.
|
||||
void TerminalPage::_ClearKeyboardState(const WORD vkey, const WORD scanCode) noexcept
|
||||
{
|
||||
std::array<BYTE, 256> keyState;
|
||||
if (!GetKeyboardState(keyState.data()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// As described in "Sometimes you *want* to interfere with the keyboard's state buffer":
|
||||
// http://archives.miloush.net/michkap/archive/2006/09/10/748775.html
|
||||
// > "The key here is to keep trying to pass stuff to ToUnicode until -1 is not returned."
|
||||
std::array<wchar_t, 16> buffer;
|
||||
while (ToUnicodeEx(vkey, scanCode, keyState.data(), buffer.data(), gsl::narrow_cast<int>(buffer.size()), 0b1, nullptr) < 0)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1446,11 +1469,11 @@ namespace winrt::TerminalApp::implementation
|
||||
// - 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.
|
||||
winrt::fire_and_forget TerminalPage::_SplitPane(TerminalTab& tab,
|
||||
const SplitState splitType,
|
||||
const SplitType splitMode,
|
||||
const float splitSize,
|
||||
const NewTerminalArgs& newTerminalArgs)
|
||||
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)
|
||||
@@ -1468,6 +1491,8 @@ namespace winrt::TerminalApp::implementation
|
||||
profile = tab.GetFocusedProfile();
|
||||
if (profile)
|
||||
{
|
||||
// TODO GH#5047 If we cache the NewTerminalArgs, we no longer need to do this.
|
||||
profile = GetClosestProfileForDuplicationOfProfile(profile);
|
||||
controlSettings = TerminalSettings::CreateWithProfile(_settings, profile, *_bindings);
|
||||
const auto workingDirectory = tab.GetActiveTerminalControl().WorkingDirectory();
|
||||
const auto validWorkingDirectory = !workingDirectory.empty();
|
||||
@@ -1495,9 +1520,7 @@ namespace winrt::TerminalApp::implementation
|
||||
controlSettings = TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings);
|
||||
}
|
||||
|
||||
co_await winrt::resume_background();
|
||||
const auto contentProc = _CreateNewContentProcess(profile, controlSettings).get();
|
||||
co_await winrt::resume_foreground(Dispatcher());
|
||||
const auto controlConnection = _CreateConnectionFromSettings(profile, controlSettings.DefaultSettings());
|
||||
|
||||
const float contentWidth = ::base::saturated_cast<float>(_tabContent.ActualWidth());
|
||||
const float contentHeight = ::base::saturated_cast<float>(_tabContent.ActualHeight());
|
||||
@@ -1512,10 +1535,10 @@ namespace winrt::TerminalApp::implementation
|
||||
const auto canSplit = tab.PreCalculateCanSplit(realSplitType, splitSize, availableSpace);
|
||||
if (!canSplit)
|
||||
{
|
||||
co_return;
|
||||
return;
|
||||
}
|
||||
|
||||
auto newControl = _InitControl(controlSettings, contentProc.Guid());
|
||||
auto newControl = _InitControl(controlSettings, controlConnection);
|
||||
|
||||
// Hookup our event handlers to the new terminal
|
||||
_RegisterTerminalEvents(newControl);
|
||||
@@ -1529,7 +1552,10 @@ namespace winrt::TerminalApp::implementation
|
||||
// the control here instead.
|
||||
if (_startupState == StartupState::Initialized)
|
||||
{
|
||||
_GetActiveControl().Focus(FocusState::Programmatic);
|
||||
if (const auto control = _GetActiveControl())
|
||||
{
|
||||
control.Focus(FocusState::Programmatic);
|
||||
}
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
@@ -2100,17 +2126,6 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
return term;
|
||||
}
|
||||
TermControl TerminalPage::_InitControl(const TerminalSettingsCreateResult& settings, const winrt::guid& contentGuid)
|
||||
{
|
||||
// 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
|
||||
const auto child = TerminalSettings::CreateWithParent(settings);
|
||||
TermControl term{ contentGuid, child.DefaultSettings(), nullptr };
|
||||
|
||||
term.UnfocusedAppearance(child.UnfocusedSettings()); // It is okay for the unfocused settings to be null
|
||||
|
||||
return term;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Hook up keybindings, and refresh the UI of the terminal.
|
||||
@@ -2419,9 +2434,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// - <none>
|
||||
void TerminalPage::ToggleFullscreen()
|
||||
{
|
||||
_isFullscreen = !_isFullscreen;
|
||||
_UpdateTabView();
|
||||
_FullscreenChangedHandlers(*this, nullptr);
|
||||
SetFullscreen(!_isFullscreen);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -2638,6 +2651,17 @@ namespace winrt::TerminalApp::implementation
|
||||
return _isAlwaysOnTop;
|
||||
}
|
||||
|
||||
void TerminalPage::SetFullscreen(bool newFullscreen)
|
||||
{
|
||||
if (_isFullscreen == newFullscreen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_isFullscreen = newFullscreen;
|
||||
_UpdateTabView();
|
||||
_FullscreenChangedHandlers(*this, nullptr);
|
||||
}
|
||||
|
||||
HRESULT TerminalPage::_OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection)
|
||||
{
|
||||
// We need to be on the UI thread in order for _OpenNewTab to run successfully.
|
||||
@@ -2646,13 +2670,28 @@ namespace winrt::TerminalApp::implementation
|
||||
// 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);
|
||||
try
|
||||
{
|
||||
NewTerminalArgs newTerminalArgs{};
|
||||
// TODO GH#10952: When we pass the actual commandline (or originating application), the
|
||||
// settings model can choose the right settings based on command matching, or synthesize
|
||||
// a profile from the registry/link settings (TODO GH#9458).
|
||||
// TODO GH#9458: Get and pass the LNK/EXE filenames.
|
||||
// Passing in a commandline forces GetProfileForArgs to use Base Layer instead of Default Profile;
|
||||
// in the future, it can make a better decision based on the value we pull out of the process handle.
|
||||
// TODO GH#5047: When we hang on to the N.T.A., try not to spawn "default... .exe" :)
|
||||
newTerminalArgs.Commandline(L"default-terminal-invocation-placeholder");
|
||||
const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) };
|
||||
const auto settings{ TerminalSettings::CreateWithProfile(_settings, profile, *_bindings) };
|
||||
|
||||
// Request a summon of this window to the foreground
|
||||
_SummonWindowRequestedHandlers(*this, nullptr);
|
||||
_CreateNewTabWithProfileAndSettings(profile, settings, connection);
|
||||
|
||||
return hr;
|
||||
// Request a summon of this window to the foreground
|
||||
_SummonWindowRequestedHandlers(*this, nullptr);
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2660,9 +2699,8 @@ namespace winrt::TerminalApp::implementation
|
||||
HRESULT finalVal = S_OK;
|
||||
|
||||
Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [&]() {
|
||||
finalVal = _OpenNewTab(nullptr, connection);
|
||||
|
||||
_SummonWindowRequestedHandlers(*this, nullptr);
|
||||
// Re-running ourselves under the dispatcher will cause us to take the first branch above.
|
||||
finalVal = _OnNewConnection(connection);
|
||||
|
||||
latch.count_down();
|
||||
});
|
||||
@@ -2811,11 +2849,38 @@ namespace winrt::TerminalApp::implementation
|
||||
// Method Description:
|
||||
// - Displays a dialog stating the "Touch Keyboard and Handwriting Panel
|
||||
// Service" is disabled.
|
||||
void TerminalPage::ShowKeyboardServiceWarning()
|
||||
void TerminalPage::ShowKeyboardServiceWarning() const
|
||||
{
|
||||
if (auto keyboardWarningInfoBar = FindName(L"KeyboardWarningInfoBar").try_as<MUX::Controls::InfoBar>())
|
||||
if (!_IsMessageDismissed(InfoBarMessage::KeyboardServiceWarning))
|
||||
{
|
||||
keyboardWarningInfoBar.IsOpen(true);
|
||||
if (const auto keyboardServiceWarningInfoBar = FindName(L"KeyboardServiceWarningInfoBar").try_as<MUX::Controls::InfoBar>())
|
||||
{
|
||||
keyboardServiceWarningInfoBar.IsOpen(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Displays a info popup guiding the user into setting their default terminal.
|
||||
void TerminalPage::ShowSetAsDefaultInfoBar() const
|
||||
{
|
||||
if (!CascadiaSettings::IsDefaultTerminalAvailable() || _IsMessageDismissed(InfoBarMessage::SetAsDefault))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If the user has already configured any terminal for hand-off we
|
||||
// shouldn't inform them again about the possibility to do so.
|
||||
if (CascadiaSettings::IsDefaultTerminalSet())
|
||||
{
|
||||
_DismissMessage(InfoBarMessage::SetAsDefault);
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto infoBar = FindName(L"SetAsDefaultInfoBar").try_as<MUX::Controls::InfoBar>())
|
||||
{
|
||||
TraceLoggingWrite(g_hTerminalAppProvider, "SetAsDefaultTipPresented", TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
infoBar.IsOpen(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3160,4 +3225,112 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
return WindowName() == QuakeWindowName;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This function stops people from duplicating the base profile, because
|
||||
// it gets ~ ~ weird ~ ~ when they do. Remove when TODO GH#5047 is done.
|
||||
Profile TerminalPage::GetClosestProfileForDuplicationOfProfile(const Profile& profile) const noexcept
|
||||
{
|
||||
if (profile == _settings.ProfileDefaults())
|
||||
{
|
||||
return _settings.FindProfile(_settings.GlobalSettings().DefaultProfile());
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Persists the user's choice not to show information bar warning about "Touch keyboard and Handwriting Panel Service" disabled
|
||||
// Then hides this information buffer.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TerminalPage::_KeyboardServiceWarningInfoDismissHandler(const IInspectable& /*sender*/, const IInspectable& /*args*/) const
|
||||
{
|
||||
_DismissMessage(InfoBarMessage::KeyboardServiceWarning);
|
||||
if (const auto infoBar = FindName(L"KeyboardServiceWarningInfoBar").try_as<MUX::Controls::InfoBar>())
|
||||
{
|
||||
infoBar.IsOpen(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Persists the user's choice not to show the information bar warning about "Windows Terminal can be set as your default terminal application"
|
||||
// Then hides this information buffer.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TerminalPage::_SetAsDefaultDismissHandler(const IInspectable& /*sender*/, const IInspectable& /*args*/)
|
||||
{
|
||||
_DismissMessage(InfoBarMessage::SetAsDefault);
|
||||
if (const auto infoBar = FindName(L"SetAsDefaultInfoBar").try_as<MUX::Controls::InfoBar>())
|
||||
{
|
||||
infoBar.IsOpen(false);
|
||||
}
|
||||
|
||||
TraceLoggingWrite(g_hTerminalAppProvider, "SetAsDefaultTipDismissed", TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
|
||||
|
||||
_FocusCurrentTab(true);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Dismisses the Default Terminal tip and opens the settings.
|
||||
void TerminalPage::_SetAsDefaultOpenSettingsHandler(const IInspectable& /*sender*/, const IInspectable& /*args*/)
|
||||
{
|
||||
if (const auto infoBar = FindName(L"SetAsDefaultInfoBar").try_as<MUX::Controls::InfoBar>())
|
||||
{
|
||||
infoBar.IsOpen(false);
|
||||
}
|
||||
|
||||
TraceLoggingWrite(g_hTerminalAppProvider, "SetAsDefaultTipInteracted", TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
|
||||
|
||||
_OpenSettingsUI();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Checks whether information bar message was dismissed earlier (in the application state)
|
||||
// Arguments:
|
||||
// - message: message to look for in the state
|
||||
// Return Value:
|
||||
// - true, if the message was dismissed
|
||||
bool TerminalPage::_IsMessageDismissed(const InfoBarMessage& message)
|
||||
{
|
||||
if (const auto dismissedMessages{ ApplicationState::SharedInstance().DismissedMessages() })
|
||||
{
|
||||
for (const auto& dismissedMessage : dismissedMessages)
|
||||
{
|
||||
if (dismissedMessage == message)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Persists the user's choice to dismiss information bar message (in application state)
|
||||
// Arguments:
|
||||
// - message: message to dismiss
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TerminalPage::_DismissMessage(const InfoBarMessage& message)
|
||||
{
|
||||
const auto applicationState = ApplicationState::SharedInstance();
|
||||
std::vector<InfoBarMessage> messages;
|
||||
|
||||
if (const auto values = applicationState.DismissedMessages())
|
||||
{
|
||||
messages.resize(values.Size());
|
||||
values.GetMany(0, messages);
|
||||
}
|
||||
|
||||
if (std::none_of(messages.begin(), messages.end(), [&](const auto& m) { return m == message; }))
|
||||
{
|
||||
messages.emplace_back(message);
|
||||
}
|
||||
|
||||
applicationState.DismissedMessages(std::move(messages));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,17 @@
|
||||
|
||||
static constexpr uint32_t DefaultRowsToScroll{ 3 };
|
||||
static constexpr std::wstring_view TabletInputServiceKey{ L"TabletInputService" };
|
||||
// fwdecl unittest classes
|
||||
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
class TabTests;
|
||||
class SettingsTests;
|
||||
};
|
||||
}
|
||||
|
||||
namespace Microsoft::Terminal::Core
|
||||
{
|
||||
class ControlKeyStates;
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
@@ -77,6 +82,7 @@ namespace winrt::TerminalApp::implementation
|
||||
bool FocusMode() const;
|
||||
bool Fullscreen() const;
|
||||
bool AlwaysOnTop() const;
|
||||
void SetFullscreen(bool);
|
||||
|
||||
void SetStartupActions(std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs>& actions);
|
||||
void SetInboundListener(bool isEmbedding);
|
||||
@@ -87,7 +93,8 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
winrt::TerminalApp::TaskbarState TaskbarState() const;
|
||||
|
||||
void ShowKeyboardServiceWarning();
|
||||
void ShowKeyboardServiceWarning() const;
|
||||
void ShowSetAsDefaultInfoBar() const;
|
||||
winrt::hstring KeyboardServiceDisabledText();
|
||||
|
||||
winrt::fire_and_forget IdentifyWindow();
|
||||
@@ -190,13 +197,7 @@ namespace winrt::TerminalApp::implementation
|
||||
HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
|
||||
void _CreateNewTabFromPane(std::shared_ptr<Pane> pane);
|
||||
void _CreateNewTabWithProfileAndSettings(const Microsoft::Terminal::Settings::Model::Profile& profile, const Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
|
||||
winrt::Windows::Foundation::IAsyncOperation<Microsoft::Terminal::Control::ContentProcess> _CreateNewContentProcess(Microsoft::Terminal::Settings::Model::Profile profile,
|
||||
Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult settings);
|
||||
Microsoft::Terminal::Control::ContentProcess _AttachToContentProcess(const winrt::guid contentGuid);
|
||||
|
||||
winrt::fire_and_forget _CreateTabWithContent(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult settings, Microsoft::Terminal::Control::ContentProcess existingContentProc);
|
||||
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettings settings);
|
||||
winrt::Microsoft::Terminal::TerminalConnection::ConnectionInformation _CreateConnectionInfoFromSettings(Microsoft::Terminal::Settings::Model::Profile profile, const Microsoft::Terminal::Settings::Model::TerminalSettings& settings);
|
||||
|
||||
winrt::fire_and_forget _OpenNewWindow(const bool elevate, const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
|
||||
|
||||
@@ -209,6 +210,8 @@ namespace winrt::TerminalApp::implementation
|
||||
void _ThirdPartyNoticesOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
|
||||
|
||||
void _KeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
|
||||
static ::Microsoft::Terminal::Core::ControlKeyStates _GetPressedModifierKeys() noexcept;
|
||||
static void _ClearKeyboardState(const WORD vkey, const WORD scanCode) noexcept;
|
||||
void _HookupKeyBindings(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap) noexcept;
|
||||
void _RegisterActionCallbacks();
|
||||
|
||||
@@ -265,11 +268,11 @@ namespace winrt::TerminalApp::implementation
|
||||
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);
|
||||
winrt::fire_and_forget _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 _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 _ToggleSplitOrientation();
|
||||
|
||||
@@ -316,9 +319,6 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::Microsoft::Terminal::Control::TermControl _InitControl(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings,
|
||||
const winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection& connection);
|
||||
|
||||
winrt::Microsoft::Terminal::Control::TermControl _InitControl(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings,
|
||||
const winrt::guid& contentGuid);
|
||||
|
||||
void _RefreshUIForSettingsReload();
|
||||
|
||||
void _SetNonClientAreaColors(const Windows::UI::Color& selectedTabColor);
|
||||
@@ -367,6 +367,14 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void _SetFocusMode(const bool inFocusMode);
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::Model::Profile GetClosestProfileForDuplicationOfProfile(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile) const noexcept;
|
||||
|
||||
void _KeyboardServiceWarningInfoDismissHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args) const;
|
||||
void _SetAsDefaultDismissHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args);
|
||||
void _SetAsDefaultOpenSettingsHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args);
|
||||
static bool _IsMessageDismissed(const winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage& message);
|
||||
static void _DismissMessage(const winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage& message);
|
||||
|
||||
#pragma region ActionHandlers
|
||||
// These are all defined in AppActionHandlers.cpp
|
||||
#define ON_ALL_ACTIONS(action) DECLARE_ACTION_HANDLER(action);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<Grid x:Name="Root"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
@@ -21,8 +22,37 @@
|
||||
<local:TabRowControl x:Name="TabRow"
|
||||
Grid.Row="0" />
|
||||
|
||||
<StackPanel Grid.Row="1">
|
||||
<mux:InfoBar x:Name="KeyboardServiceWarningInfoBar"
|
||||
x:Load="False"
|
||||
IsClosable="True"
|
||||
IsIconVisible="True"
|
||||
IsOpen="False"
|
||||
Message="{x:Bind KeyboardServiceDisabledText, Mode=OneWay}"
|
||||
Severity="Warning">
|
||||
<mux:InfoBar.ActionButton>
|
||||
<Button x:Uid="InfoBarDismissButton"
|
||||
Click="_KeyboardServiceWarningInfoDismissHandler" />
|
||||
</mux:InfoBar.ActionButton>
|
||||
</mux:InfoBar>
|
||||
|
||||
<mux:InfoBar x:Name="SetAsDefaultInfoBar"
|
||||
x:Uid="SetAsDefaultInfoBar"
|
||||
x:Load="False"
|
||||
CloseButtonClick="_SetAsDefaultDismissHandler"
|
||||
IsClosable="True"
|
||||
IsIconVisible="True"
|
||||
IsOpen="False"
|
||||
Severity="Informational">
|
||||
<mux:InfoBar.ActionButton>
|
||||
<HyperlinkButton x:Uid="SetAsDefaultTip_OpenSettingsLink"
|
||||
Click="_SetAsDefaultOpenSettingsHandler" />
|
||||
</mux:InfoBar.ActionButton>
|
||||
</mux:InfoBar>
|
||||
</StackPanel>
|
||||
|
||||
<Grid x:Name="TabContent"
|
||||
Grid.Row="1"
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch" />
|
||||
|
||||
@@ -104,19 +134,11 @@
|
||||
</ContentDialog>
|
||||
|
||||
<local:CommandPalette x:Name="CommandPalette"
|
||||
Grid.Row="1"
|
||||
Grid.Row="2"
|
||||
VerticalAlignment="Stretch"
|
||||
PreviewKeyDown="_KeyDownHandler"
|
||||
Visibility="Collapsed" />
|
||||
|
||||
<mux:InfoBar x:Name="KeyboardWarningInfoBar"
|
||||
x:Load="False"
|
||||
IsClosable="True"
|
||||
IsIconVisible="True"
|
||||
IsOpen="False"
|
||||
Message="{x:Bind KeyboardServiceDisabledText, Mode=OneWay}"
|
||||
Severity="Warning" />
|
||||
|
||||
<!--
|
||||
A TeachingTip with IsLightDismissEnabled="True" will immediately
|
||||
dismiss itself if the window is unfocused (In Xaml Islands). This is
|
||||
|
||||
@@ -89,13 +89,13 @@
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<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.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.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">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('$(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.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.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>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.1.3" targetFramework="native" />
|
||||
<package id="Microsoft.UI.Xaml" version="2.6.2-prerelease.210818003" targetFramework="native" />
|
||||
<package id="Microsoft.UI.Xaml" version="2.5.0-prerelease.201202003" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.210309.3" targetFramework="native" />
|
||||
</packages>
|
||||
|
||||
@@ -99,29 +99,57 @@ static HRESULT _duplicateHandle(const HANDLE in, HANDLE& out) noexcept
|
||||
// from the registered handler event function.
|
||||
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;
|
||||
try
|
||||
{
|
||||
// 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();
|
||||
// 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 };
|
||||
std::unique_lock lock{ _mtx };
|
||||
|
||||
// Report an error if no one registered a handoff function before calling this.
|
||||
RETURN_HR_IF_NULL(E_NOT_VALID_STATE, localPfnHandoff);
|
||||
// Report an error if no one registered a handoff function before calling this.
|
||||
THROW_HR_IF_NULL(E_NOT_VALID_STATE, localPfnHandoff);
|
||||
|
||||
// Duplicate the handles from what we received.
|
||||
// The contract with COM specifies that any HANDLEs we receive from the caller belong
|
||||
// to the caller and will be freed when we leave the scope of this method.
|
||||
// Making our own duplicate copy ensures they hang around in our lifetime.
|
||||
RETURN_IF_FAILED(_duplicateHandle(in, in));
|
||||
RETURN_IF_FAILED(_duplicateHandle(out, out));
|
||||
RETURN_IF_FAILED(_duplicateHandle(signal, signal));
|
||||
RETURN_IF_FAILED(_duplicateHandle(ref, ref));
|
||||
RETURN_IF_FAILED(_duplicateHandle(server, server));
|
||||
RETURN_IF_FAILED(_duplicateHandle(client, client));
|
||||
// Duplicate the handles from what we received.
|
||||
// The contract with COM specifies that any HANDLEs we receive from the caller belong
|
||||
// to the caller and will be freed when we leave the scope of this method.
|
||||
// Making our own duplicate copy ensures they hang around in our lifetime.
|
||||
THROW_IF_FAILED(_duplicateHandle(in, in));
|
||||
THROW_IF_FAILED(_duplicateHandle(out, out));
|
||||
THROW_IF_FAILED(_duplicateHandle(signal, signal));
|
||||
THROW_IF_FAILED(_duplicateHandle(ref, ref));
|
||||
THROW_IF_FAILED(_duplicateHandle(server, server));
|
||||
THROW_IF_FAILED(_duplicateHandle(client, client));
|
||||
|
||||
// Call registered handler from when we started listening.
|
||||
return localPfnHandoff(in, out, signal, ref, server, client);
|
||||
// Call registered handler from when we started listening.
|
||||
THROW_IF_FAILED(localPfnHandoff(in, out, signal, ref, server, client));
|
||||
|
||||
#pragma warning(suppress : 26477)
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalConnectionProvider,
|
||||
"ReceiveTerminalHandoff_Success",
|
||||
TraceLoggingDescription("successfully received a terminal handoff"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
const auto hr = wil::ResultFromCaughtException();
|
||||
|
||||
#pragma warning(suppress : 26477)
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalConnectionProvider,
|
||||
"ReceiveTerminalHandoff_Failed",
|
||||
TraceLoggingDescription("failed while receiving a terminal handoff"),
|
||||
TraceLoggingHResult(hr),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
|
||||
|
||||
return hr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,59 +36,21 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
::IInspectable** raw = reinterpret_cast<::IInspectable**>(pointer);
|
||||
#pragma warning(pop)
|
||||
|
||||
TerminalConnection::ITerminalConnection connection{ nullptr };
|
||||
|
||||
// A couple short-circuits, for connections that _we_ implement.
|
||||
// Sometimes, RoActivateInstance is weird and fails with errors like the
|
||||
// following
|
||||
// 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.
|
||||
//
|
||||
/*
|
||||
onecore\com\combase\inc\RegistryKey.hpp(527)\combase.dll!00007FFF75E1F855:
|
||||
(caller: 00007FFF75D3BC29) LogHr(2) tid(83a8) 800700A1 The specified
|
||||
path is invalid.
|
||||
Msg:[StaticNtOpen failed with
|
||||
path:\REGISTRY\A\{A41685A4-AD85-4C4C-BA5D-A849ADBF3C40}\ActivatableClassId
|
||||
\REGISTRY\MACHINE\Software\Classes\ActivatableClasses]
|
||||
...\src\cascadia\TerminalConnection\ConnectionInformation.cpp(47)\TerminalConnection.dll!00007FFEC1381FC5:
|
||||
(caller: 00007FFEC13810A5) LogHr(1) tid(83a8) 800700A1 The specified
|
||||
path is invalid.
|
||||
[...TerminalConnection::implementation::ConnectionInformation::CreateConnection(RoActivateInstance(name,
|
||||
raw))]
|
||||
*/
|
||||
//
|
||||
// So to avoid those, we'll manually instantiate these
|
||||
if (info.ClassName() == winrt::name_of<TerminalConnection::ConptyConnection>())
|
||||
// When we get to extensions (GH#4000), we may want to revisit.
|
||||
if (LOG_IF_FAILED(RoActivateInstance(name, raw)))
|
||||
{
|
||||
connection = TerminalConnection::ConptyConnection();
|
||||
}
|
||||
else if (info.ClassName() == winrt::name_of<TerminalConnection::AzureConnection>())
|
||||
{
|
||||
connection = TerminalConnection::AzureConnection();
|
||||
}
|
||||
else if (info.ClassName() == winrt::name_of<TerminalConnection::EchoConnection>())
|
||||
{
|
||||
connection = TerminalConnection::EchoConnection();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
connection = inspectable.try_as<TerminalConnection::ITerminalConnection>();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Now that thing we made, make sure it's actually a ITerminalConnection
|
||||
if (connection)
|
||||
if (const auto connection{ inspectable.try_as<TerminalConnection::ITerminalConnection>() })
|
||||
{
|
||||
// Initialize it, and return it.
|
||||
connection.Initialize(info.Settings());
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "ConptyConnection.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <userenv.h>
|
||||
|
||||
#include "ConptyConnection.g.cpp"
|
||||
#include "CTerminalHandoff.h"
|
||||
@@ -161,11 +160,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
environment.clear();
|
||||
});
|
||||
|
||||
{
|
||||
const auto newEnvironmentBlock{ Utils::CreateEnvironmentBlock() };
|
||||
// Populate the environment map with the current environment.
|
||||
RETURN_IF_FAILED(Utils::UpdateEnvironmentMapW(environment, newEnvironmentBlock.get()));
|
||||
}
|
||||
// Populate the environment map with the current environment.
|
||||
RETURN_IF_FAILED(Utils::UpdateEnvironmentMapW(environment));
|
||||
|
||||
{
|
||||
// Convert connection Guid to string and ignore the enclosing '{}'.
|
||||
@@ -369,6 +365,16 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
// window is expecting it to be on the first layout.
|
||||
else
|
||||
{
|
||||
#pragma warning(suppress : 26477 26485 26494 26482 26446) // We don't control TraceLoggingWrite
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalConnectionProvider,
|
||||
"ConPtyConnectedToDefterm",
|
||||
TraceLoggingDescription("Event emitted when ConPTY connection is started, for a defterm session"),
|
||||
TraceLoggingGuid(_guid, "SessionGuid", "The WT_SESSION's GUID"),
|
||||
TraceLoggingWideString(_clientName.c_str(), "Client", "The attached client process"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
|
||||
THROW_IF_FAILED(ConptyResizePseudoConsole(_hPC.get(), dimensions));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "ContentProcess.h"
|
||||
#include "ContentProcess.g.cpp"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
ContentProcess::ContentProcess(winrt::guid g) :
|
||||
_ourPID{ GetCurrentProcessId() }, _guid{ g } {}
|
||||
|
||||
bool ContentProcess::Initialize(Control::IControlSettings settings,
|
||||
TerminalConnection::ConnectionInformation connectionInfo)
|
||||
{
|
||||
auto conn{ TerminalConnection::ConnectionInformation::CreateConnection(connectionInfo) };
|
||||
if (conn == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_interactivity = winrt::make<implementation::ControlInteractivity>(settings, conn);
|
||||
return true;
|
||||
}
|
||||
|
||||
ContentProcess::~ContentProcess()
|
||||
{
|
||||
// DANGER - We're straight up going to EXIT THE ENTIRE PROCESS when we
|
||||
// get destructed. This eliminates the need to do any sort of
|
||||
// refcounting weirdness. This entire process exists to host one
|
||||
// singular ContentProcess instance. When we're destructed, it's because
|
||||
// every other window process was done with us. We can die now, knowing
|
||||
// that our job is complete.
|
||||
ExitProcess(0);
|
||||
}
|
||||
|
||||
Control::ControlInteractivity ContentProcess::GetInteractivity()
|
||||
{
|
||||
return _interactivity;
|
||||
}
|
||||
|
||||
uint64_t ContentProcess::GetPID()
|
||||
{
|
||||
return _ourPID;
|
||||
}
|
||||
winrt::guid ContentProcess::Guid()
|
||||
{
|
||||
return _guid;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Duplicate the swap chain handle to the provided process.
|
||||
// - If the provided PID is our pid, then great - we don't need to do anything.
|
||||
// Arguments:
|
||||
// - callersPid: the PID of the process calling this method.
|
||||
// Return Value:
|
||||
// - The value of the swapchain handle in the callers process
|
||||
// Notes:
|
||||
// - This is BODGY! We're basically asking to marshal a HANDLE here. WinRT
|
||||
// has no good mechanism for doing this, so we're doing it by casting the
|
||||
// value to a uint64_t. In all reality, we _should_ be using a COM
|
||||
// interface for this, because it can set up the security on these handles
|
||||
// more appropriately. Fortunately, all we're dealing with is swapchains,
|
||||
// so the security doesn't matter all that much.
|
||||
uint64_t ContentProcess::RequestSwapChainHandle(const uint64_t callersPid)
|
||||
{
|
||||
auto ourPid = GetCurrentProcessId();
|
||||
HANDLE ourHandle = reinterpret_cast<HANDLE>(_interactivity.Core().SwapChainHandle());
|
||||
if (callersPid == ourPid)
|
||||
{
|
||||
return reinterpret_cast<uint64_t>(ourHandle);
|
||||
}
|
||||
|
||||
wil::unique_handle hWindowProcess{ OpenProcess(PROCESS_ALL_ACCESS,
|
||||
FALSE,
|
||||
static_cast<DWORD>(callersPid)) };
|
||||
if (hWindowProcess.get() == nullptr)
|
||||
{
|
||||
const auto gle = GetLastError();
|
||||
gle;
|
||||
// TODO! tracelog an error here
|
||||
return 0;
|
||||
}
|
||||
|
||||
HANDLE theirHandle{ nullptr };
|
||||
BOOL success = DuplicateHandle(GetCurrentProcess(),
|
||||
ourHandle,
|
||||
hWindowProcess.get(),
|
||||
&theirHandle,
|
||||
0,
|
||||
FALSE,
|
||||
DUPLICATE_SAME_ACCESS);
|
||||
if (!success)
|
||||
{
|
||||
const auto gle = GetLastError();
|
||||
gle;
|
||||
// TODO! tracelog an error here
|
||||
return 0;
|
||||
}
|
||||
|
||||
// At this point, the handle is now in their process space, with value
|
||||
// theirHandle
|
||||
return reinterpret_cast<uint64_t>(theirHandle);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ContentProcess.g.h"
|
||||
#include "ControlInteractivity.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
struct ContentProcess : ContentProcessT<ContentProcess>
|
||||
{
|
||||
ContentProcess(winrt::guid g);
|
||||
~ContentProcess();
|
||||
bool Initialize(Control::IControlSettings settings,
|
||||
TerminalConnection::ConnectionInformation connectionInfo);
|
||||
Control::ControlInteractivity GetInteractivity();
|
||||
|
||||
uint64_t GetPID();
|
||||
winrt::guid Guid();
|
||||
uint64_t RequestSwapChainHandle(const uint64_t pid);
|
||||
|
||||
private:
|
||||
Control::ControlInteractivity _interactivity{ nullptr };
|
||||
uint64_t _ourPID;
|
||||
winrt::guid _guid;
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Control::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(ContentProcess);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "ControlInteractivity.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Control
|
||||
{
|
||||
runtimeclass ContentProcess {
|
||||
|
||||
ContentProcess(Guid g);
|
||||
|
||||
Boolean Initialize(IControlSettings settings,
|
||||
Microsoft.Terminal.TerminalConnection.ConnectionInformation connectionInfo);
|
||||
|
||||
ControlInteractivity GetInteractivity();
|
||||
|
||||
UInt64 GetPID();
|
||||
Guid Guid { get; };
|
||||
|
||||
UInt64 RequestSwapChainHandle(UInt64 pid);
|
||||
};
|
||||
}
|
||||
@@ -414,6 +414,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// This is a scroll event that wasn't initiated by the terminal
|
||||
// itself - it was initiated by the mouse wheel, or the scrollbar.
|
||||
_terminal->UserScrollViewport(viewTop);
|
||||
|
||||
_updatePatternLocations->Run();
|
||||
}
|
||||
|
||||
void ControlCore::AdjustOpacity(const double adjustment)
|
||||
@@ -595,11 +597,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_desiredFont = { _actualFont };
|
||||
|
||||
// Update the terminal core with its new Core settings
|
||||
// Are you seeing a crash specifically on this line?
|
||||
Core::ICoreSettings coreSettings{ _settings };
|
||||
// Then you might not have Microsoft.Terminal.Core.winmd in the package
|
||||
// or SxS with windowsterminal.exe!
|
||||
_terminal->UpdateSettings(coreSettings);
|
||||
_terminal->UpdateSettings(_settings);
|
||||
|
||||
if (!_initializedTerminal)
|
||||
{
|
||||
@@ -1005,6 +1003,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
_terminal->WritePastedText(hstr);
|
||||
_terminal->ClearSelection();
|
||||
_renderer->TriggerSelection();
|
||||
_terminal->TrySnapOnInput();
|
||||
}
|
||||
|
||||
|
||||
@@ -247,17 +247,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
inline bool _IsClosing() const noexcept
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
// // TODO! This may not be strictly true if the core is running out of
|
||||
// // proc with XAML. I keep hitting this assertion every time it
|
||||
// // exits, so we might need a better solution.
|
||||
// if (_dispatcher)
|
||||
// {
|
||||
// // _closing isn't atomic and may only be accessed from the main thread.
|
||||
// //
|
||||
// // Though, the unit tests don't actually run in TAEF's main
|
||||
// // thread, so we don't care when we're running in tests.
|
||||
// assert(_inUnitTests || _dispatcher.HasThreadAccess());
|
||||
// }
|
||||
if (_dispatcher)
|
||||
{
|
||||
// _closing isn't atomic and may only be accessed from the main thread.
|
||||
//
|
||||
// Though, the unit tests don't actually run in TAEF's main
|
||||
// thread, so we don't care when we're running in tests.
|
||||
assert(_inUnitTests || _dispatcher.HasThreadAccess());
|
||||
}
|
||||
#endif
|
||||
return _closing;
|
||||
}
|
||||
|
||||
@@ -214,12 +214,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
else if (_canSendVTMouseInput(modifiers))
|
||||
{
|
||||
const auto adjustment = _core->ScrollOffset() > 0 ? _core->BufferHeight() - _core->ScrollOffset() - _core->ViewHeight() : 0;
|
||||
// If the click happened outside the active region, just don't send any mouse event
|
||||
if (const auto adjustedY = terminalPosition.y() - adjustment; adjustedY >= 0)
|
||||
{
|
||||
_core->SendMouseEvent({ terminalPosition.x(), adjustedY }, pointerUpdateKind, modifiers, 0, toInternalMouseState(buttonState));
|
||||
}
|
||||
_sendMouseEventHelper(terminalPosition, pointerUpdateKind, modifiers, 0, buttonState);
|
||||
}
|
||||
else if (WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown))
|
||||
{
|
||||
@@ -287,7 +282,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// Short-circuit isReadOnly check to avoid warning dialog
|
||||
if (focused && !_core->IsInReadOnlyMode() && _canSendVTMouseInput(modifiers))
|
||||
{
|
||||
_core->SendMouseEvent(terminalPosition, pointerUpdateKind, modifiers, 0, toInternalMouseState(buttonState));
|
||||
_sendMouseEventHelper(terminalPosition, pointerUpdateKind, modifiers, 0, buttonState);
|
||||
}
|
||||
// GH#4603 - don't modify the selection if the pointer press didn't
|
||||
// actually start _in_ the control bounds. Case in point - someone drags
|
||||
@@ -370,7 +365,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// Short-circuit isReadOnly check to avoid warning dialog
|
||||
if (!_core->IsInReadOnlyMode() && _canSendVTMouseInput(modifiers))
|
||||
{
|
||||
_core->SendMouseEvent(terminalPosition, pointerUpdateKind, modifiers, 0, toInternalMouseState(buttonState));
|
||||
_sendMouseEventHelper(terminalPosition, pointerUpdateKind, modifiers, 0, buttonState);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -420,11 +415,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// here with a PointerPoint. However, as of #979, we don't have a
|
||||
// PointerPoint to work with. So, we're just going to do a
|
||||
// mousewheel event manually
|
||||
return _core->SendMouseEvent(terminalPosition,
|
||||
return _sendMouseEventHelper(terminalPosition,
|
||||
WM_MOUSEWHEEL,
|
||||
modifiers,
|
||||
::base::saturated_cast<short>(delta),
|
||||
toInternalMouseState(buttonState));
|
||||
buttonState);
|
||||
}
|
||||
|
||||
const auto ctrlPressed = modifiers.IsCtrlPressed();
|
||||
@@ -600,6 +595,21 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
return til::point{ pixelPosition / fontSize };
|
||||
}
|
||||
|
||||
bool ControlInteractivity::_sendMouseEventHelper(const til::point terminalPosition,
|
||||
const unsigned int pointerUpdateKind,
|
||||
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
|
||||
const SHORT wheelDelta,
|
||||
Control::MouseButtonState buttonState)
|
||||
{
|
||||
const auto adjustment = _core->ScrollOffset() > 0 ? _core->BufferHeight() - _core->ScrollOffset() - _core->ViewHeight() : 0;
|
||||
// If the click happened outside the active region, just don't send any mouse event
|
||||
if (const auto adjustedY = terminalPosition.y() - adjustment; adjustedY >= 0)
|
||||
{
|
||||
return _core->SendMouseEvent({ terminalPosition.x(), adjustedY }, pointerUpdateKind, modifiers, wheelDelta, toInternalMouseState(buttonState));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Creates an automation peer for the Terminal Control, enabling
|
||||
// accessibility on our control.
|
||||
|
||||
@@ -142,6 +142,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void _sendPastedTextToConnection(std::wstring_view wstr);
|
||||
til::point _getTerminalPosition(const til::point& pixelPosition);
|
||||
|
||||
bool _sendMouseEventHelper(const til::point terminalPosition,
|
||||
const unsigned int pointerUpdateKind,
|
||||
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
|
||||
const SHORT wheelDelta,
|
||||
Control::MouseButtonState buttonState);
|
||||
|
||||
friend class ControlUnitTests::ControlCoreTests;
|
||||
friend class ControlUnitTests::ControlInteractivityTests;
|
||||
};
|
||||
|
||||
@@ -43,8 +43,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
_controlPadding = padding;
|
||||
}
|
||||
|
||||
void InteractivityAutomationPeer::SetParentProvider(Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple parentProvider)
|
||||
void InteractivityAutomationPeer::ParentProvider(AutomationPeer parentProvider)
|
||||
{
|
||||
_parentProvider = parentProvider;
|
||||
}
|
||||
@@ -115,33 +114,21 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// ScreenInfoUiaProvider doesn't actually use parameter, so just pass in nullptr
|
||||
THROW_IF_FAILED(_uiaProvider->RangeFromChild(/* IRawElementProviderSimple */ nullptr,
|
||||
&returnVal));
|
||||
|
||||
// const auto parentProvider = this->ProviderFromPeer(*this);
|
||||
const auto parentProvider = _parentProvider;
|
||||
const auto xutr = winrt::make_self<XamlUiaTextRange>(returnVal, parentProvider);
|
||||
return xutr.as<XamlAutomation::ITextRangeProvider>();
|
||||
return _CreateXamlUiaTextRange(returnVal);
|
||||
}
|
||||
|
||||
XamlAutomation::ITextRangeProvider InteractivityAutomationPeer::RangeFromPoint(Windows::Foundation::Point screenLocation)
|
||||
{
|
||||
UIA::ITextRangeProvider* returnVal;
|
||||
THROW_IF_FAILED(_uiaProvider->RangeFromPoint({ screenLocation.X, screenLocation.Y }, &returnVal));
|
||||
|
||||
// const auto parentProvider = this->ProviderFromPeer(*this);
|
||||
const auto parentProvider = _parentProvider;
|
||||
const auto xutr = winrt::make_self<XamlUiaTextRange>(returnVal, parentProvider);
|
||||
return xutr.as<XamlAutomation::ITextRangeProvider>();
|
||||
return _CreateXamlUiaTextRange(returnVal);
|
||||
}
|
||||
|
||||
XamlAutomation::ITextRangeProvider InteractivityAutomationPeer::DocumentRange()
|
||||
{
|
||||
UIA::ITextRangeProvider* returnVal;
|
||||
THROW_IF_FAILED(_uiaProvider->get_DocumentRange(&returnVal));
|
||||
|
||||
// const auto parentProvider = this->ProviderFromPeer(*this);
|
||||
const auto parentProvider = _parentProvider;
|
||||
const auto xutr = winrt::make_self<XamlUiaTextRange>(returnVal, parentProvider);
|
||||
return xutr.as<XamlAutomation::ITextRangeProvider>();
|
||||
return _CreateXamlUiaTextRange(returnVal);
|
||||
}
|
||||
|
||||
XamlAutomation::SupportedTextSelection InteractivityAutomationPeer::SupportedTextSelection()
|
||||
@@ -188,6 +175,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
XamlAutomation::ITextRangeProvider InteractivityAutomationPeer::_CreateXamlUiaTextRange(UIA::ITextRangeProvider* returnVal) const
|
||||
{
|
||||
// LOAD-BEARING: use _parentProvider->ProviderFromPeer(_parentProvider) instead of this->ProviderFromPeer(*this).
|
||||
// Since we split the automation peer into TermControlAutomationPeer and InteractivityAutomationPeer,
|
||||
// using "this" returns null. This can cause issues with some UIA Client scenarios like any navigation in Narrator.
|
||||
const auto parent{ _parentProvider.get() };
|
||||
if (!parent)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
const auto xutr = winrt::make_self<XamlUiaTextRange>(returnVal, parent.ProviderFromPeer(parent));
|
||||
return xutr.as<XamlAutomation::ITextRangeProvider>();
|
||||
};
|
||||
|
||||
// Method Description:
|
||||
// - extracts the UiaTextRanges from the SAFEARRAY and converts them to Xaml ITextRangeProviders
|
||||
// Arguments:
|
||||
@@ -202,12 +203,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
std::vector<XamlAutomation::ITextRangeProvider> vec;
|
||||
vec.reserve(count);
|
||||
// auto parentProvider = this->ProviderFromPeer(*this);
|
||||
const auto parentProvider = _parentProvider;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
auto xutr = make_self<XamlUiaTextRange>(providers[i].detach(), parentProvider);
|
||||
vec.emplace_back(xutr.as<XamlAutomation::ITextRangeProvider>());
|
||||
if (auto xutr = _CreateXamlUiaTextRange(providers[i].detach()))
|
||||
{
|
||||
vec.emplace_back(std::move(xutr));
|
||||
}
|
||||
}
|
||||
|
||||
com_array<XamlAutomation::ITextRangeProvider> result{ vec };
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
void SetControlBounds(const Windows::Foundation::Rect bounds);
|
||||
void SetControlPadding(const Core::Padding padding);
|
||||
void SetParentProvider(Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple parentProvider);
|
||||
void ParentProvider(Windows::UI::Xaml::Automation::Peers::AutomationPeer parentProvider);
|
||||
|
||||
#pragma region IUiaEventDispatcher
|
||||
void SignalSelectionChanged() override;
|
||||
@@ -75,9 +75,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
TYPED_EVENT(CursorChanged, IInspectable, IInspectable);
|
||||
|
||||
private:
|
||||
Windows::UI::Xaml::Automation::Provider::ITextRangeProvider _CreateXamlUiaTextRange(::ITextRangeProvider* returnVal) const;
|
||||
|
||||
::Microsoft::WRL::ComPtr<::Microsoft::Terminal::TermControlUiaProvider> _uiaProvider;
|
||||
winrt::Microsoft::Terminal::Control::implementation::ControlInteractivity* _interactivity;
|
||||
winrt::Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple _parentProvider{ nullptr };
|
||||
weak_ref<Windows::UI::Xaml::Automation::Peers::AutomationPeer> _parentProvider;
|
||||
|
||||
til::rectangle _controlBounds{};
|
||||
til::rectangle _controlPadding{};
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Terminal.Control
|
||||
{
|
||||
[default_interface] runtimeclass InteractivityAutomationPeer/* :
|
||||
[default_interface] runtimeclass InteractivityAutomationPeer :
|
||||
Windows.UI.Xaml.Automation.Peers.AutomationPeer,
|
||||
Windows.UI.Xaml.Automation.Provider.ITextProvider*/
|
||||
Windows.UI.Xaml.Automation.Provider.ITextProvider
|
||||
{
|
||||
|
||||
Windows.UI.Xaml.Automation.Provider.ITextRangeProvider[] GetSelection();
|
||||
Windows.UI.Xaml.Automation.Provider.ITextRangeProvider[] GetVisibleRanges();
|
||||
Windows.UI.Xaml.Automation.Provider.ITextRangeProvider RangeFromChild(Windows.UI.Xaml.Automation.Provider.IRawElementProviderSimple childElement);
|
||||
Windows.UI.Xaml.Automation.Provider.ITextRangeProvider RangeFromPoint(Windows.Foundation.Point screenLocation);
|
||||
Windows.UI.Xaml.Automation.Provider.ITextRangeProvider DocumentRange();
|
||||
Windows.UI.Xaml.Automation.SupportedTextSelection SupportedTextSelection();
|
||||
|
||||
void SetControlBounds(Windows.Foundation.Rect bounds);
|
||||
void SetControlPadding(Microsoft.Terminal.Core.Padding padding);
|
||||
void SetParentProvider(Windows.UI.Xaml.Automation.Provider.IRawElementProviderSimple provider);
|
||||
void ParentProvider(Windows.UI.Xaml.Automation.Peers.AutomationPeer parentProvider);
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> SelectionChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> TextChanged;
|
||||
|
||||
@@ -200,10 +200,4 @@ Please either install the missing font or choose another one.</value>
|
||||
<data name="TermControlReadOnly" xml:space="preserve">
|
||||
<value>Read-only mode is enabled.</value>
|
||||
</data>
|
||||
<data name="TermControl_ContentDiedTextBlock.Text" xml:space="preserve">
|
||||
<value>The content of this terminal was closed unexpectedly.</value>
|
||||
</data>
|
||||
<data name="TermControl_ContentDiedButton.Content" xml:space="preserve">
|
||||
<value>Close</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// The functions for handling content processes in the TermControl are largely
|
||||
// in TermControlContentManageent.cpp
|
||||
|
||||
#include "pch.h"
|
||||
#include "TermControl.h"
|
||||
@@ -35,10 +32,8 @@ using namespace winrt::Windows::ApplicationModel::DataTransfer;
|
||||
// The updates are throttled to limit power usage.
|
||||
constexpr const auto ScrollBarUpdateInterval = std::chrono::milliseconds(8);
|
||||
|
||||
// The minimum delay between updating the TSF input control. This is already
|
||||
// throttled primarily in the ControlCore, with a timeout of 100ms. We're adding
|
||||
// another smaller one here, as the (potentially x-proc) call will come in off
|
||||
// the UI thread
|
||||
// The minimum delay between updating the TSF input control.
|
||||
// This is already throttled primarily in the ControlCore, with a timeout of 100ms. We're adding another smaller one here, as the (potentially x-proc) call will come in off the UI thread
|
||||
constexpr const auto TsfRedrawInterval = std::chrono::milliseconds(8);
|
||||
|
||||
// The minimum delay between updating the locations of regex patterns
|
||||
@@ -55,14 +50,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
TermControl::TermControl(IControlSettings settings,
|
||||
TerminalConnection::ITerminalConnection connection) :
|
||||
TermControl(winrt::guid{}, settings, connection) {}
|
||||
|
||||
TermControl::TermControl(winrt::guid contentGuid,
|
||||
IControlSettings settings,
|
||||
TerminalConnection::ITerminalConnection connection) :
|
||||
_initializedTerminal{ false },
|
||||
_settings{ settings },
|
||||
_closing{ false },
|
||||
_isInternalScrollBarUpdate{ false },
|
||||
_autoScrollVelocity{ 0 },
|
||||
_autoScrollingPointerPoint{ std::nullopt },
|
||||
@@ -74,21 +62,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
if (contentGuid != winrt::guid{})
|
||||
{
|
||||
_contentProc = create_instance<Control::ContentProcess>(contentGuid, CLSCTX_LOCAL_SERVER);
|
||||
if (_contentProc != nullptr)
|
||||
{
|
||||
_interactivity = _contentProc.GetInteractivity();
|
||||
_contentWaitInterrupt.create();
|
||||
_createContentWaitThread();
|
||||
}
|
||||
}
|
||||
|
||||
if (_interactivity == nullptr)
|
||||
{
|
||||
_interactivity = winrt::make<implementation::ControlInteractivity>(settings, connection);
|
||||
}
|
||||
_interactivity = winrt::make<implementation::ControlInteractivity>(settings, connection);
|
||||
_core = _interactivity.Core();
|
||||
|
||||
// These events might all be triggered by the connection, but that
|
||||
@@ -101,8 +75,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// This event is specifically triggered by the renderer thread, a BG thread. Use a weak ref here.
|
||||
_core.RendererEnteredErrorState({ get_weak(), &TermControl::_RendererEnteredErrorState });
|
||||
|
||||
_core.ConnectionStateChanged({ get_weak(), &TermControl::_coreConnectionStateChanged });
|
||||
|
||||
// These callbacks can only really be triggered by UI interactions. So
|
||||
// they don't need weak refs - they can't be triggered unless we're
|
||||
// alive.
|
||||
@@ -528,11 +500,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
TermControl::~TermControl()
|
||||
{
|
||||
if (_contentIsOutOfProc())
|
||||
{
|
||||
_contentWaitInterrupt.SetEvent();
|
||||
_contentWaitThread.join();
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -559,7 +526,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
margins.Right,
|
||||
margins.Bottom };
|
||||
_automationPeer = winrt::make<implementation::TermControlAutomationPeer>(this, padding, interactivityAutoPeer);
|
||||
interactivityAutoPeer.SetParentProvider(_automationPeer.GetParentProvider());
|
||||
return _automationPeer;
|
||||
}
|
||||
}
|
||||
@@ -580,12 +546,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
TerminalConnection::ConnectionState TermControl::ConnectionState() const
|
||||
{
|
||||
try
|
||||
{
|
||||
return _core.ConnectionState();
|
||||
}
|
||||
CATCH_LOG();
|
||||
return TerminalConnection::ConnectionState::Closed;
|
||||
return _core.ConnectionState();
|
||||
}
|
||||
|
||||
winrt::fire_and_forget TermControl::RenderEngineSwapChainChanged(IInspectable /*sender*/, IInspectable /*args*/)
|
||||
@@ -599,10 +560,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
if (auto control{ weakThis.get() })
|
||||
{
|
||||
// TODO! very good chance we leak this handle
|
||||
const HANDLE chainHandle = reinterpret_cast<HANDLE>(control->_contentIsOutOfProc() ?
|
||||
control->_contentProc.RequestSwapChainHandle(GetCurrentProcessId()) :
|
||||
control->_core.SwapChainHandle());
|
||||
const HANDLE chainHandle = reinterpret_cast<HANDLE>(control->_core.SwapChainHandle());
|
||||
_AttachDxgiSwapChainToXaml(chainHandle);
|
||||
}
|
||||
}
|
||||
@@ -690,11 +648,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
_interactivity.Initialize();
|
||||
|
||||
// TODO! very good chance we leak this handle
|
||||
const HANDLE chainHandle = reinterpret_cast<HANDLE>(_contentIsOutOfProc() ?
|
||||
_contentProc.RequestSwapChainHandle(GetCurrentProcessId()) :
|
||||
_core.SwapChainHandle());
|
||||
_AttachDxgiSwapChainToXaml(chainHandle);
|
||||
_AttachDxgiSwapChainToXaml(reinterpret_cast<HANDLE>(_core.SwapChainHandle()));
|
||||
|
||||
// Tell the DX Engine to notify us when the swap chain changes. We do
|
||||
// this after we initially set the swapchain so as to avoid unnecessary
|
||||
@@ -791,12 +745,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_HidePointerCursorHandlers(*this, nullptr);
|
||||
|
||||
const auto ch = e.Character();
|
||||
const auto scanCode = gsl::narrow_cast<WORD>(e.KeyStatus().ScanCode);
|
||||
const auto keyStatus = e.KeyStatus();
|
||||
const auto scanCode = gsl::narrow_cast<WORD>(keyStatus.ScanCode);
|
||||
auto modifiers = _GetPressedModifierKeys();
|
||||
if (e.KeyStatus().IsExtendedKey)
|
||||
|
||||
if (keyStatus.IsExtendedKey)
|
||||
{
|
||||
modifiers |= ControlKeyStates::EnhancedKey;
|
||||
}
|
||||
|
||||
const bool handled = _core.SendCharEvent(ch, scanCode, modifiers);
|
||||
e.Handled(handled);
|
||||
}
|
||||
@@ -886,6 +843,28 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
return;
|
||||
}
|
||||
|
||||
const auto keyStatus = e.KeyStatus();
|
||||
const auto vkey = gsl::narrow_cast<WORD>(e.OriginalKey());
|
||||
const auto scanCode = gsl::narrow_cast<WORD>(keyStatus.ScanCode);
|
||||
auto modifiers = _GetPressedModifierKeys();
|
||||
|
||||
if (keyStatus.IsExtendedKey)
|
||||
{
|
||||
modifiers |= ControlKeyStates::EnhancedKey;
|
||||
}
|
||||
|
||||
// GH#11076:
|
||||
// For some weird reason we sometimes receive a WM_KEYDOWN
|
||||
// message without vkey or scanCode if a user drags a tab.
|
||||
// The KeyChord constructor has a debug assertion ensuring that all KeyChord
|
||||
// either have a valid vkey/scanCode. This is important, because this prevents
|
||||
// accidential insertion of invalid KeyChords into classes like ActionMap.
|
||||
if (!vkey && !scanCode)
|
||||
{
|
||||
e.Handled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark the event as handled and do nothing if we're closing, or the key
|
||||
// was the Windows key.
|
||||
//
|
||||
@@ -894,19 +873,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// win32-input-mode, then we'll send all these keystrokes to the
|
||||
// terminal - it's smart enough to ignore the keys it doesn't care
|
||||
// about.
|
||||
if (_IsClosing() ||
|
||||
e.OriginalKey() == VirtualKey::LeftWindows ||
|
||||
e.OriginalKey() == VirtualKey::RightWindows)
|
||||
|
||||
if (_IsClosing() || vkey == VK_LWIN || vkey == VK_RWIN)
|
||||
{
|
||||
e.Handled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
auto modifiers = _GetPressedModifierKeys();
|
||||
const auto vkey = gsl::narrow_cast<WORD>(e.OriginalKey());
|
||||
const auto scanCode = gsl::narrow_cast<WORD>(e.KeyStatus().ScanCode);
|
||||
|
||||
// Short-circuit isReadOnly check to avoid warning dialog
|
||||
if (_core.IsInReadOnlyMode())
|
||||
{
|
||||
@@ -914,18 +886,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.KeyStatus().IsExtendedKey)
|
||||
{
|
||||
modifiers |= ControlKeyStates::EnhancedKey;
|
||||
}
|
||||
|
||||
// Alt-Numpad# input will send us a character once the user releases
|
||||
// Alt, so we should be ignoring the individual keydowns. The character
|
||||
// will be sent through the TSFInputControl. See GH#1401 for more
|
||||
// details
|
||||
if (modifiers.IsAltPressed() &&
|
||||
(e.OriginalKey() >= VirtualKey::NumberPad0 && e.OriginalKey() <= VirtualKey::NumberPad9))
|
||||
|
||||
(vkey >= VK_NUMPAD0 && vkey <= VK_NUMPAD9))
|
||||
{
|
||||
e.Handled(true);
|
||||
return;
|
||||
@@ -946,6 +912,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
return;
|
||||
}
|
||||
|
||||
if (vkey == VK_SPACE && modifiers.IsAltPressed() && !modifiers.IsAltGrPressed())
|
||||
{
|
||||
if (const auto bindings = _settings.KeyBindings())
|
||||
{
|
||||
if (!bindings.IsKeyChordExplicitlyUnbound({ modifiers.IsCtrlPressed(), modifiers.IsAltPressed(), modifiers.IsShiftPressed(), modifiers.IsWinPressed(), vkey, scanCode }))
|
||||
{
|
||||
// If we get here, it means that
|
||||
// 1. we do not have a command bound to alt+space
|
||||
// 2. alt+space was not explicitly unbound
|
||||
// That means that XAML handled the alt+space to open up the context menu, and
|
||||
// so we don't want to send anything to the terminal
|
||||
// TODO GH#11018: Add a new "openSystemMenu" keybinding
|
||||
e.Handled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_TrySendKeyEvent(vkey, scanCode, modifiers, keyDown))
|
||||
{
|
||||
e.Handled(true);
|
||||
@@ -955,7 +939,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// Manually prevent keyboard navigation with tab. We want to send tab to
|
||||
// the terminal, and we don't want to be able to escape focus of the
|
||||
// control with tab.
|
||||
e.Handled(e.OriginalKey() == VirtualKey::Tab);
|
||||
e.Handled(vkey == VK_TAB);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -997,7 +981,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// Arguments:
|
||||
// - vkey: The vkey of the key pressed.
|
||||
// - scanCode: The scan code of the key pressed.
|
||||
void TermControl::_ClearKeyboardState(const WORD vkey, const WORD scanCode) const noexcept
|
||||
void TermControl::_ClearKeyboardState(const WORD vkey, const WORD scanCode) noexcept
|
||||
{
|
||||
std::array<BYTE, 256> keyState;
|
||||
if (!GetKeyboardState(keyState.data()))
|
||||
@@ -1780,10 +1764,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// Disconnect the TSF input control so it doesn't receive EditContext events.
|
||||
TSFInputControl().Close();
|
||||
_autoScrollTimer.Stop();
|
||||
if (!_contentIsOutOfProc())
|
||||
{
|
||||
_core.Close();
|
||||
}
|
||||
|
||||
_core.Close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2073,7 +2055,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// don't necessarily include that state.
|
||||
// Return Value:
|
||||
// - The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states.
|
||||
ControlKeyStates TermControl::_GetPressedModifierKeys() const
|
||||
ControlKeyStates TermControl::_GetPressedModifierKeys() noexcept
|
||||
{
|
||||
const CoreWindow window = CoreWindow::GetForCurrentThread();
|
||||
// DONT USE
|
||||
@@ -2616,8 +2598,4 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
_playWarningBell->Run();
|
||||
}
|
||||
void TermControl::_coreConnectionStateChanged(const IInspectable& /*sender*/, const IInspectable& /*args*/)
|
||||
{
|
||||
_ConnectionStateChangedHandlers(*this, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
struct TermControl : TermControlT<TermControl>
|
||||
{
|
||||
TermControl(winrt::guid contentGuid, IControlSettings settings, TerminalConnection::ITerminalConnection connection);
|
||||
TermControl(IControlSettings settings, TerminalConnection::ITerminalConnection connection);
|
||||
|
||||
winrt::fire_and_forget UpdateSettings();
|
||||
@@ -71,7 +70,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
winrt::fire_and_forget _RendererEnteredErrorState(IInspectable sender, IInspectable args);
|
||||
|
||||
void _RenderRetryButton_Click(IInspectable const& button, IInspectable const& args);
|
||||
void _ContentDiedCloseButton_Click(IInspectable const& button, IInspectable const& args);
|
||||
winrt::fire_and_forget _RendererWarning(IInspectable sender,
|
||||
Control::RendererWarningArgs args);
|
||||
|
||||
@@ -117,7 +115,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
PROJECTED_FORWARDED_TYPED_EVENT(TitleChanged, IInspectable, Control::TitleChangedEventArgs, _core, TitleChanged);
|
||||
PROJECTED_FORWARDED_TYPED_EVENT(TabColorChanged, IInspectable, IInspectable, _core, TabColorChanged);
|
||||
PROJECTED_FORWARDED_TYPED_EVENT(SetTaskbarProgress, IInspectable, IInspectable, _core, TaskbarProgressChanged);
|
||||
TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable);
|
||||
PROJECTED_FORWARDED_TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable, _core, ConnectionStateChanged);
|
||||
|
||||
PROJECTED_FORWARDED_TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs, _interactivity, PasteFromClipboard);
|
||||
|
||||
@@ -147,7 +145,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
Control::TermControlAutomationPeer _automationPeer{ nullptr };
|
||||
Control::ControlInteractivity _interactivity{ nullptr };
|
||||
Control::ControlCore _core{ nullptr };
|
||||
Control::ContentProcess _contentProc{ nullptr };
|
||||
|
||||
winrt::com_ptr<SearchBoxControl> _searchBox;
|
||||
|
||||
@@ -186,11 +183,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker;
|
||||
|
||||
wil::unique_event _contentWaitInterrupt;
|
||||
std::thread _contentWaitThread;
|
||||
void _createContentWaitThread();
|
||||
bool _contentIsOutOfProc() const;
|
||||
|
||||
inline bool _IsClosing() const noexcept
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
@@ -254,9 +246,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void _UpdateAutoScroll(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
|
||||
|
||||
void _KeyHandler(Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e, const bool keyDown);
|
||||
::Microsoft::Terminal::Core::ControlKeyStates _GetPressedModifierKeys() const;
|
||||
static ::Microsoft::Terminal::Core::ControlKeyStates _GetPressedModifierKeys() noexcept;
|
||||
bool _TryHandleKeyBinding(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers) const;
|
||||
void _ClearKeyboardState(const WORD vkey, const WORD scanCode) const noexcept;
|
||||
static void _ClearKeyboardState(const WORD vkey, const WORD scanCode) noexcept;
|
||||
bool _TrySendKeyEvent(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const bool keyDown);
|
||||
|
||||
const til::point _toTerminalOrigin(winrt::Windows::Foundation::Point cursorPosition);
|
||||
@@ -278,8 +270,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
winrt::fire_and_forget _coreTransparencyChanged(IInspectable sender, Control::TransparencyChangedEventArgs args);
|
||||
void _coreRaisedNotice(const IInspectable& s, const Control::NoticeEventArgs& args);
|
||||
void _coreWarningBell(const IInspectable& sender, const IInspectable& args);
|
||||
winrt::fire_and_forget _raiseContentDied();
|
||||
void _coreConnectionStateChanged(const IInspectable& sender, const IInspectable& args);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -15,10 +15,6 @@ namespace Microsoft.Terminal.Control
|
||||
IMouseWheelListener,
|
||||
ICoreState
|
||||
{
|
||||
TermControl(Guid contentGuid,
|
||||
IControlSettings settings,
|
||||
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
|
||||
|
||||
TermControl(IControlSettings settings,
|
||||
Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
|
||||
|
||||
|
||||
@@ -130,27 +130,6 @@
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<Grid x:Name="ContentDiedNotice"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
x:Load="False">
|
||||
<Border Margin="8,8,8,8"
|
||||
Padding="8,8,8,8"
|
||||
Background="{ThemeResource SystemControlBackgroundAltHighBrush}"
|
||||
BorderBrush="{ThemeResource SystemAccentColor}"
|
||||
BorderThickness="2,2,2,2"
|
||||
CornerRadius="{ThemeResource OverlayCornerRadius}">
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="TermControl_ContentDiedTextBlock"
|
||||
HorizontalAlignment="Center"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<Button x:Uid="TermControl_ContentDiedButton"
|
||||
HorizontalAlignment="Right"
|
||||
Click="_ContentDiedCloseButton_Click" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
|
||||
@@ -45,6 +45,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_contentAutomationPeer.SelectionChanged([this](auto&&, auto&&) { SignalSelectionChanged(); });
|
||||
_contentAutomationPeer.TextChanged([this](auto&&, auto&&) { SignalTextChanged(); });
|
||||
_contentAutomationPeer.CursorChanged([this](auto&&, auto&&) { SignalCursorChanged(); });
|
||||
_contentAutomationPeer.ParentProvider(*this);
|
||||
};
|
||||
|
||||
// Method Description:
|
||||
@@ -67,12 +68,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_contentAutomationPeer.SetControlPadding(padding);
|
||||
}
|
||||
|
||||
XamlAutomation::IRawElementProviderSimple TermControlAutomationPeer::GetParentProvider()
|
||||
{
|
||||
const auto parentProvider = this->ProviderFromPeer(*this);
|
||||
return parentProvider;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Signals the ui automation client that the terminal's selection has changed and should be updated
|
||||
// Arguments:
|
||||
@@ -159,8 +154,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
hstring TermControlAutomationPeer::GetLocalizedControlTypeCore() const
|
||||
{
|
||||
// return RS_(L"TerminalControl_ControlType");
|
||||
return L"foo";
|
||||
return RS_(L"TerminalControl_ControlType");
|
||||
}
|
||||
|
||||
Windows::Foundation::IInspectable TermControlAutomationPeer::GetPatternCore(PatternInterface patternInterface) const
|
||||
@@ -233,30 +227,4 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
// Method Description:
|
||||
// - extracts the UiaTextRanges from the SAFEARRAY and converts them to Xaml ITextRangeProviders
|
||||
// Arguments:
|
||||
// - SAFEARRAY of UIA::UiaTextRange (ITextRangeProviders)
|
||||
// Return Value:
|
||||
// - com_array of Xaml Wrapped UiaTextRange (ITextRangeProviders)
|
||||
com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::WrapArrayOfTextRangeProviders(SAFEARRAY* textRanges)
|
||||
{
|
||||
// transfer ownership of UiaTextRanges to this new vector
|
||||
auto providers = SafeArrayToOwningVector<::Microsoft::Terminal::TermControlUiaTextRange>(textRanges);
|
||||
int count = gsl::narrow<int>(providers.size());
|
||||
|
||||
std::vector<XamlAutomation::ITextRangeProvider> vec;
|
||||
vec.reserve(count);
|
||||
auto parentProvider = this->ProviderFromPeer(*this);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
auto xutr = make_self<XamlUiaTextRange>(providers[i].detach(), parentProvider);
|
||||
vec.emplace_back(xutr.as<XamlAutomation::ITextRangeProvider>());
|
||||
}
|
||||
|
||||
com_array<XamlAutomation::ITextRangeProvider> result{ vec };
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
void UpdateControlBounds();
|
||||
void SetControlPadding(const Core::Padding padding);
|
||||
Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple GetParentProvider();
|
||||
|
||||
#pragma region FrameworkElementAutomationPeer
|
||||
hstring GetClassNameCore() const;
|
||||
@@ -79,7 +78,5 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
private:
|
||||
winrt::Microsoft::Terminal::Control::implementation::TermControl* _termControl;
|
||||
Control::InteractivityAutomationPeer _contentAutomationPeer;
|
||||
|
||||
winrt::com_array<Windows::UI::Xaml::Automation::Provider::ITextRangeProvider> WrapArrayOfTextRangeProviders(SAFEARRAY* textRanges);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,6 +12,5 @@ namespace Microsoft.Terminal.Control
|
||||
|
||||
void UpdateControlBounds();
|
||||
void SetControlPadding(Microsoft.Terminal.Core.Padding padding);
|
||||
Windows.UI.Xaml.Automation.Provider.IRawElementProviderSimple GetParentProvider();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// The functions in this class are specific to the handling of out-of-proc
|
||||
// content processes by the TermControl. Putting them all in one file keeps
|
||||
// TermControl.cpp a little less cluttered.
|
||||
|
||||
#include "pch.h"
|
||||
#include "TermControl.h"
|
||||
|
||||
using namespace ::Microsoft::Console::Types;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Core;
|
||||
using namespace winrt::Windows::System;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
bool TermControl::_contentIsOutOfProc() const
|
||||
{
|
||||
return _contentProc != nullptr;
|
||||
}
|
||||
|
||||
bool s_waitOnContentProcess(uint64_t contentPid, HANDLE contentWaitInterrupt)
|
||||
{
|
||||
// This is the array of HANDLEs that we're going to wait on in
|
||||
// WaitForMultipleObjects below.
|
||||
// * waits[0] will be the handle to the content process. It gets
|
||||
// signalled when the process exits / dies.
|
||||
// * waits[1] is the handle to our _contentWaitInterrupt event. Another
|
||||
// thread can use that to manually break this loop. We'll do that when
|
||||
// we're getting torn down.
|
||||
HANDLE waits[2];
|
||||
waits[1] = contentWaitInterrupt;
|
||||
bool displayError = true;
|
||||
|
||||
// At any point in all this, the content process might die. If it does,
|
||||
// we want to raise an error message, to inform that this control is now
|
||||
// dead.
|
||||
try
|
||||
{
|
||||
// This might fail to even ask the content for it's PID.
|
||||
wil::unique_handle hContent{ OpenProcess(PROCESS_ALL_ACCESS,
|
||||
FALSE,
|
||||
static_cast<DWORD>(contentPid)) };
|
||||
|
||||
// If we fail to open the content, then they don't exist
|
||||
// anymore! We'll need to immediately raise the notification that the content has died.
|
||||
if (hContent.get() == nullptr)
|
||||
{
|
||||
const auto gle = GetLastError();
|
||||
TraceLoggingWrite(g_hTerminalControlProvider,
|
||||
"TermControl_FailedToOpenContent",
|
||||
TraceLoggingUInt64(gle, "lastError", "The result of GetLastError"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
return displayError;
|
||||
}
|
||||
|
||||
waits[0] = hContent.get();
|
||||
|
||||
switch (WaitForMultipleObjects(2, waits, FALSE, INFINITE))
|
||||
{
|
||||
case WAIT_OBJECT_0 + 0: // waits[0] was signaled, the handle to the content process
|
||||
|
||||
TraceLoggingWrite(g_hTerminalControlProvider,
|
||||
"TermControl_ContentDied",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
break;
|
||||
|
||||
case WAIT_OBJECT_0 + 1: // waits[1] was signaled, our manual interrupt
|
||||
|
||||
TraceLoggingWrite(g_hTerminalControlProvider,
|
||||
"TermControl_ContentWaitInterrupted",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
displayError = false;
|
||||
break;
|
||||
|
||||
case WAIT_TIMEOUT:
|
||||
// This should be impossible.
|
||||
TraceLoggingWrite(g_hTerminalControlProvider,
|
||||
"TermControl_ContentWaitTimeout",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
// Returning any other value is invalid. Just die.
|
||||
const auto gle = GetLastError();
|
||||
TraceLoggingWrite(g_hTerminalControlProvider,
|
||||
"TermControl_WaitFailed",
|
||||
TraceLoggingUInt64(gle, "lastError", "The result of GetLastError"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Theoretically, if window[1] dies when we're trying to get
|
||||
// it's PID we'll get here. We can probably just exit here.
|
||||
|
||||
TraceLoggingWrite(g_hTerminalControlProvider,
|
||||
"TermControl_ExceptionInWaitThread",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
return displayError;
|
||||
}
|
||||
|
||||
void TermControl::_createContentWaitThread()
|
||||
{
|
||||
_contentWaitThread = std::thread([weakThis = get_weak(), contentPid = _contentProc.GetPID(), contentWaitInterrupt = _contentWaitInterrupt.get()] {
|
||||
if (s_waitOnContentProcess(contentPid, contentWaitInterrupt))
|
||||
{
|
||||
// When s_waitOnContentProcess returns, if it returned true, we
|
||||
// should display a dialog in our bounds to indicate that we
|
||||
// were closed unexpectedly. If we closed in an expected way,
|
||||
// then s_waitOnContentProcess will return false.
|
||||
if (auto control{ weakThis.get() })
|
||||
{
|
||||
control->_raiseContentDied();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
winrt::fire_and_forget TermControl::_raiseContentDied()
|
||||
{
|
||||
auto weakThis{ get_weak() };
|
||||
co_await winrt::resume_foreground(Dispatcher());
|
||||
|
||||
if (auto control{ weakThis.get() })
|
||||
{
|
||||
if (auto loadedUiElement{ FindName(L"ContentDiedNotice") })
|
||||
{
|
||||
if (auto uiElement{ loadedUiElement.try_as<::winrt::Windows::UI::Xaml::UIElement>() })
|
||||
{
|
||||
uiElement.Visibility(Visibility::Visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Method Description:
|
||||
// - Handler for when the "Content Died" dialog's button is clicked.
|
||||
void TermControl::_ContentDiedCloseButton_Click(IInspectable const& /*sender*/, IInspectable const& /*args*/)
|
||||
{
|
||||
// Alert whoever's hosting us that the connection was closed.
|
||||
// When they come asking what the new connection state is, we'll reply with Closed
|
||||
_ConnectionStateChangedHandlers(*this, nullptr);
|
||||
}
|
||||
}
|
||||
@@ -28,9 +28,6 @@
|
||||
<!-- ========================= Headers ======================== -->
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="ContentProcess.h">
|
||||
<DependentUpon>ContentProcess.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ControlCore.h">
|
||||
<DependentUpon>ControlCore.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
@@ -61,18 +58,13 @@
|
||||
<ClInclude Include="TSFInputControl.h">
|
||||
<DependentUpon>TSFInputControl.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="XamlUiaTextRange.h" >
|
||||
<DependentUpon>XamlUiaTextRange.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="XamlUiaTextRange.h" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= Cpp Files ======================== -->
|
||||
<ItemGroup>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ContentProcess.cpp">
|
||||
<DependentUpon>ContentProcess.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ControlCore.cpp">
|
||||
<DependentUpon>ControlCore.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
@@ -95,9 +87,6 @@
|
||||
<ClCompile Include="TermControl.cpp">
|
||||
<DependentUpon>TermControl.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TermControlContentManagement.cpp">
|
||||
<DependentUpon>TermControl.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TSFInputControl.cpp">
|
||||
<DependentUpon>TSFInputControl.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
@@ -108,13 +97,10 @@
|
||||
<ClCompile Include="InteractivityAutomationPeer.cpp">
|
||||
<DependentUpon>InteractivityAutomationPeer.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="XamlUiaTextRange.cpp" >
|
||||
<DependentUpon>XamlUiaTextRange.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="XamlUiaTextRange.cpp" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= idl Files ======================== -->
|
||||
<ItemGroup>
|
||||
<Midl Include="ContentProcess.idl" />
|
||||
<Midl Include="ControlCore.idl" />
|
||||
<Midl Include="ControlInteractivity.idl" />
|
||||
<Midl Include="ICoreState.idl" />
|
||||
@@ -137,7 +123,6 @@
|
||||
<Midl Include="TSFInputControl.idl">
|
||||
<DependentUpon>TSFInputControl.xaml</DependentUpon>
|
||||
</Midl>
|
||||
<Midl Include="XamlUiaTextRange.idl" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= XAML Files ======================== -->
|
||||
<ItemGroup>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "../types/TermControlUiaTextRange.hpp"
|
||||
#include <UIAutomationClient.h>
|
||||
#include <UIAutomationCoreApi.h>
|
||||
#include "../types/UiaTracing.h"
|
||||
|
||||
// the same as COR_E_NOTSUPPORTED
|
||||
// we don't want to import the CLR headers to get it
|
||||
@@ -182,6 +183,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
XamlAutomation::IRawElementProviderSimple XamlUiaTextRange::GetEnclosingElement()
|
||||
{
|
||||
::Microsoft::Console::Types::UiaTracing::TextRange::GetEnclosingElement(*static_cast<::Microsoft::Console::Types::UiaTextRangeBase*>(_uiaProvider.get()));
|
||||
return _parentProvider;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,13 +21,13 @@ Author(s):
|
||||
#pragma once
|
||||
|
||||
#include "TermControlAutomationPeer.h"
|
||||
#include "XamlUiaTextRange.g.h"
|
||||
#include <UIAutomationCore.h>
|
||||
#include "../types/TermControlUiaTextRange.hpp"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
class XamlUiaTextRange : public XamlUiaTextRangeT<XamlUiaTextRange>
|
||||
class XamlUiaTextRange :
|
||||
public winrt::implements<XamlUiaTextRange, Windows::UI::Xaml::Automation::Provider::ITextRangeProvider>
|
||||
{
|
||||
public:
|
||||
XamlUiaTextRange(::ITextRangeProvider* uiaProvider, Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple parentProvider) :
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Terminal.Control
|
||||
{
|
||||
[default_interface] runtimeclass XamlUiaTextRange :
|
||||
Windows.UI.Xaml.Automation.Provider.ITextRangeProvider
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -849,7 +849,13 @@ WORD Terminal::_TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept
|
||||
// will release this lock when it's destructed.
|
||||
[[nodiscard]] std::unique_lock<til::ticket_lock> Terminal::LockForReading()
|
||||
{
|
||||
#ifdef NDEBUG
|
||||
return std::unique_lock{ _readWriteLock };
|
||||
#else
|
||||
auto lock = std::unique_lock{ _readWriteLock };
|
||||
_lastLocker = GetCurrentThreadId();
|
||||
return lock;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -859,7 +865,13 @@ WORD Terminal::_TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept
|
||||
// will release this lock when it's destructed.
|
||||
[[nodiscard]] std::unique_lock<til::ticket_lock> Terminal::LockForWriting()
|
||||
{
|
||||
#ifdef NDEBUG
|
||||
return std::unique_lock{ _readWriteLock };
|
||||
#else
|
||||
auto lock = std::unique_lock{ _readWriteLock };
|
||||
_lastLocker = GetCurrentThreadId();
|
||||
return lock;
|
||||
#endif
|
||||
}
|
||||
|
||||
Viewport Terminal::_GetMutableViewport() const noexcept
|
||||
|
||||
@@ -199,6 +199,7 @@ public:
|
||||
const COORD GetSelectionEnd() const noexcept override;
|
||||
const std::wstring_view GetConsoleTitle() const noexcept override;
|
||||
void ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute) override;
|
||||
const bool IsUiaDataInitialized() const noexcept override;
|
||||
#pragma endregion
|
||||
|
||||
void SetWriteInputCallback(std::function<void(std::wstring&)> pfn) noexcept;
|
||||
@@ -254,6 +255,9 @@ private:
|
||||
// But we can abuse the fact that the surrounding members rarely change and are huge
|
||||
// (std::function is like 64 bytes) to create some natural padding without wasting space.
|
||||
til::ticket_lock _readWriteLock;
|
||||
#ifndef NDEBUG
|
||||
DWORD _lastLocker;
|
||||
#endif
|
||||
|
||||
std::function<void(const int, const int, const int)> _pfnScrollPositionChanged;
|
||||
std::function<void(const til::color)> _pfnBackgroundColorChanged;
|
||||
|
||||
@@ -233,6 +233,9 @@ catch (...)
|
||||
void Terminal::LockConsole() noexcept
|
||||
{
|
||||
_readWriteLock.lock();
|
||||
#ifndef NDEBUG
|
||||
_lastLocker = GetCurrentThreadId();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -250,3 +253,12 @@ bool Terminal::IsScreenReversed() const noexcept
|
||||
{
|
||||
return _screenReversed;
|
||||
}
|
||||
|
||||
const bool Terminal::IsUiaDataInitialized() const noexcept
|
||||
{
|
||||
// GH#11135: Windows Terminal needs to create and return an automation peer
|
||||
// when a screen reader requests it. However, the terminal might not be fully
|
||||
// initialized yet. So we use this to check if any crucial components of
|
||||
// UiaData are not yet initialized.
|
||||
return !!_buffer;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
<StackPanel Margin="{StaticResource StandardControlMargin}">
|
||||
<local:SettingContainer x:Uid="AddProfile_Duplicate">
|
||||
<muxc:RadioButtons x:Name="Profiles"
|
||||
AutomationProperties.AccessibilityView="Content"
|
||||
ItemsSource="{x:Bind State.Settings.AllProfiles, Mode=OneWay}">
|
||||
<muxc:RadioButtons.ItemTemplate>
|
||||
<DataTemplate x:DataType="model:Profile">
|
||||
|
||||
@@ -163,7 +163,8 @@
|
||||
ClearSettingValue="{x:Bind Appearance.ClearCursorShape}"
|
||||
HasSettingValue="{x:Bind Appearance.HasCursorShape, Mode=OneWay}"
|
||||
SettingOverrideSource="{x:Bind Appearance.CursorShapeOverrideSource, Mode=OneWay}">
|
||||
<muxc:RadioButtons ItemTemplate="{StaticResource EnumRadioButtonTemplate}"
|
||||
<muxc:RadioButtons AutomationProperties.AccessibilityView="Content"
|
||||
ItemTemplate="{StaticResource EnumRadioButtonTemplate}"
|
||||
ItemsSource="{x:Bind CursorShapeList, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind CurrentCursorShape, Mode=TwoWay}" />
|
||||
</local:SettingContainer>
|
||||
@@ -207,7 +208,7 @@
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox IsEnabled="{x:Bind local:Converters.StringsAreNotEqual('desktopWallpaper', Appearance.BackgroundImagePath), Mode=OneWay}"
|
||||
Style="{StaticResource TextBoxSettingStyle}"
|
||||
Text="{x:Bind local:Converters.StringFallBackToEmptyString('desktopWallpaper', Appearance.BackgroundImagePath), Mode=TwoWay, BindBack=Appearance.SetBackgroundImagePath}" />
|
||||
Text="{x:Bind local:Converters.StringOrEmptyIfPlaceholder('desktopWallpaper', Appearance.BackgroundImagePath), Mode=TwoWay, BindBack=Appearance.SetBackgroundImagePath}" />
|
||||
<Button x:Uid="Profile_BackgroundImageBrowse"
|
||||
Click="BackgroundImage_Click"
|
||||
IsEnabled="{x:Bind local:Converters.StringsAreNotEqual('desktopWallpaper', Appearance.BackgroundImagePath), Mode=OneWay}"
|
||||
@@ -225,7 +226,8 @@
|
||||
HasSettingValue="{x:Bind Appearance.HasBackgroundImageStretchMode, Mode=OneWay}"
|
||||
SettingOverrideSource="{x:Bind Appearance.BackgroundImageStretchModeOverrideSource, Mode=OneWay}"
|
||||
Visibility="{x:Bind Appearance.BackgroundImageSettingsVisible, Mode=OneWay}">
|
||||
<muxc:RadioButtons ItemTemplate="{StaticResource EnumRadioButtonTemplate}"
|
||||
<muxc:RadioButtons AutomationProperties.AccessibilityView="Content"
|
||||
ItemTemplate="{StaticResource EnumRadioButtonTemplate}"
|
||||
ItemsSource="{x:Bind BackgroundImageStretchModeList, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind CurrentBackgroundImageStretchMode, Mode=TwoWay}" />
|
||||
</local:SettingContainer>
|
||||
@@ -440,7 +442,8 @@
|
||||
ClearSettingValue="{x:Bind Appearance.ClearIntenseTextStyle}"
|
||||
HasSettingValue="{x:Bind Appearance.HasIntenseTextStyle, Mode=OneWay}"
|
||||
SettingOverrideSource="{x:Bind Appearance.IntenseTextStyleOverrideSource, Mode=OneWay}">
|
||||
<muxc:RadioButtons ItemTemplate="{StaticResource EnumRadioButtonTemplate}"
|
||||
<muxc:RadioButtons AutomationProperties.AccessibilityView="Content"
|
||||
ItemTemplate="{StaticResource EnumRadioButtonTemplate}"
|
||||
ItemsSource="{x:Bind IntenseTextStyleList, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind CurrentIntenseTextStyle, Mode=TwoWay}" />
|
||||
</local:SettingContainer>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "pch.h"
|
||||
#include "pch.h"
|
||||
#include "Converters.h"
|
||||
#if __has_include("Converters.g.cpp")
|
||||
#include "Converters.g.cpp"
|
||||
@@ -99,8 +99,16 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
return value.empty() ? winrt::Windows::UI::Xaml::Visibility::Collapsed : winrt::Windows::UI::Xaml::Visibility::Visible;
|
||||
}
|
||||
winrt::hstring Converters::StringFallBackToEmptyString(winrt::hstring expected, winrt::hstring actual)
|
||||
|
||||
// Method Description:
|
||||
// - Returns the value string, unless it matches the placeholder in which case the empty string.
|
||||
// Arguments:
|
||||
// - placeholder - the placeholder string.
|
||||
// - value - the value string.
|
||||
// Return Value:
|
||||
// - The value string, unless it matches the placeholder in which case the empty string.
|
||||
winrt::hstring Converters::StringOrEmptyIfPlaceholder(winrt::hstring placeholder, winrt::hstring value)
|
||||
{
|
||||
return expected == actual ? expected : L"";
|
||||
return placeholder == value ? L"" : value;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user