diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt index e7ffdca52..eb9b2e277 100644 --- a/.github/actions/spelling/allow/allow.txt +++ b/.github/actions/spelling/allow/allow.txt @@ -1,6 +1,7 @@ apc calt ccmp +cybersecurity Apc clickable clig diff --git a/.github/actions/spelling/expect/web.txt b/.github/actions/spelling/expect/web.txt index f50d31a6e..3072b0075 100644 --- a/.github/actions/spelling/expect/web.txt +++ b/.github/actions/spelling/expect/web.txt @@ -11,6 +11,7 @@ leonerd fixterms winui appshellintegration +mdtauk cppreference gfycat Guake diff --git a/build/Helix/OutputTestResults.ps1 b/build/Helix/OutputTestResults.ps1 index 19aa8441b..eee4c5bae 100644 --- a/build/Helix/OutputTestResults.ps1 +++ b/build/Helix/OutputTestResults.ps1 @@ -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) { diff --git a/build/Helix/ProcessHelixFiles.ps1 b/build/Helix/ProcessHelixFiles.ps1 index f74187219..dcd3608a5 100644 --- a/build/Helix/ProcessHelixFiles.ps1 +++ b/build/Helix/ProcessHelixFiles.ps1 @@ -20,13 +20,31 @@ function Generate-File-Links Out-File -FilePath $helixLinkFile -Append -InputObject "" Out-File -FilePath $helixLinkFile -Append -InputObject "" } } +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 } } diff --git a/build/Helix/UpdateUnreliableTests.ps1 b/build/Helix/UpdateUnreliableTests.ps1 index 06af44f69..ecf4e8bf7 100644 --- a/build/Helix/UpdateUnreliableTests.ps1 +++ b/build/Helix/UpdateUnreliableTests.ps1 @@ -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 diff --git a/build/pipelines/release.yml b/build/pipelines/release.yml index e200db166..3c922be02 100644 --- a/build/pipelines/release.yml +++ b/build/pipelines/release.yml @@ -152,7 +152,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 +194,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 diff --git a/build/pipelines/templates/build-console-pgo.yml b/build/pipelines/templates/build-console-pgo.yml index 1e33e82c8..d3dbaa99c 100644 --- a/build/pipelines/templates/build-console-pgo.yml +++ b/build/pipelines/templates/build-console-pgo.yml @@ -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 }} diff --git a/build/pipelines/templates/check-formatting.yml b/build/pipelines/templates/check-formatting.yml index f8b513cf4..f487a7a81 100644 --- a/build/pipelines/templates/check-formatting.yml +++ b/build/pipelines/templates/check-formatting.yml @@ -11,7 +11,7 @@ jobs: clean: true - task: PowerShell@2 - displayName: 'Code Formattting Check' + displayName: 'Code Formatting Check' inputs: targetType: filePath filePath: '.\build\scripts\Invoke-FormattingCheck.ps1' diff --git a/build/pipelines/templates/helix-processtestresults-job.yml b/build/pipelines/templates/helix-processtestresults-job.yml index 9d301e9cb..1e5ff3c73 100644 --- a/build/pipelines/templates/helix-processtestresults-job.yml +++ b/build/pipelines/templates/helix-processtestresults-job.yml @@ -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 diff --git a/build/pipelines/templates/helix-runtests-job.yml b/build/pipelines/templates/helix-runtests-job.yml index d429687be..042c54c12 100644 --- a/build/pipelines/templates/helix-runtests-job.yml +++ b/build/pipelines/templates/helix-runtests-job.yml @@ -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)' diff --git a/build/pipelines/templates/pgo-build-and-publish-nuget-job.yml b/build/pipelines/templates/pgo-build-and-publish-nuget-job.yml index 85bc6c353..92484ebf3 100644 --- a/build/pipelines/templates/pgo-build-and-publish-nuget-job.yml +++ b/build/pipelines/templates/pgo-build-and-publish-nuget-job.yml @@ -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 \ No newline at end of file + 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' \ No newline at end of file diff --git a/custom.props b/custom.props index fa5ee678f..95b29b746 100644 --- a/custom.props +++ b/custom.props @@ -5,7 +5,7 @@ true 2021 1 - 11 + 12 Windows Terminal diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 329e73b41..424661ac2 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -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" @@ -253,6 +269,7 @@ "movePane", "swapPane", "moveTab", + "multipleActions", "newTab", "newWindow", "nextTab", @@ -300,7 +317,8 @@ "down", "previous", "nextInOrder", - "previousInOrder" + "previousInOrder", + "first" ], "type": "string" }, @@ -809,6 +827,24 @@ ], "required": [ "direction" ] }, + "MultipleActionsAction": { + "description": "Arguments for the multiple actions command", + "allOf": [ + { "$ref": "#/definitions/ShortcutAction" }, + { + "properties": { + "action": { "type": "string", "pattern": "multipleActions" }, + "actions" : { + "$ref": "#/definitions/ShortcutAction", + "type": "array", + "minItems": 1, + "description": "A list of other actions." + } + } + } + ], + "required": [ "actions" ] + }, "CommandPaletteAction": { "description": "Arguments for a commandPalette action", "allOf": [ @@ -1237,6 +1273,11 @@ "default": "false", "description": "When set to true, the Terminal's tray icon will always be shown in the system tray.", "type": "boolean" + }, + "useAcrylicInTabRow": { + "default": "false", + "description": "When set to true, the tab row will have an acrylic background with 50% opacity.", + "type": "boolean" }, "actions": { "description": "Properties are specific to each custom action.", diff --git a/doc/specs/#5000 - Process Model 2.0/#1032 - Elevation Quality of Life Improvements.md b/doc/specs/#5000 - Process Model 2.0/#1032 - Elevation Quality of Life Improvements.md new file mode 100644 index 000000000..1757352e9 --- /dev/null +++ b/doc/specs/#5000 - Process Model 2.0/#1032 - Elevation Quality of Life Improvements.md @@ -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. + +![UAC-shield-in-titlebar](UAC-shield-in-titlebar.png) +_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. + + + +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 +Alt+clicking on a profile. We could similarly add support to open a +tab elevated with Ctrl+click. This is similar to the behavior of the +Windows taskbar. It supports creating an elevated instance of a program by +Ctrl+clicking 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 + + + + + + + + + + + + + + + + + + + + + + +
Accessibility + +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. + +
Security + +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. + +
+ +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. + +
Reliability + +No changes to our reliability are expected as a part of this change. + +
Compatibility + +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. + +
Performance, Power, and Efficiency + +No changes to our performance are expected as a part of this change. + +
+ +### 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. + + + + + +[#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 diff --git a/doc/specs/#5000 - Process Model 2.0/UAC-shield-in-titlebar.png b/doc/specs/#5000 - Process Model 2.0/UAC-shield-in-titlebar.png new file mode 100644 index 000000000..879cbc335 Binary files /dev/null and b/doc/specs/#5000 - Process Model 2.0/UAC-shield-in-titlebar.png differ diff --git a/doc/terminal-v2-roadmap.md b/doc/terminal-v2-roadmap.md index 8fedbe2cc..8f4cab235 100644 --- a/doc/terminal-v2-roadmap.md +++ b/doc/terminal-v2-roadmap.md @@ -29,8 +29,8 @@ Below is the schedule for when milestones will be included in release builds of | 2021-03-01 | [1.7] in Windows Terminal Preview
[1.6] in Windows Terminal | [Windows Terminal Preview 1.7 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-7-release/) | | 2021-04-14 | [1.8] in Windows Terminal Preview
[1.7] in Windows Terminal | [Windows Terminal Preview 1.8 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-8-release/) | | 2021-05-31 | [1.9] in Windows Terminal Preview
[1.8] in Windows Terminal | [Windows Terminal Preview 1.9 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-9-release/) | -| 2021-07-31 | 1.10 in Windows Terminal Preview
[1.9] in Windows Terminal | | -| 2021-08-30 | 1.11 in Windows Terminal Preview
1.10 in Windows Terminal | | +| 2021-07-14 | [1.10] in Windows Terminal Preview
[1.9] in Windows Terminal | [Windows Terminal Preview 1.10 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-10-release/) | +| 2021-08-31 | [1.11] in Windows Terminal Preview
[1.10] in Windows Terminal | [Windows Terminal Preview 1.11 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-11-release/) | | 2021-10-31 | 1.12 in Windows Terminal Preview
1.11 in Windows Terminal | | | 2021-11-30 | 2.0 RC in Windows Terminal Preview
2.0 RC in Windows Terminal | | | 2021-12-31 | [2.0] in Windows Terminal Preview
[2.0] in Windows Terminal | | @@ -89,6 +89,8 @@ Feature Notes: [1.7]: https://github.com/microsoft/terminal/milestone/32 [1.8]: https://github.com/microsoft/terminal/milestone/33 [1.9]: https://github.com/microsoft/terminal/milestone/34 +[1.10]: https://github.com/microsoft/terminal/milestone/35 +[1.11]: https://github.com/microsoft/terminal/milestone/36 [2.0]: https://github.com/microsoft/terminal/milestone/22 [#1564]: https://github.com/microsoft/terminal/issues/1564 [#6720]: https://github.com/microsoft/terminal/pull/6720 diff --git a/oss/boost/MAINTAINER_README.md b/oss/boost/MAINTAINER_README.md index 89bd2d2a7..a05641fab 100644 --- a/oss/boost/MAINTAINER_README.md +++ b/oss/boost/MAINTAINER_README.md @@ -4,7 +4,7 @@ This was originally imported by @Austin-Lamb in December 2020. The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme. Please update the provenance information in that file when ingesting an updated version of the dependent library. -That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards. +That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards. ## What should be done to update this in the future? @@ -12,4 +12,4 @@ That provenance file is automatically read and inventoried by Microsoft systems 2. Take the parts you want, but leave most of it behind since it's HUGE and will bloat the repo to take it all. At the time of this writing, we only use small_vector.hpp and its dependencies as a header-only library. 3. Validate that the license in the root of the repository didn't change and update it if so. It is sitting in a version-specific subdirectory below this readme. If it changed dramatically, ensure that it is still compatible with our license scheme. Also update the NOTICE file in the root of our repository to declare the third-party usage. -4. Submit the pull. \ No newline at end of file +4. Submit the pull. diff --git a/oss/chromium/MAINTAINER_README.md b/oss/chromium/MAINTAINER_README.md index 0168187b6..e48db75ba 100644 --- a/oss/chromium/MAINTAINER_README.md +++ b/oss/chromium/MAINTAINER_README.md @@ -4,7 +4,7 @@ This was originally imported by @miniksa in January 2020. The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme. Please update the provenance information in that file when ingesting an updated version of the dependent library. -That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards. +That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards. ## What should be done to update this in the future? diff --git a/oss/dynamic_bitset/MAINTAINER_README.md b/oss/dynamic_bitset/MAINTAINER_README.md index 4782372cd..283b1414f 100644 --- a/oss/dynamic_bitset/MAINTAINER_README.md +++ b/oss/dynamic_bitset/MAINTAINER_README.md @@ -4,7 +4,7 @@ This was originally imported by @miniksa in March 2020. The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme. Please update the provenance information in that file when ingesting an updated version of the dependent library. -That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards. +That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards. ## What should be done to update this in the future? diff --git a/oss/fmt/MAINTAINER_README.md b/oss/fmt/MAINTAINER_README.md index 82371033d..d3b1ae4ec 100644 --- a/oss/fmt/MAINTAINER_README.md +++ b/oss/fmt/MAINTAINER_README.md @@ -4,7 +4,7 @@ This was originally imported by @DHowett-MSFT in April 2020. The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme. Please update the provenance information in that file when ingesting an updated version of the dependent library. -That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards. +That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards. ## What should be done to update this in the future? diff --git a/oss/interval_tree/MAINTAINER_README.md b/oss/interval_tree/MAINTAINER_README.md index 648db77d6..97bbe6199 100644 --- a/oss/interval_tree/MAINTAINER_README.md +++ b/oss/interval_tree/MAINTAINER_README.md @@ -4,7 +4,7 @@ This was originally imported by @PankajBhojwani in September 2020. The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme. Please update the provenance information in that file when ingesting an updated version of the dependent library. -That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards. +That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards. ## What should be done to update this in the future? diff --git a/oss/libpopcnt/MAINTAINER_README.md b/oss/libpopcnt/MAINTAINER_README.md index 45048fce5..843a481ed 100644 --- a/oss/libpopcnt/MAINTAINER_README.md +++ b/oss/libpopcnt/MAINTAINER_README.md @@ -4,7 +4,7 @@ This was originally imported by @miniksa in March 2020. The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme. Please update the provenance information in that file when ingesting an updated version of the dependent library. -That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards. +That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards. ## What should be done to update this in the future? diff --git a/oss/xorg_apps_rgb/MAINTAINER_README.md b/oss/xorg_apps_rgb/MAINTAINER_README.md index b675bdf94..03feea581 100644 --- a/oss/xorg_apps_rgb/MAINTAINER_README.md +++ b/oss/xorg_apps_rgb/MAINTAINER_README.md @@ -4,4 +4,4 @@ This manifest anchors our usage of rgb.txt from the X11 distribution. The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme. Please update the provenance information in that file when ingesting an updated version of the dependent library. -That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropiate governance standards. +That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards. diff --git a/res/fonts/CascadiaCode.ttf b/res/fonts/CascadiaCode.ttf index 117b8de3e..ddd6fc6f8 100644 Binary files a/res/fonts/CascadiaCode.ttf and b/res/fonts/CascadiaCode.ttf differ diff --git a/res/fonts/CascadiaCodeItalic.ttf b/res/fonts/CascadiaCodeItalic.ttf index 0e2587de4..0e8aef2fb 100644 Binary files a/res/fonts/CascadiaCodeItalic.ttf and b/res/fonts/CascadiaCodeItalic.ttf differ diff --git a/res/fonts/CascadiaMono.ttf b/res/fonts/CascadiaMono.ttf index 6a66e2b0e..5872da996 100644 Binary files a/res/fonts/CascadiaMono.ttf and b/res/fonts/CascadiaMono.ttf differ diff --git a/res/fonts/CascadiaMonoItalic.ttf b/res/fonts/CascadiaMonoItalic.ttf index 086b1b161..d84fbfd24 100644 Binary files a/res/fonts/CascadiaMonoItalic.ttf and b/res/fonts/CascadiaMonoItalic.ttf differ diff --git a/res/fonts/README.md b/res/fonts/README.md index d4ece8657..80aa1efb6 100644 --- a/res/fonts/README.md +++ b/res/fonts/README.md @@ -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 (2108.26) + * from microsoft/cascadia-code@f91d08f703ee61cf4ae936b9700ca974de2748fe diff --git a/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp b/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp index cecfade05..3b2ed8d14 100644 --- a/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp @@ -65,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}", @@ -84,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}" } }, @@ -219,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('F'), 0 }; diff --git a/src/cascadia/TerminalApp/App.xaml b/src/cascadia/TerminalApp/App.xaml index 46ee22eba..bc77872e3 100644 --- a/src/cascadia/TerminalApp/App.xaml +++ b/src/cascadia/TerminalApp/App.xaml @@ -45,7 +45,7 @@ Color="{ThemeResource SystemErrorTextColor}" /> - 8,0,8,0 + 9,0,8,0 12 diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 532e290eb..6402aaf1d 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -884,4 +884,37 @@ namespace winrt::TerminalApp::implementation args.Handled(true); } } + + void TerminalPage::_HandleClearBuffer(const IInspectable& /*sender*/, + const ActionEventArgs& args) + { + if (args) + { + if (const auto& realArgs = args.ActionArgs().try_as()) + { + if (const auto termControl{ _GetActiveControl() }) + { + termControl.ClearBuffer(realArgs.Clear()); + args.Handled(true); + } + } + } + } + + void TerminalPage::_HandleMultipleActions(const IInspectable& /*sender*/, + const ActionEventArgs& args) + { + if (args) + { + if (const auto& realArgs = args.ActionArgs().try_as()) + { + for (const auto& action : realArgs.Actions()) + { + _actionDispatch->DoAction(action); + } + + args.Handled(true); + } + } + } } diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp index a2da96205..69f609ea8 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp @@ -399,8 +399,10 @@ static const std::map focusDirectionMap = { { "right", FocusDirection::Right }, { "up", FocusDirection::Up }, { "down", FocusDirection::Down }, + { "previous", FocusDirection::Previous }, { "nextInOrder", FocusDirection::NextInOrder }, { "previousInOrder", FocusDirection::PreviousInOrder }, + { "first", FocusDirection::First }, }; // Method Description: diff --git a/src/cascadia/TerminalApp/CommandPalette.cpp b/src/cascadia/TerminalApp/CommandPalette.cpp index bd6badf65..a4b4c0b8f 100644 --- a/src/cascadia/TerminalApp/CommandPalette.cpp +++ b/src/cascadia/TerminalApp/CommandPalette.cpp @@ -36,7 +36,6 @@ namespace winrt::TerminalApp::implementation _allCommands = winrt::single_threaded_vector(); _tabActions = winrt::single_threaded_vector(); _mruTabActions = winrt::single_threaded_vector(); - _commandLineHistory = winrt::single_threaded_vector(); _switchToMode(CommandPaletteMode::ActionMode); @@ -245,6 +244,17 @@ namespace winrt::TerminalApp::implementation _PreviewActionHandlers(*this, actionPaletteItem.Command()); } } + else if (_currentMode == CommandPaletteMode::CommandlineMode) + { + if (filteredCommand) + { + SearchBoxPlaceholderText(filteredCommand.Item().Name()); + } + else + { + SearchBoxPlaceholderText(RS_(L"CmdPalCommandlinePrompt")); + } + } } void CommandPalette::_previewKeyDownHandler(IInspectable const& /*sender*/, @@ -365,6 +375,17 @@ namespace winrt::TerminalApp::implementation _searchBox().PasteFromClipboard(); e.Handled(true); } + else if (key == VirtualKey::Right && _currentMode == CommandPaletteMode::CommandlineMode) + { + if (const auto command{ _filteredActionsView().SelectedItem().try_as() }) + { + _searchBox().Text(command.Item().Name()); + _searchBox().Select(_searchBox().Text().size(), 0); + _searchBox().Focus(FocusState::Programmatic); + _filteredActionsView().SelectedIndex(-1); + e.Handled(true); + } + } } // Method Description: @@ -587,7 +608,7 @@ namespace winrt::TerminalApp::implementation case CommandPaletteMode::TabSwitchMode: return _tabSwitcherMode == TabSwitcherMode::MostRecentlyUsed ? _mruTabActions : _tabActions; case CommandPaletteMode::CommandlineMode: - return _commandLineHistory; + return _loadRecentCommands(); default: return _allCommands; } @@ -720,14 +741,10 @@ namespace winrt::TerminalApp::implementation // - void CommandPalette::_dispatchCommandline(winrt::TerminalApp::FilteredCommand const& command) { - const auto filteredCommand = command ? command : _buildCommandLineCommand(_getTrimmedInput()); + const auto filteredCommand = command ? command : _buildCommandLineCommand(winrt::hstring(_getTrimmedInput())); if (filteredCommand.has_value()) { - if (_commandLineHistory.Size() == CommandLineHistoryLength) - { - _commandLineHistory.RemoveAtEnd(); - } - _commandLineHistory.InsertAt(0, filteredCommand.value()); + _updateRecentCommands(filteredCommand.value().Item().Name()); TraceLoggingWrite( g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider @@ -744,15 +761,14 @@ namespace winrt::TerminalApp::implementation } } - std::optional CommandPalette::_buildCommandLineCommand(std::wstring const& commandLine) + std::optional CommandPalette::_buildCommandLineCommand(const hstring& commandLine) { if (commandLine.empty()) { return std::nullopt; } - winrt::hstring cl{ commandLine }; - auto commandLinePaletteItem{ winrt::make(cl) }; + auto commandLinePaletteItem{ winrt::make(commandLine) }; return winrt::make(commandLinePaletteItem); } @@ -1217,4 +1233,81 @@ namespace winrt::TerminalApp::implementation itemContainer.DataContext(args.Item()); } } + + // Method Description: + // - Reads the list of recent commands from the persistent application state + // Return Value: + // - The list of FilteredCommand representing the ones stored in the state + IVector CommandPalette::_loadRecentCommands() + { + const auto recentCommands = ApplicationState::SharedInstance().RecentCommands(); + // If this is the first time we've opened the commandline mode and + // there aren't any recent commands, then just return an empty vector. + if (!recentCommands) + { + return single_threaded_vector(); + } + + std::vector parsedCommands; + parsedCommands.reserve(std::min(recentCommands.Size(), CommandLineHistoryLength)); + + for (const auto& c : recentCommands) + { + if (parsedCommands.size() >= CommandLineHistoryLength) + { + // Don't load more than CommandLineHistoryLength commands + break; + } + + if (const auto parsedCommand = _buildCommandLineCommand(c)) + { + parsedCommands.push_back(*parsedCommand); + } + } + return single_threaded_vector(std::move(parsedCommands)); + } + + // Method Description: + // - Update recent commands by putting the provided command as most recent. + // Upon race condition might override an update made by another window. + // Return Value: + // - + void CommandPalette::_updateRecentCommands(const hstring& command) + { + const auto recentCommands = ApplicationState::SharedInstance().RecentCommands(); + // If this is the first time we've opened the commandline mode and + // there aren't any recent commands, then just store the new command. + if (!recentCommands) + { + ApplicationState::SharedInstance().RecentCommands(single_threaded_vector(std::move(std::vector{ command }))); + return; + } + + const auto numNewRecentCommands = std::min(recentCommands.Size() + 1, CommandLineHistoryLength); + + std::vector newRecentCommands; + newRecentCommands.reserve(numNewRecentCommands); + + std::unordered_set uniqueCommands; + uniqueCommands.reserve(numNewRecentCommands); + + newRecentCommands.push_back(command); + uniqueCommands.insert(command); + + for (const auto& c : recentCommands) + { + if (newRecentCommands.size() >= CommandLineHistoryLength) + { + // Don't store more than CommandLineHistoryLength commands + break; + } + + if (uniqueCommands.emplace(c).second) + { + newRecentCommands.push_back(c); + } + } + + ApplicationState::SharedInstance().RecentCommands(single_threaded_vector(std::move(newRecentCommands))); + } } diff --git a/src/cascadia/TerminalApp/CommandPalette.h b/src/cascadia/TerminalApp/CommandPalette.h index af065d67d..29838eee8 100644 --- a/src/cascadia/TerminalApp/CommandPalette.h +++ b/src/cascadia/TerminalApp/CommandPalette.h @@ -123,15 +123,16 @@ namespace winrt::TerminalApp::implementation void _dispatchCommand(winrt::TerminalApp::FilteredCommand const& command); void _dispatchCommandline(winrt::TerminalApp::FilteredCommand const& command); void _switchToTab(winrt::TerminalApp::FilteredCommand const& command); - std::optional _buildCommandLineCommand(std::wstring const& commandLine); + static std::optional _buildCommandLineCommand(const winrt::hstring& commandLine); void _dismissPalette(); void _scrollToIndex(uint32_t index); uint32_t _getNumVisibleItems(); - static constexpr int CommandLineHistoryLength = 10; - Windows::Foundation::Collections::IVector _commandLineHistory{ nullptr }; + static constexpr uint32_t CommandLineHistoryLength = 20; + static Windows::Foundation::Collections::IVector _loadRecentCommands(); + static void _updateRecentCommands(const winrt::hstring& command); ::TerminalApp::AppCommandlineArgs _appArgs; void _choosingItemContainer(Windows::UI::Xaml::Controls::ListViewBase const& sender, Windows::UI::Xaml::Controls::ChoosingItemContainerEventArgs const& args); diff --git a/src/cascadia/TerminalApp/MinMaxCloseControl.xaml b/src/cascadia/TerminalApp/MinMaxCloseControl.xaml index fd3de4f43..105de2226 100644 --- a/src/cascadia/TerminalApp/MinMaxCloseControl.xaml +++ b/src/cascadia/TerminalApp/MinMaxCloseControl.xaml @@ -9,7 +9,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" HorizontalAlignment="Left" VerticalAlignment="Top" - d:DesignHeight="36" + d:DesignHeight="40" d:DesignWidth="400" Background="Transparent" Orientation="Horizontal" @@ -124,8 +124,9 @@ tabs will be flush with the top of the window. See GH#2541 for details. --> - 36.0 - 32.0 + 40.0 + + 33.0