Compare commits

...

84 Commits

Author SHA1 Message Date
Mike Griese
5eb08ff804 Merge branch 'main' into dev/miniksa/env 2023-01-24 06:50:54 -06:00
Dustin Howett
7d0baa7946 Revert "Manually set the automation name of the default color scheme for screen reader (#14704)"
This reverts commit 47f38e31a1.
2023-01-23 19:01:49 -06:00
Carlos Zamora
a0e830cc1a [UIA] Dispatch a TextChanged event on new output (#14723)
For some reason, Windows Terminal stops dispatching UIA TextChanged events sometimes. There isn't a reliable repro for this bug.

However, under NVDA's logger, it appears that when the bug does occur, we still dispatch UIA notifications (which may be ignored by NVDA in some configurations). A "quick fix" here is to dispatch a TextChanged event if we're going to dispatch a notification. Since we're just enabling a flag, we won't send two events at once.

Closes #10911
2023-01-23 21:53:09 +00:00
Mike Griese
a4cf4e2761 Resize our ContentDialog's when the window resizes (#14722)
Major thanks to @dongle-the-gadget in https://github.com/microsoft/microsoft-ui-xaml/issues/3577#issuecomment-1399250405 for coming up with this workaround.

This PR will manually forward a `WM_SIZE` message to our `CoreWindow`, to trigger the `ContentDialog` to resize itself. 

We always closed these issues as dupes of the upstream one, so this doesn't actually close anything.
HOWEVER, these are the following issues that reported this bug:
- #2380
- #4463
- #5252
- #5810
- #6181
- #7113
- #7225
- #8245
- #8496
- #8643
- #9336
- #9563
- #5808
- #10351
- #10634
- #10995
- #11770
- #13796
2023-01-23 12:42:27 -06:00
Carlos Zamora
3dd40791c9 Ensure TermControl is not closing when firing UIA events (#14714)
The `SignalTextChanged` crash seems to be occurring due to the `TermControlAutomationPeer` being destructed by the time the UIA event is actually dispatched. Even though we're already checking if TCAP and TermControl still exist, it could be that the TermControl is being closed as text is being output.

The proposed fix here is to record when the closing process starts and exposing that information directly to the TCAP. If TCAP sees that we're in the process of closing, don't bother sending a UIA event.


Closes #13978
2023-01-21 15:48:33 +00:00
Dustin Howett
8f1960d0b4 version: bump to 1.18 on main 2023-01-20 15:24:08 -06:00
Dustin L. Howett
72be9a95af Code sign the contents of the Terminal package (#14710)
Up until now, we have been relying on the catalog signature produced for our MSIX package.
There are some things (Packaged COM, Process Explorer as of 2022) that cannot handle catalog-signed
files. It's easier and safer for us to simply sign all the executables we produce before packaging them.

Unfortunately, we can't do it before we package them. We have to unpack and re-pack our package.

In the future, this will allow us to provide a codesigned distribution that is not in an MSIX package.

TEST=Ran a build and checked out the contents of the package. They were all signed!

Closes #13294
Closes #12695
Closes #9670
2023-01-20 11:47:18 -06:00
Mike Griese
596d0c5155 Update the titlebar visibility when we gain/lose focus too (#14708)
We forgot to updateTheme again when we change the titlebar color. That would result in us setting the titlebar visibility only when the settings were reloaded, but if the unfocused BG was opaque, and the focused was transparent, we'd use the current _unfocused_ color to set the titlebar visibility. 

Also, this fixes a bug with `tabRow.BG: terminalBackground`

from teams for brevity 

> tabRow.BG = terminalBackground might not work, and here's why
>
> the termcontrol's BG brush might be (r,g,b, 255), with an Opacity of 0
> 
> so my "use mica when the brush's A is <1.0" doesn't work
> 

closes #14563
2023-01-20 01:38:34 +00:00
Mike Griese
79eb9b3d3c Use a dark titlebar when we've requested dark theme (#14536)
This applies to `"showTabsInTitlebar": false,`. We literally never set that for the `IslandWindow` before, only ever the NCIW. 

Closes #11589


![update-titlebar-for-theme](https://user-images.githubusercontent.com/18356694/207109370-a63a8b19-4c42-4b1f-8d39-8c3abdf1b403.gif)


For a dramatic example - here's the IW with mica enabled, in dark mode:

![image](https://user-images.githubusercontent.com/18356694/207109465-a6165637-31a5-45a4-bff0-51ac79404cd6.png)

Theme json:

```json
        {
            "name": "chonk",
            "tab":
            {
                "background": "#00000000",
                "unfocusedBackground": "#00000000",
                "showCloseButton": "never"
            },
            "tabRow":
            {
                "background": "#00000000",
                "unfocusedBackground": "#00000000",
            },
            "window":
            {
                "applicationTheme": "dark",
                "useMica": true
            }
        },
```
2023-01-19 23:51:21 +00:00
Joshua Boelter
eab1c239a9 Launch elevated instances via shell:AppFolder (#14637)
This uses `shell:AppsFolder` to launch elevated instances of the app via
`ShellExecuteEx` and `runas` in elevate-shim.exe. The app to launch is
discovered via the `GetCurrentApplicationUserModelId` API.

e.g. `shell:AppsFolder\WindowsTerminalDev_8wekyb3d8bbwe!App`

This will fallback to launching `WindowsTerminal.exe` if it fails to
discover the app user model id to launch.

This also fixes a bug in elevate-shim where the first argument of
WinMain was lost (e.g. `new-tab`). 

Curiously, `AppLogic::RunAsUwp()` is never called and
`AppLogic::IsUwp()` is always false when running debug builds locally
(e.g. WindowsTerminalDev). It's not clear if this is an artifact of
development packages or something else.

## Validation Steps Performed

Various manual debug/execution scenarios.

Verified the fallback path by running the unbundled app by extracting
the `CascadiaPackage_0.0.1.0_x64.msix` from the 'drop' build artifact.

Fixes #14501
2023-01-19 23:40:53 +00:00
PankajBhojwani
96a9dd5209 Automatically focus the ColorSchemeListView when we navigate to the ColorSchemes page (#14631)
This regressed when we implemented ColorSchemeViewModel in #13179.
This commit fixes that by automatically focusing the ColorSchemeListView
when we navigate to the ColorSchemes page, which makes sense from a
user experience perspective anyway.

Closes #11971
2023-01-19 23:20:56 +00:00
PankajBhojwani
6278a2d9bf Fix the selected color scheme list item container background being blue in Windows 10 (#14706)
Specify the resource to use for the list view item background when selected.

References #14693 

## Validation Steps Performed
Background is no longer blue, it is light gray (like in Windows 11). Screenshots below.
2023-01-19 22:04:28 +00:00
PankajBhojwani
16fe2e5905 Add color chips to the color scheme dropdown in Appearance (#14587)
Does what it says on the tin.
2023-01-19 21:46:59 +00:00
Mike Griese
f2b82cd054 Only use Mica in the SUI if Mica is available (#14675)
This should make sure we only use Mica for the BG of the SUI when we're on a Windows build that supports it. Otherwise, we're just gonna get the emergency backstop / full transparency under it. 

Confirmed this fixes it on my win10 VM.

Closes #14667
2023-01-19 21:06:49 +00:00
James Holderness
7813953b23 Add support for IRM (Insert Replace Mode) (#14700)
This PR add support for the ANSI Insert/Replace mode (`IRM`), which
determines whether output characters are inserted at the active cursor
position, moving existing content to the right, or whether they should
overwrite the content that is already there.

The implementation is a bit of a hack. When that mode is enabled, it
first measures how many cells the string is expected to occupy, then
scrolls the target line right by that amount before writing out the new
text.

In the longer term it might be better if this was implemented entirely
in the `TextBuffer` itself, so the scrolling could take place at the
same time as the content was being written.

## Validation Steps Performed

I've added a very basic unit test that verifies the mode is working as
expected. But I've also done a lot more manual testing, confirming edge
cases like wide characters, double-width lines, and both with and
without wrapping mode enabled.

Closes #1947
2023-01-19 19:59:05 +00:00
PankajBhojwani
47f38e31a1 Manually set the automation name of the default color scheme for screen reader (#14704)
## Summary of the Pull Request
When we navigate to the color schemes page, find the default color scheme (if present) and manually set the container's automation name to include the word 'default'. 

Note that we don't want to change the actual `ColorSchemeViewModel`'s name since that name is used internally to identify schemes. We only want to change the `ListViewItem`'s name, i.e. the container in the SUI.

## PR Checklist
* [x] Closes #14401 
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

## Validation Steps Performed
Screen reader reads out the 'default' text now. It also correctly reads out the new default scheme if the default scheme is changed via SUI or json.
2023-01-19 19:39:08 +00:00
Mike Griese
0fe0868f98 Remove the win10/win11 digit from our version number. (#14660)
As of about 2022, the one's digit of the Build of our version is a placeholder value to differentiate the Windows 10 build from the Windows 11 build. Let's trim that out, it's only a source of confusion.

For additional clarity, let's omit the Revision, which _must_ be `.0`, and doesn't provide any value to report.

We will need to make sure we report releases as `1.17.ABC` now, instead of `1.17.ABC1.0`/`1.17.ABC2.0`

Let's not backport this. 1.17 will be the start of the new numbering scheme. Otherwise, `1.16.EFG` < `1.16.ABC1.0`, for an old version `ABC1` and a new version `EFG`.

* closes #14106
* As summarized here: https://github.com/microsoft/terminal/issues/14106#issuecomment-1289462310
2023-01-19 19:32:23 +00:00
Dustin L. Howett
179bb9bded Add TerminalStress, Mike Treit's application for breaking WT (#14701)
From Treit/TerminalStress@39c03e2d00

Co-authored-by: Dustin L. Howett <duhowett@microsoft.com>
Co-authored-by: Mike Treit <mtreit@ntdev.microsoft.com>
2023-01-19 13:20:43 -06:00
Mike Griese
90485e4c79 Fix a crash on startup with a folder entry without entries (#14629)
`_Entries` was getting default constructed to `nullptr`. We should be careful about that. 

Adds a test too, and fixes a regression in the local tests introduced in #13763.

Closes #14557
2023-01-19 19:12:40 +00:00
Dustin L. Howett
29ef49252b Make the PR template less unfriendly (#14697)
Dunno how to feel about this. I want to guide people, but i don't want HTML comments in my `git log`
2023-01-19 11:39:04 +00:00
PankajBhojwani
5614116c6e Update Profile and Appearance VMs to use CSVM; remove nav state (#14572)
ColorScheme MVVM was implemented in #13179. This PR updates the
ProfileViewModel/AppearanceViewModels to use color scheme view models
instead of the raw settings model objects.

* [x] Updates ProfileViewModel/AppearanceViewModel to use
  ColorSchemesPageViewModel/ColorSchemeViewModel implemented in #13179
* [x] Removes ProfilePageNavigationState

## Validation Steps Performed
Settings UI still works (we _probably_ want to bug bash this at some
point though as with all SUI changes)
2023-01-18 15:30:00 -06:00
James Holderness
a1865b9cf7 Merge the PrintString functionality into AdaptDispatch (#14640)
The main purpose of this PR was to merge the `ITerminalApi::PrintString`
implementations into a shared method in `AdaptDispatch`, and avoid the
VT code path depending on `WriteCharsLegacy`. But this refactoring has
also fixed some bugs that existed in the original implementations. 

This helps to close the gap between the Conhost and Terminal (#13408).

I started by taking the `WriteCharsLegacy` implementation, and stripping
out everything that didn't apply to the VT code path. What was left was
a fairly simple loop with the following steps:

1. Check if _delayed wrap_ is set, and if so, move to the next line.
2. Write out as much of the string as will fit on the current line.
3. If we reach the end of the line, set the _delayed wrap_ flag again.
4. Repeat the loop until the entire string has been output.

But step 2 was a little more complicated than necessary because of its
legacy history. It was copying the string into a temporary buffer,
manually estimated how much of it would fit, and then passing on that
partial buffer to the `TextBuffer::Write` method.

In the new implementation, we just pass the entire string directly to
`TextBuffer::WriteLine`, and that handles the clipping itself. The
returned `OutputCellIterator` tells us how much of the string is left.
This approach fixes some issues with wide characters, and should also
cope better with future buffer enhancements.

Another improvement from the new implementation is that the Terminal now
handles delayed EOL wrap correctly. However, the downside of this is
that it introduced a cursor-dropping bug that previously only affected
conhost. I hadn't originally intended to fix that, but it became more of
an issue now.

The root cause was the fact that we called `cursor.StartDeferDrawing()`
before outputting the text, and this was something I had adopted in the
new implementation as well. But I've now removed that, and instead just
call `cursor.SetIsOn(false)`. This seems to avoid the cursor droppings,
and hopefully still has similar performance benefits.

The other thing worth mentioning is that I've eliminated some special
casing handling for the `ENABLE_VIRTUAL_TERMINAL_PROCESSING` mode and
the `WC_DELAY_EOL_WRAP` flag in the `WriteCharsLegacy` function. They
were only used for VT output, so aren't needed here anymore.

## Validation Steps Performed

I've just been testing manually, writing out sample text both in ASCII
and with wide Unicode chars. I've made sure it wraps correctly when
exceeding the available space, but doesn't wrap when stopped at the last
column, and with `DECAWM` disabled, it doesn't wrap at all.

I've also confirmed that the test case from issue #12739 is now working
correctly, and the cursor no longer disappears in Windows Terminal when
writing to the last column (i.e. the delayed EOL wrap is working).

Closes #780
Closes #6162
Closes #6555
Closes #12440
Closes #12739
2023-01-18 20:26:04 +00:00
Mike Griese
c79298d3fd Use ThemeLookup for the SUI bg too (#14644)
I can't exactly repro #14559. I suspect that's due to #14567 having been merged. This, however, seemed related. Without this, we'll use the App's `RequestedTheme` (the one that can't be changed at runtime), rather than the user's `requestedTheme`. That will do weird things, like make the BG of the SUI dark, with white expanders.

I think this should close #14559.
2023-01-18 18:31:21 +00:00
Carlos Zamora
8e041692b3 Add mutex to keyEvents in TermControlAutomationPeer (#14694)
Some investigation revealed that `_keyEvents` would get a `NULL_POINTER_READ` error. On the main thread, `RecordKeyEvent()` would be called, which mainly updates `_keyEvents`. On the renderer thread, `NotifyNewOutput()` would be called, which starts by iterating through `_keyEvents` and slowly clearing it out. On occasion, these two threads are modifying `_keyEvents` simultaneously, causing a crash.

The fix is to add a mutex on this variable. 

Closes #14592
2023-01-18 07:28:06 +00:00
Mike Griese
4c7879bfb5 Make sure focused tab text color accounts for alpha (#14643)
Basically what it says on the tin. For transparent tabs, we should layer on to the tab row to evaluate what the actual color of the tab will be. We did this for deselected tabs, but not for selected ones.

Gif below.

Closes #14561
2023-01-16 20:04:30 +00:00
Ian O'Neill
f3439e201e Ensure export and find tab context menu items work for unfocused tabs (#14673)
## Summary of the Pull Request
Updates the tab event handling so that the "Export Text" and "Find" tab context menu items work when a tab isn't focused.

## PR Checklist
* [x] Closes #13948
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

## Validation Steps Performed
Manually tested.
2023-01-16 18:18:06 +00:00
musvaage
d3264964a9 Fix some additional typos (#14671)
Fix spelling errors in code comments and markdown
Closes #14670
2023-01-16 16:17:54 +00:00
Mike Griese
dd2736f334 Make sure to update the selection pivot while circling (#14636)
This builds upon #10749. When we added a separate pivot to track the "active" anchor, we forgot to update the pivot while circling. What does that mean? Moving the mouse would trigger us to update the selection using new endpoint and the old _pivot_, which hadn't been updated. 

There's probably a more elegant way of doing this, but it's good enough. 

Updated the test to cover this as well. 

Closes #14462
2023-01-16 16:13:20 +00:00
Mike Griese
020746d93d Update the Theme schema for 1.17 (#14666)
What it says on the tin. 

I finally figured out the right way to validate schema updates in VsCode, which made this a lot faster. Tossed notes in the wiki for next time I do this. 

Closes #14560
2023-01-13 16:40:09 -08:00
Carlos Zamora
dc6d82e9bc [WPF] Add TermCore null checks to HwndTerminal (#14678)
#14461 is caused by a null pointer exception in `TerminalIsSelectionActive()`. Since this reveals that it is possible to enter a state where `_terminal` is null, I've gone ahead and added null-checks throughout the code to make it more stable.

Closes #14461

## Validation Steps Performed
Ran locally. Still works.
2023-01-13 23:03:14 +00:00
Dustin L. Howett
239b4d16b9 Fix an issue where JsonUtils produces a bad error message (#14668)
CascadiaSettings relies on getting a JsonUtils::DeserializationException
from the various JSON Utility functions, and then formatting that into
an error message. Well, DeserializationException tries to include an
object representation in its what() message . . . and generates an
exception trying to do so. CascadiaSettings never gets the
DeserializationException, and displays a weird message.

It's safe to remove the stringification in DeserializationException
because CascadiaSettings was never using it (_and_ because
CascadiaSettings was using an even better version of the same logic.)

Fixes #14373
2023-01-13 00:43:24 +00:00
James Holderness
fb485a2b40 Prevent horizontally scrolling wide chars erasing themselves (#14650)
When the buffer contains wide characters that occupy more than one cell,
and those cells are scrolled horizontally by exactly one column, that
operation can result in the wide characters being completely erased.
This PR attempts to fix that bug, although it's not an ideal long term
solution.

Although not really to blame, it was PR #13626 that exposed this issue.

The root of the problem is that scrolling operations copy cells one by
one, but wide characters are written to the buffer two cells at a time.
So when you move a wide character one position to the left or right, it
can overwrite itself before it's finished copying, and the end result is
the whole character gets erased.

I've attempt to solve this by getting the affected operations to read
two cells in advance before they start writing, so there's no risk of
losing the source data before it's fully output. This may not work in
the long term, with characters wider than two cells, but it should at
least be good enough for now.

I've also changed the `TextBuffer::Write` call to a `WriteLine` call to
improve the handling of a wide character on the end of the line, where
moving it right by one column would place it half off screen. It should
just be dropped, but with the `Write` method, it would end up pushed
onto the following line.

## Validation Steps Performed

I've manually confirmed this fixes all the test cases described in
#14626, and also added some unit tests that replicate those scenarios.

Closes #14626
2023-01-13 00:10:22 +00:00
Mike Griese
45a36cf83a Enable switching app theme based off of OS theme (#14497)
This is basically just like #14064, but with the `theme` instead.

If you define a pair of `theme` names:

```json
    "theme": { "dark": "light", "light": "dark" },
```

then the Terminal will use the one relevant for the current OS theme. This cooperates with #14064, who sets the `scheme` based on the app's theme. 

This was spec'd as a part of #3327 / #12530, but never promoted to its own issue. 
Gif below.
2023-01-12 15:43:40 +00:00
Carlos Zamora
09273be1c8 Introduce PseudoConsoleWindow a11y provider (#14541)
In order to modify the accessibility information for the PseudoConsoleWindow, it needs to have a UIA provider registered. This PR introduces `PseudoConsoleWindowAccessibilityProvider` and registers it as a UIA provider appropriately. The registration process is based on that of the `WindowUiaProvider` for ConHost.

Closes #14385

## Validation Steps Performed
Run Accessibility Insights FastPass on the window. The PseudoConsoleWindow no longer is tagged as missing a name.
2023-01-12 00:05:55 +00:00
James Holderness
f1090a07f5 Correct the cursor invalidation region (#14661)
Depending on the line rendition, and whether the cursor is over a wide
character or not, it's possible for the width to take up anywhere from 1
to 4 cells. And when it's more than 1 cell wide, part of the cursor may
end up off screen. However, our bounds check requires the entire cursor
to be on screen, otherwise it doesn't render anything, and that can
result in cursor droppings being left behind. This PR fixes that.

The bounds check that is causing this issue was introduced in #13001 to
fix a debug assertion.

To fix this, I've removed the bounds checking, and instead clip the
cursor rect to the boundaries of the viewport. This is what the original
code was trying to do prior to the #13001 fix, but was mistakenly using
the `Viewport:Clamp` method, instead of `TrimToViewport`. Since this
implementation doesn't require a clamp, there should be no risk of the
debug assertion returning.

## Validation Steps Performed

I've confirmed that the test case in #14657 is now working correctly,
and not leaving cursor droppings behind. I've also tested under conhost
with buffer sizes wider than the viewport, and confirmed it can handle a
wide cursor partially scrolled off screen.

Closes #14657
2023-01-11 21:01:34 +00:00
Jie 'Jason' Liu
b7e537e5e7 Fix missing paths when items dropped from archive (#14648)
Grab all paths from `DROPFILES` struct provided in drag event data

`GetStorageItemsAsync()` only giving up to 16 items when items are dropped from any archives
- When this occurs, we should look into `FileDrop` key for a stream of the [`DROPFILES struct`](https://learn.microsoft.com/en-us/windows/win32/shell/clipboard#cf_hdrop)
- This struct contains a null-character delimited string of paths which we can just read out

## Validation Steps Performed
* [X] Unit tests pass locally
* [X] Drag and drop paths work for both archives and non-archives files, folders, shortcuts, etc.

Closes #14628
2023-01-11 00:40:22 +00:00
Joshua Boelter
7b9ec0ed6a Check for null lParam in WM_SETTINGSCHANGE (#14653)
Fix access violation on lParam when constructing wstring.

Per the [WM_SETTINGSCHANGE docs], NULL is a valid value for lParam. A
null lParam was observed when using the 'Switch User' feature of Windows
while developing the Terminal app.

## Validation Steps Performed
Reproduced the scenario w/ the check in place and verified no crash.

Closes #14652

[WM_SETTINGSCHANGE docs]: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-settingchange
2023-01-10 00:01:58 +00:00
grammar-police
06baead9ea Minor grammar fix (#14614)
`s/it's/its/`

Note that I didn't touch the several errors in the doc and doc/spec directories, since those seem to be dated and signed email excerpts, and I don't want to violate authorial integrity. Let me know if you would like me to fix those as well.

## References
p 57.  Murray, L.  (1824).  English grammar.  Philadelphia :  E. T. Scott.

I skimmed several hundred usages of the word "it's" in the code. This actually wasn't as tiresome as it sounds, since many of the code comments in this repo are entertaining and educational &mdash; the adjectives do not _necessarily_ apply in that order, but do _possibly_ apply in that order.
2023-01-09 19:25:03 +00:00
Mike Griese
21a62c5fef Change "easy starter" -> "good first issue"
I changed that label months ago, but forgot about this doc
2023-01-05 13:30:24 -06:00
Vamsi Krishna Kanjeevaram
2d66dc44f5 Duplicated tabs open next to the current tab (#14521)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
Added an additional optional parameter that indicates the position of the new tab being created.

https://user-images.githubusercontent.com/17508246/206831900-d8349fb6-4241-4c37-8dd8-e1645ba94c90.mp4

https://user-images.githubusercontent.com/17508246/206831949-02e4156e-f471-4d2f-b54b-3b0d294c62fe.mp4


<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes #14313 
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] Tests added/passed

<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
An optional parameter is added to TerminalPage::_CreateNewTabPane() and TerminalPage::_InitializeTab() which indicates the insert position of the duplicated tab.
During a new tab creation (not duplicate), this optional parameter has a default value(-1) and the new tab is inserted at the end.
The duplicated tab is inserted next to the original one even if it is not focused.

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Created different tabs and duplicated them.
Duplicated tabs that are focused and not focused.
2023-01-04 14:16:31 +00:00
Alex
547349af77 GitHub Workflows security hardening (#14513)
This PR adds explicit [permissions section] to workflows. This is a
security best practice because by default workflows run with [extended
set of permissions] (except from `on: pull_request` [from external
forks]). By specifying any permission explicitly all others are set to
none. By using the principle of least privilege the damage a compromised
workflow can do (because of an [injection] or compromised third party
tool or action) is restricted.

It is recommended to have [most strict permissions on the top level] and
grant write permissions on [job level] case by case.

[permissions section]: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
[extended set of permissions]: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token
[from external forks]: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
[injection]: https://securitylab.github.com/research/github-actions-untrusted-input/
[most strict permissions on the top level]: https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions
[job level]: https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs
2022-12-19 12:07:25 -08:00
Carlos Zamora
7ab0e982c7 Replace UIA CompareInBounds with til::point comparators (#14551)
This PR replaces the uses of `Viewport::CompareInBounds()` in the UIA code with `til::point` comparators. Additionally, it simplifies the logic further by using `std::max` and `std::min`.

In doing so, we are no longer hitting the `assert` in `CompareInBounds()`.


Closes #14542
2022-12-19 18:09:42 +00:00
Leonard Hecker
165d3edde9 Add support for start /B and FreeConsole (#14544)
2 new ConPTY APIs were added as part of this commit:
* `ClosePseudoConsoleTimeout`
  Complements `ClosePseudoConsole`, allowing users to override the `INFINITE`
  wait for OpenConsole to exit with any arbitrary timeout, including 0.
* `ConptyReleasePseudoConsole`
  This releases the `\Reference` handle held by the `HPCON`. While it makes
  launching further PTY clients via `PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE`
  impossible, it does allow conhost/OpenConsole to exit naturally once all
  console clients have disconnected. This makes it unnecessary to having to
  monitor the exit of the spawned shell/application, just to then call
  `ClosePseudoConsole`, while carefully continuing to read from the output
  pipe. Instead users can now just read from the output pipe until it is
  closed, knowing for sure that no more data will come or clients connect.
  This is especially useful in combination with `ClosePseudoConsoleTimeout`
  and a timeout of 0, to allow conhost/OpenConsole to exit asynchronously.

These new APIs are used to fix an array of bugs around Windows Terminal exiting
either too early or too late. They also make usage of the ConPTY API simpler in
most situations (when spawning a single application and waiting for it to exit).

Depends on #13882, #14041, #14160, #14282

Closes #4564
Closes #14416
Closes MSFT-42244182

## Validation Steps Performed
* Calling `FreeConsole` in a handoff'd application closes the tab 
* Create a .bat file containing only `start /B cmd.exe`.
  If WT stable is the default terminal the tab closes instantly 
  With these changes included the tab stays open with a cmd.exe prompt 
* New ConPTY tests 
2022-12-16 22:06:30 +00:00
Carlos Zamora
a3c7bc3349 [SUI] Polish Launch Parameters setting controls (#14569)
## Summary of the Pull Request
Performs some cleanup in the Settings UI for the Launch Parameters settings:
1. Updates all `NumberBox` controls in the Settings UI to have a `Compact` `SpinButtonPlacementMode` instead of an inline one. This alleviates the XAML bug where the spin button would appear over the number box's input value.
2. Fixes an issue where a long X/Y value would resize the settings controls weirdly. This was fixed by introducing a `Grid` inside the main grid and applying a width to the number boxes.
3. Rename "Use system default" checkbox to be more clear. Propagate the new localized string into expander preview.


Closes #14558
2022-12-16 21:00:30 +00:00
Mike Griese
772ed3a7b7 Use mica in the SUI if mica is enabled in the theme (#14567)
Does what it says on the tin!
2022-12-16 17:36:08 +00:00
Carlos Zamora
dbc27ab041 Match 'add appearance' button text to AutoProp.Name (#14564)
Similar to #14519.

Voice Access allows for functionality like "click <name>" to automatically move the cursor and click on a control. The Search Box had an AutoProp.Name that didn't match the button text, leading to confusion because Voice Access wouldn't be able to find a control named "Create".

To fix this, we simply aligned the button text and the AutoProp.Name.

Closes #13808
2022-12-15 23:50:34 +00:00
PankajBhojwani
286fdfea79 Fix use of tabs instead of spaces in ColorSchemesPageViewModel.idl (#14550)
Didn't know the code formatter won't hit `idl` files
2022-12-13 23:39:53 +00:00
PankajBhojwani
decbc0f5a4 Improve the color schemes page in the SUI (#14470)
## Summary of the Pull Request

- [x] Clicking a color scheme in the list view immediately takes you to the page to edit the scheme
- [x] Adding a new scheme immediately takes you to the page to edit the new scheme
- [x] 'delete' and 'set as default' buttons have been moved to the edit scheme page

## PR Checklist
* [x] Closes #14289 
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [ ] Schema updated.
* [x] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

## Validation Steps Performed
The schemes page still works, keyboard navigation also works
2022-12-13 21:57:16 +00:00
Carlos Zamora
f9a36f1ff7 Match SearchBox AutoProp.Name to placeholder text (#14519)
Voice Access allows for functionality like "click \<name\>" to automatically move the cursor and click on a control. The Search Box had an AutoProp.Name that didn't match the placeholder text, leading to confusion because Voice Access wouldn't be able to find a control named "Find...".

To fix this, we simply aligned the placeholder text and the AutoProp.Name to be "Find". The elipses were removed because no dialog is opened.

The fix was accomplished in two ways to ensure that this is backportable:
1. via code, directly set the AutoProp.Name to match the placeholder text. 
2. in the resources file, explicitly match the AutoProp.Name to the placeholder text. This allows this change to be backportable in the event that the code above wasn't backported.

Closes #14398
2022-12-13 21:40:08 +00:00
Ian O'Neill
6a763bcd41 Ensure X/Y controls are updated when default launch pos selected (#14522)
Ensures the X & Y XAML controls are updated when the default launch position checkbox is toggled.
References #14518
2022-12-13 19:00:37 +00:00
Mike Griese
69728632eb Fix a crash when the theme doesn't have a window (#14540)
From team sync today.
2022-12-13 18:50:31 +00:00
Mike Griese
3a122bf420 DRAFT Spec for Broadcast Input (#9365)
### ⇒ [doc link](https://github.com/microsoft/terminal/blob/dev/migrie/s/2634-broadcast-input/doc/specs/drafts/%232634%20-%20Broadcast%20Input/%232634%20-%20Broadcast%20Input.md) ⇐

## Summary of the Pull Request

This is supposed to be a quick and dirty spec to socialize the various different options for Broadcast Input mode with the team. Hopefully we can come up with a big-picture design for the feature, so we can unblock #9222. 

### Abstract

> With a viable prototype in #9222, it's important that we have a well-defined
> plan for how we want this feature to be exposed before merging that PR. This
> spec is intended to be a lighter-than-usual spec to build consensus on the
> design of how the actions should be expressed.

...

> _**Fortunately**_: All these proposals actually use the same set of actions. So
> it doesn't _really_ matter which we pick right now. We can unblock #9222 as
> the implementation of the `"tab"` scope, and address other scopes in the future.
> We should still decide long-term which of these we'd like, but the actions seem
> universal.


## PR Checklist
* [x] Specs: #2634
* [x] References: #9222, #4998
* [x] I work here

## Detailed Description of the Pull Request / Additional comments
_\*<sup>\*</sup><sub>\*</sub> read the spec  <sub>\*</sub><sup>\*</sup>\*_
2022-12-13 10:40:40 -08:00
Carlos Zamora
28d28cb469 Introduce WT A11y Proposal 2023 (#14319)
The release of Windows Terminal has served as a way to reinvigorate the command-line ecosystem on Windows by providing a modern experience that is consistently updated. This experience has caused faster innovation in the Windows command-line ecosystem that can be seen across various areas like expanded virtual terminal sequence support and enhanced accessibility. Command-line apps can now leverage these innovations to create a better experience for the end-user.

Since accessibility is a very broad area, this document is intended to present recent innovations in the accessibility space for the command-line ecosystem. Furthermore, it will propose additional improvements that can be made alongside key stakeholders that could benefit from such changes.
2022-12-13 10:22:29 -08:00
Mike Griese
274bbf0e44 DRAFT Spec for Buffer Exporting and Logging (#11090)
### ⇒ [doc link](https://github.com/microsoft/terminal/blob/dev/migrie%2Fs%2F642-logging/doc/specs/drafts/%23642%20-%20Buffer%20Exporting%20and%20Logging/%23642%20-%20Buffer%20Exporting%20and%20Logging.md) ⇐

## Summary of the Pull Request

This is an intentionally brief spec to address the full scope of #642. The
intention of this spec is to quickly build consensus around all the features we
want for logging, and prepare an implementation plan.

### Abstract

> A common user need is the ability to export the history of a terminal session to
> a file, for later inspection or validation. This is something that could be
> triggered manually. Many terminal emulators provide the ability to automatically
> log the output of a session to a file, so the history is always captured. This
> spec will address improvements to the Windows Terminal to enable these kinds of
> exporting and logging scenarios.

## PR Checklist
* [x] Specs: #642
* [x] References: #5000, #9287, #11045, #11062
* [x] I work here

## Detailed Description of the Pull Request / Additional comments
_\*<sup>\*</sup><sub>\*</sub> read the spec  <sub>\*</sub><sup>\*</sup>\*_

## Open Discussion
* [ ] What formatting string syntax and variables do we want to use?
* [ ] How exactly do we want to handle "log printable output"? Do we include backspaces? Do we only log on newlines?
* [ ] > maybe consider even simpler options like just `${date}` and `${time}`, and allow for future variants with something like `${date:yyyy-mm-dd}` or `${time:hhmm}`
2022-12-13 17:06:28 +00:00
Dustin L. Howett
68cce101bc Remove our dependency on Microsoft.Toolkit.Win32.UI.XamlApplication (#14520)
We originally needed this library (or a separate DLL in our own project)
to handle hooking up the XAML resource loader to the providers that our
application needed. It was introduced in its nascent form in 2019, in a
PR titled "Make XAML files work."

It appears we no longer need it, and the provider hookup is being
handled by our `AppT2` base class override. I've tested this in Windows
10 Vb running unpackaged, and it seems to work totally fine. Crazy.

Removing this dependency saves us a couple hundred kilobytes on disk and
removes one consumer of the App CRT from our package.
2022-12-12 20:59:23 +00:00
Mike Griese
58ce22d484 Don't crash when turning on Voice Access (#14534)
I should have checked the `wparam`. 

Regressed in #14064.

Tested manually. No more crashy. 

Closes #14504
2022-12-12 19:25:00 +00:00
Floris Westerman
2668273616 New Tab Menu Customization (#13763)
Implements an initial version of #1571 as it has been specified, the
only big thing missing now is the possibility to add actions, which
depends on #6899.

Further upcoming spec tracked in #12584 

Implemented according to [instructions by @zadjii-msft]. Mostly
relatively straightforward, but some notable details:
- In accordance with the spec, the counting/indexing of profiles is
  based on their index in the json (so the index of the profile, not of
  the entry in the menu).
- Resolving a profile name to an actual profile is done in a similar
  fashion as how currently the `DefaultProfile` field is populated: the
  `CascadiaSettings` constructor now has an extra `_resolve` function
  that will iterate over all entries and resolve the names to instances;
  this same function will compute all profile sets (the set of all
  profiles from source "x", and the set of all remaining profiles)
- ~Fun~ fact: I spent two whole afternoons and evenings trying to add 2
  classes (which turned out to be a trivial `.vcxproj` error), and then
  managed to finish the entire rest of it in another afternoon and
  evening...

## Validation Steps Performed
A lot of manual testing; as mentioned above I was not able to run any
tests so I could not add them for now. However, the logic is not too
tricky so it should be relatively safe.

Closes #1571

[instructions by @zadjii-msft]: https://github.com/microsoft/terminal/issues/1571#issuecomment-1184851000
2022-12-09 22:40:38 +00:00
Jon Thysell
a5f9c85c39 Dynamically generate profiles from hosts in OpenSSH config files (#14042)
This PR adds a new dynamic profile generator which creates profiles to
quickly connect to detected SSH hosts.

This PR adds a new `SshHostGenerator` inbox dynamic profile generator.
When run, it looks for an install of our
[Win32-OpenSSH](https://github.com/PowerShell/Win32-OpenSSH) client app
`ssh.exe` in all of the (official) places it gets installed. If the exe
is found, the generator then looks for and parses both the user and
system OpenSSH config files for valid SSH hosts. Each host is then
converted into a profiles to call `ssh.exe` and connect to those hosts.

VALIDATION
Installed OpenSSH, configured host for alt.org NetHack server, connected
and played some NetHack from the created profile.

* [x] When OpenSSH is not installed, don't add profiles
* [x] Detected when installed via Optional Features (installs in
  `System32\OpenSSH`, added to PATH)
* [x] Detected when installed via the 32-Bit OpenSSH MSI from GitHub
  (installs in `Program Files (x86)\OpenSSH`, not added to PATH)
* [x] Detected when installed via the 64-Bit OpenSSH MSI from GitHub
  (installs in `Program Files\OpenSSH`, not added to PATH)
* [x] Detected when installed via `winget install
  Microsoft.OpenSSH.Beta` (uses MSI from GitHub)
* [x] With `"disabledProfileSources": ["Windows.Terminal.SSH"]` the
  profiles are not generated

Closes #9031

Co-authored-by: Carlos Zamora <carlos.zamora@microsoft.com>
Co-authored-by: Mike Griese <migrie@microsoft.com>
Closes https://github.com/microsoft/terminal/issues/9031
2022-12-09 16:01:53 -06:00
Mike Griese
84bb98bc81 Make the Pane inactive border respect the theme.window.applicationTheme (#14486)
We couldn't do this before, because `App::Current().Resources().Lookup()` would always return the OS theme version of a resource. That thread has lengthy details on why.

FORTUNATELY FOR US, this isn't the first time we've dealt with this. We've come up with a workaround in the past, and we can just use it again here. 

Closes #3917

This will also make #3061 easy 😉
2022-12-09 21:26:17 +00:00
James Holderness
4d27a05318 Add support for DEC macro operations (#14402)
## Summary of the Pull Request

This PR adds support for the DEC macro operations `DECDMAC` (Define Macro), and `DECINVM` (Invoke Macro), which allow an application to define a sequence of characters as a macro, and then later invoke that macro to execute the content as if it had just been received from the host.

This PR also adds two new `DSR` queries: one for reporting the available space remaining in the macro buffer (`DECMSR`), and another reporting a checksum of the macros that are currently defined (`DECCKSR`).

## PR Checklist
* [x] Closes #14205
* [x] CLA signed.
* [x] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

## Detailed Description of the Pull Request / Additional comments

I've created a separate `MacroBuffer` class to handle the parsing and storage of macros, so the `AdaptDispatch` class doesn't have to do much more than delegate the macro operations to that.

The one complication is the macro invocation, which requires injecting characters back into the state machine's input stream. Ideally we'd just pass the content to the `ProcessString` method, but we can't do that when it's already in the middle of a `CSI` dispatch.

My solution for this was to add an `OnCsiComplete` method via which we could register a callback function that injects the macro sequence only once the state machine has returned to the ground state. This feels a bit hacky, but that was the best approach I could come up with.

## Validation Steps Performed

Thanks to @KalleOlaviNiemitalo, we've been able to do some testing on a real VT420 to determine how the macro operations are intended to work, and I've tried to get our implementation to match that behavior as much as possible (we differ in some aspects of the checksum reporting, where the VT420 behavior seemed undesirable, or potentially buggy).

I've also added unit tests covering some of the same scenarios that we tested on the VT420.
2022-12-09 21:10:41 +00:00
Bharat Dev Burman
86aa666903 reword en-US value for confirmCloseAllTabs (#14473)
Fixes #14433
2022-12-09 14:54:55 -06:00
Mike Griese
031271f824 Re-add support for Mica, transparent titlebars (#13935)
This reverts commit 19b6d35.

This re-enables support for Mica, and transparent titlebars in general. It also syncs the titlebar opacity with the control opacity for `terminalBackground`. It also somehow fixes the bug where the bottom few pixels of the max btn doesn't work to trigger the snap flyout.

Closes #10509 

Does nothing for #13631

### To-done's

* [x] Check the mica API on 22000, windows 11 RTM
  - this works on 10.0.22621.674, but that's not 22000
* [x] Check how this behaves on windows 10. 
  - For both, this API just no-ops. That's fine! we can just say "Mica is only supported on >=22621"
2022-12-09 20:52:03 +00:00
Mike Griese
a5c5b8a50e Enable vintage opacity on Windows 10 (#14481)
This reverts #11372 and #11285, and brings #11180 to everyone, now that MSFT:37879806 has been serviced to everyone in [KB5011831](https://support.microsoft.com/en-gb/topic/april-25-2022-kb5011831-os-builds-19042-1682-19043-1682-and-19044-1682-preview-fe4ff411-d25a-4185-aabb-8bc66e9dbb6c)[1].

I tested this on my home Win10 laptop that's super old and doesn't have a functioning clock, but it does have that update at the very least. 

I don't think we had an issue tracking this?

[1]: I'm pretty sure about this at least
2022-12-09 20:50:56 +00:00
Leonard Hecker
c5d417fcaf Harden against resizing down to 0 columns/rows (#14467)
This changeset includes various guards against resizing the terminal down to 0
columns/rows: The 2 `TextBuffer` locations that accept new sizes, as well as
the `HwndTerminal::Refresh` which was the entrypoint causing the issue.

Closes #14404
2022-12-09 20:15:49 +00:00
PankajBhojwani
b9c3b5cd1e Fix setting launch position in the Settings UI not working (#14518)
## Summary of the Pull Request
This change should have been a part of #14190 but was missed. 

## PR Checklist
* [x] Closes #14469 
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [ ] Schema updated.
* [x] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

## Validation Steps Performed
Setting launch position in the settings UI works now
2022-12-09 17:51:26 +00:00
Javier
79c47f64a2 Added null check before rendering a string from the terminal connection (#14515)
## Summary of the Pull Request
Watson reports show that Visual Studio terminal attempts to render a string that is null causing the renderer to crash.

More specifically, we see "NULL_POINTER_WRITE_c0000005_PublicTerminalCore.dll!TextBuffer::WriteLine" with the following stack:
PublicTerminalCore!TextBuffer::WriteLine+0x1da
PublicTerminalCore!TextBuffer::Write+0x191
PublicTerminalCore!Microsoft::Terminal::Core::Terminal::_WriteBuffer+0x1d3
PublicTerminalCore!Microsoft::Terminal::Core::Terminal::PrintString+0x9
PublicTerminalCore!TerminalDispatch::PrintString+0x22
PublicTerminalCore!Microsoft::Console::VirtualTerminal::OutputStateMachineEngine::ActionPrintString+0x42
PublicTerminalCore!Microsoft::Console::VirtualTerminal::StateMachine::ProcessString+0x123
PublicTerminalCore!TerminalSendOutput+0x68
Microsoft_DotNet_MSBuildSdkResolver!DomainBoundILStubClass.IL_STUB_PInvoke(IntPtr, System.String)+0x8f
Microsoft_Terminal_Wpf!Microsoft.Terminal.Wpf.TerminalContainer.Connection_TerminalOutput(System.Object, Microsoft.Terminal.Wpf.TerminalOutputEventArgs)+0x20
Microsoft_VisualStudio_Terminal_Implementation!Microsoft.VisualStudio.Terminal.TerminalWindowBase+<>c__DisplayClass59_0+<<BeginProcessingPtyData>b__0>d.MoveNext()+0x55f

## References
Internal bug: [Bug 1614709](https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1614709): [Watson] crash64: NULL_POINTER_WRITE_c0000005_PublicTerminalCore.dll!TextBuffer::WriteLine

## Detailed Description of the Pull Request / Additional comments
Added a null check before PInvoking TerminalSendOutput.

## Validation Steps Performed
Validated locally that the check prevents null strings from rendering.
2022-12-09 17:44:35 +00:00
Steve Otteson
c0e4689e77 When the terminal has exited, ctrl+D to close pane, Enter to restart terminal (#14060)
When a terminal process exits (successful or not) and the profile isn't
set to automatically close the pane, a new message is displayed:

You can now close this terminal with ^D or Enter to restart.

Ctrl+D then is able to close the pane and Enter restarts it.

I originally tried to do this at the ConptyConnection layer by changing
the connection state from Failed to Closed, but then that didn't work
for the case where the process exited successfully but the profile isn't
set to exit automatically. So, I added an event to
ControlCore/TermControl that Pane watches. ControlCore watches to see if
the input is Ctrl+D (0x4) and if the connection is closed or failed, and
then raises the event so that Pane can close itself. As it turned out, I
think this is the better place to have the logic to watch for the Ctrl+D
key. Doing it at the ConptyConnection layer meant I had to parse out the
key from the escaped text passed to ConptyConnection::WriteInput.

## Validation Steps Performed
Tried adding lots of panes and then killing the processes outside of
Terminal. Each showed the new message and I could close them with Ctrl+D
or restart them with Enter. Also set a profile to never close
automatically to make sure Ctrl+D would work when a process exits
successfully.

Closes #12849 
Closes #11570
Closes #4379
2022-12-08 13:29:34 +00:00
Nicholas Bennett
da2b80bc0a Add support for switching the scheme based on the app's theme (#14064)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
This pull request solved the problem of users not being able to set color schemes specifically for dark or light mode. Now the code has been updated to accept a dark and light color scheme in the json. The old setting is still compatible. Keep in mind if you update your color scheme through the settings UI, it will set both dark and light to the color scheme selected. This is because the settings UI update for selecting both Dark and Light color schemes is not supported yet.

This also solves the problem of the UI not using the system OS theme. Now you can select system theme and your color scheme will be selected based on if the system theme is dark or light.


<!-- Other than the issue solved, is this relevant to any other issues/existing PRs? --> 
## References
#4066 

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes #4066 
* [x] Closes #14050
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA. 
* [x] Tests added/passed I believe so, added one test to ColorSchemeTests.cpp and I believe it passed. Also had to modify TerminalSettingsTests.cpp to accept the new ApplyAppearanceSettings function template
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [x] Schema updated.
* [x] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #4066 and also teams messages with @carlos-zamora 

<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
-Removed ColorSchemeName from MTSMSettings.h in order to process the setting for both string and object.
-Added DarkColorSchemeName and LightColorSchemeName properties to the AppearanceConfig to replace ColorSchemeName.
-Hacked a few processes to play nice with all 3 properties listed above as in some cases around the UI, we need to still use the ColorSchemeName. Once we change the UI I believe we can go back to just Dark and LightColorSchemeName
-Added and Updated Test to align to the new code.

Acceptable Json values,

"colorScheme": 
                {
                    "dark": "Campbell",
                    "light": "Campbell"
                }
or

"colorScheme": "Campbell"

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Individual testing along with the test case added.
2022-12-06 17:33:22 +00:00
Dustin L. Howett
32bf894f14 Add an empty PGO rule to the wpf test harness app (#14484)
This should fix the PGO build.
2022-12-06 16:26:06 +00:00
Mike Griese
b89c8ced22 Remove "Requires relaunch" from "use acrylic in tab row" text (#14478)
what it says on the can. 

* [x] closes #14182

![image](https://user-images.githubusercontent.com/18356694/205180310-e08c3fb2-0e99-41a4-ba60-7eba09fbb328.png)
2022-12-06 00:28:28 +00:00
Leonard Hecker
4d9a266c12 Merge IBaseData, IRenderData and IUiaData (#14427)
My goal is to make `IRenderData` "snapshottable", so that we
can render a frame of text without holding the console lock.
To facilitate this, this commit merges our 3 data interfaces
into one. It includes no actual changes apart from renames.
2022-12-05 19:38:31 +00:00
Leonard Hecker
391abafc2e Refactor ConsoleProcessList (#14421)
This commit is just a slight refactor of `ConsoleProcessList` which I've noticed
was in a poor shape. It replaces iterators with for-range loops, etc.

Additionally this fixes a bug in `SetConsoleWindowOwner`, where it used to
default to the newest client process instead of the oldest.

Finally, it changes the process container type from a doubly linked list
over to a simple array/vector, because using linked lists for heap allocated
elements felt quite a bit silly. To preserve the previous behavior of
`GetProcessList`, it simply iterates through the vector backwards.

## Validation Steps Performed
* All unit/feature tests pass 
* Launching a TUI application inside pwsh inside cmd
  and exiting kills all 3 applications 
2022-12-02 23:15:57 +00:00
Leonard Hecker
3c78e01ab5 Fix a potential deadlock for PtySignal::SetParent (#14463)
This changeset consists of two parts:
* Refactor `PtySignalInputThread` to move more code from `_InputThread`
  into the various `_Do*` handlers. This allows us to precisely control
  console locking behavior which is the cause of this bug.
* Add the 1-line fix to `_DoSetWindowParent` to unlock the console before
  calling foreign functions (`SetWindowLongPtrW` in this case).

This fix is theoretical in nature, based on a memory dump from an affected user
and most likely fixes: https://developercommunity.visualstudio.com/t/10199439

## Validation Steps Performed
* ConPTY tests complete. 
2022-12-01 23:13:18 +00:00
Leonard Hecker
4bbe3a388c Clean up CodepointWidthDetector (#14396)
My long-term plan is to replace the `CodepointWidth` enum with a simple integer
return value that indicates the amount of columns a codepoint is wide.
This is necessary so that we can return 0 for ZWJs (zero width joiners).

This initial commit represents a cleanup effort around `CodepointWidthDetector`.
Since less code runs faster, this change has the nice side-effect of running
roughly 5-10% faster across the board. It also drops the binary size by ~1.2kB.

## Validation Steps Performed
* `CodepointWidthDetectorTests` passes 
* U+26bf (``"`u{26bf}"`` inside pwsh) is a wide glyph
  in OpenConsole and narrow one in Windows Terminal 
2022-12-01 22:23:25 +00:00
Dustin L. Howett
62ffa4ba41 Give the root PID ownership of the pseudoconsole window (#14196)
This is a partial fix for the Get-Credential issue. While investigating it, I found that the pseudoconsole window is not marked as being "owned" (in NTUSER) by the PID/TID of the console application that is "hosted" "in" it. Doing this does not (and cannot) fix `Get-Credential` failing in DefTerm scenarios.

ConsoleSetWindowOwner is one of the operations that can be done by a conhost to any window, and so the RemoteConsoleControl can call through to the Win32 ConsoleControl to pull it off.

I chose to add SetWindowOwner to the IConsoleControl interface instead of moving ConsoleControl::Control into the interface to reduce the amount of churn and better separate interface responsibilities.

References #14119
2022-12-01 22:22:50 +00:00
Mike Griese
37aa29545b Implement the rest of the FTCS marks (#14341)
As noted in #11000.

This adds support for `FTCS_COMMAND_START`, `FTCS_COMMAND_EXECUTED` and `FTCS_COMMAND_FINISHED`, which allow a shell to more clearly markup parts of the buffer. 

As a trick, I'm also making the `experimental.autoMarkPrompts` setting act like a `FTCS_COMMAND_EXECUTED` if it comes after a `FTCS_COMMAND_START`. This lets the whole sequence work for cmd.exe (which wouldn't otherwise be possible).

* My cmd prompt
  ```bat
  PROMPT $e]133;D$e\$e]133;A$e\$e]9;9;$P$e\[$T]$e[97;46m%_seperator%$P$e[36;49m%_seperator%$e[0m$_$e[0m%_GITPROMPT%$e[94m%username%$e[0m@$e[32m%computername%$e[0m$G$e]133;B$e\
  ```
* pwsh profile, heavily cribbed from vscode
  ```pwsh

	$Global:__LastHistoryId = -1
	
	function Global:__Terminal-Get-LastExitCode {
	  if ($? -eq $True) {
	    return 0
	  }
	  # TODO: Should we just return a string instead?
	  # return -1
	  if ("$LastExitCode" -ne "") { return $LastExitCode }
	  return -1
	}
	
	function prompt {
	  # $gle = $LastExitCode
	
	
	  $gle = $(__Terminal-Get-LastExitCode);
	  $LastHistoryEntry = $(Get-History -Count 1)
	  # Skip finishing the command if the first command has not yet started
	  if ($Global:__LastHistoryId -ne -1) {
	    if ($LastHistoryEntry.Id -eq $Global:__LastHistoryId) {
	      # Don't provide a command line or exit code if there was no history entry (eg. ctrl+c, enter on no command)
	      $out += "`e]133;D`a"
	    } else {
	      # Command finished exit code
	      # OSC 633 ; D [; <ExitCode>] ST
	      $out += "`e]133;D;$gle`a"
	    }
	  }
	
	
	  $loc = $($executionContext.SessionState.Path.CurrentLocation);
	  # IMPORTANT: Make sure there's a printable charater _last_ in the prompt.
	  # Otherwise, PSReadline is gonna use the terminating `\` here and colorize
	  # that if it detects a syntax error
	  $out += "`e]133;A$([char]07)";
	  $out += "`e]9;9;`"$loc`"$([char]07)";
	  $out += "PWSH $loc$('>' * ($nestedPromptLevel + 1)) ";
	  $out += "`e]133;B$([char]07)";
	
	  $Global:__LastHistoryId = $LastHistoryEntry.Id
	
	  return $out
	}
  ```



* Doesn't close any issues, because this was always just an element in #11000
* I work here
* From FHL code
2022-12-01 22:22:07 +00:00
Mike Griese
52cc523ed2 Find out how often are people actually using marks, Themes (#14356)
Let's find out!

* [x] This is all the current bullets in #14324. I'm going to leave that open till we signoff on the release.
2022-12-01 02:10:53 +00:00
Leonard Hecker
0eff8c06e3 Clean up til::point/size/rect member usage (#14458)
This is a follow-up of #13025 to make the members of `til::point/size/rect`
uniform and consistent without the use of `unions`. The only file that has
any changes is `src/host/getset.cpp` where an if condition was simplified.

## Validation Steps Performed
* Host unit tests 
* Host feature tests 
* ControlCore feature tests 
2022-12-01 00:40:00 +00:00
James Holderness
437914807a Add support for the DECRQM escape sequence (#14444)
This PR adds support for the `DECRQM` (Request Mode) escape sequence,
which allows applications to query the state of the various modes
supported by the terminal. It also adds support for the `DECNKM` mode,
which aliases the existing `DECKPAM` and `DECKPNM` operations, so they
can be queried with `DECRQM` too.

This is one solution for #10153 (saving and restoring the state of
bracketed paste mode), and should also help with #1040 (providing a way
for clients to determine the capabilities of the terminal).

Prior to adding `DECRQM`, I also did some refactoring of the mode
handling to get rid of the mode setting methods in the `ITermDispatch`
interface that had no need to be there. Most of them were essentially a
single line of code that could easily be executed directly from the
`_ModeParamsHelper` handler anyway.

As part of this refactoring I combined all the internal `AdaptDispatch`
modes into an `enumset` to allow for easier management, and made sure
all modes were correctly reset in the `HardReset` method (prior to this,
there were a number of modes that we weren't restoring when we should
have been).

And note that there are some differences in behavior between conhost and
Windows Terminal. In conhost, `DECRQM` will report bracketed paste mode
as unsupported, and in Terminal, both `DECCOLM` and `AllowDECCOLM` are
reported as unsupported. And `DECCOLM` is now explicitly ignored in
conpty mode, to avoid the conpty client and conhost getting out of sync.
 
## Validation Steps Performed

I've manually confirmed that all the supported modes are reported in the
`DECRQM` tests in Vttest, and I have my own test scripts which I've used
to confirm that `RIS` is now resetting the modes correctly.

I've also added a unit test in `AdapterTest` that iterates through the
modes, checking the responses from `DECRQM` for both the set and reset
states.

I should also mention that I had to do some refactoring of the existing
tests to compensate for methods that were removed from `ITermDispatch`,
particularly in `OutputEngineTest`. In many cases, though, these tests
weren't doing much more than testing the test framework.
2022-11-30 22:05:54 +00:00
Dustin L. Howett
d1fbbb8a83 Merge the DotNet_###Test "platforms" back into the right place (#14468)
I originally added these platforms to prevent the .NET components from
building when you built the entire solution, and to prevent them from
building in CI.

It turns out that managing an extra thousand project-platform-config
triples is an absolute pain, **and** that we should have been building
these things in CI the entire time. So.

This should make life _a lot_ easier.

As a bonus, this PR enables the WPF test harness to build for ARM64.
2022-11-30 19:46:29 +00:00
Dustin L. Howett
65ada350ce Migrate spelling-0.0.21 changes from main 2022-01-14 18:04:01 -08:00
Michael Niksa
3fb8a8be4b make a note 2022-01-14 18:04:01 -08:00
Michael Niksa
cd1c4b6b10 IT WORKS! 2022-01-14 17:52:17 -08:00
Michael Niksa
6cac0969f3 attempt to replicate algorithm 2022-01-14 15:15:39 -08:00
387 changed files with 11445 additions and 6785 deletions

View File

@@ -1,20 +1,14 @@
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
<!-- Other than the issue solved, is this relevant to any other issues/existing PRs? -->
## References
## References and Relevant Issues
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [ ] Closes #xxx
* [ ] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx
<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
## PR Checklist
- [ ] Closes #xxx
- [ ] Tests added/passed
- [ ] Documentation updated
- If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
- [ ] Schema updated (if necessary)

View File

@@ -2,6 +2,7 @@ admins
allcolors
Apc
apc
backpressure
breadcrumb
breadcrumbs
bsd
@@ -12,18 +13,23 @@ clickable
clig
CMMI
copyable
CtrlDToClose
cybersecurity
dalet
Dcs
dcs
deselection
dialytika
diffing
dje
downside
downsides
dze
dzhe
DTo
EDDB
EDDC
Emacspeak
Enum'd
Fitt
formattings
@@ -60,6 +66,7 @@ lol
lorem
Lorigin
maxed
megathread
minimalistic
mkmk
mnt
@@ -85,6 +92,7 @@ shcha
slnt
Sos
ssh
stakeholders
timeline
timelines
timestamped

View File

@@ -56,6 +56,7 @@ QWORD
regedit
robocopy
SACLs
segoe
sdkddkver
Shobjidl
Skype

View File

@@ -23,6 +23,7 @@ Griese
Hernan
Howett
Illhardt
Imms
iquilezles
italo
jantari
@@ -34,6 +35,7 @@ KODELIFE
Kodelife
Kourosh
kowalczyk
leonardder
leonmsft
Lepilleur
lhecker
@@ -77,15 +79,18 @@ sonpham
stakx
talo
thereses
Thysell
Walisch
WDX
Wellons
Westerman
Wirt
Wojciech
zadjii
Zamor
Zamora
zamora
zljubisic
Zoey
zorio
Zverovich

View File

@@ -61,7 +61,6 @@ argb
ARRAYSIZE
ARROWKEYS
asan
ASBRST
ASBSET
ASDF
asdfghjkl
@@ -252,6 +251,7 @@ conattrs
conbufferout
concfg
conclnt
concretizations
conddkrefs
condrv
conechokey
@@ -410,20 +410,24 @@ DECAWM
DECBKM
DECCARA
DECCKM
DECCKSR
DECCOLM
DECCRA
DECCTR
DECDHL
decdld
DECDMAC
DECDWL
DECEKBD
DECERA
DECFRA
DECID
DECINVM
DECKPAM
DECKPM
DECKPNM
DECLRMM
DECMSR
DECNKM
DECNRCM
DECOM
@@ -433,6 +437,7 @@ DECRARA
DECRC
DECREQTPARM
DECRLM
DECRPM
DECRQM
DECRQSS
DECRQTSR
@@ -453,6 +458,7 @@ DECSLRM
DECSMKR
DECSR
DECSTBM
DECSTGLT
DECSTR
DECSWL
DECTCEM
@@ -811,6 +817,7 @@ HORZ
hostable
hostlib
HPA
hpcon
HPCON
hpj
HPR
@@ -1818,6 +1825,7 @@ SYSCOMMAND
SYSDEADCHAR
SYSKEYDOWN
SYSKEYUP
SYSLIB
SYSLINK
SYSMENU
sysparams
@@ -2183,6 +2191,7 @@ wnd
WNDALLOC
WNDCLASS
WNDCLASSEX
WNDCLASSEXW
WNDCLASSW
Wndproc
WNegative
@@ -2288,6 +2297,7 @@ YOffset
YSubstantial
YVIRTUALSCREEN
YWalk
zabcd
Zabcdefghijklmnopqrstuvwxyz
ZCmd
ZCtrl

View File

@@ -7,6 +7,7 @@ on:
- labeled
- unlabeled
permissions: {}
jobs:
add-to-project:
name: Add issue to project

View File

@@ -101,7 +101,7 @@ If you don't have any additional info/context to add but would like to indicate
If you're able & willing to help fix issues and/or implement features, we'd love your contribution!
The best place to start is the list of ["Easy Starter"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22+label%3A%22Easy+Starter%22+) issues. These are bugs or tasks that we on the team believe would be easier to implement for someone without any prior experience in the codebase. Once you're feeling more comfortable in the codebase, feel free to just use the ["Help Wanted"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22+) label, or just find an issue your interested in and hop in!
The best place to start is the list of ["good first issue"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22++label%3A%22good+first+issue%22+)s. These are bugs or tasks that we on the team believe would be easier to implement for someone without any prior experience in the codebase. Once you're feeling more comfortable in the codebase, feel free to just use the ["Help Wanted"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22+) label, or just find an issue you're interested in and hop in!
Generally, we categorize issues in the following way, which is largely derived from our old internal work tracking system:
* ["Bugs"](https://github.com/microsoft/terminal/issues?q=is%3Aopen+is%3Aissue+label%3A%22Issue-Bug%22+) are parts of the Terminal & Console that are not quite working the right way. There's code to already support some scenario, but it's not quite working right. Fixing these is generally a matter of debugging the broken functionality and fixing the wrong code.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,117 @@
{
"Version": "1.0.0",
"UseMinimatch": false,
"SignBatches": [
{
"MatchedPath": [
// Namespaced DLLs
"Microsoft.Terminal.*.dll",
"Microsoft.Terminal.*.winmd",
// ConPTY and DefTerm
"OpenConsole.exe",
"OpenConsoleProxy.dll",
// VCRT Forwarders
"*_app.dll",
// Legacy DLLs with old names
"TerminalApp.dll",
"TerminalApp.winmd",
"TerminalConnection.dll",
"TerminalThemeHelpers.dll",
"WindowsTerminalShellExt.dll",
// The rest
"TerminalAzBridge.exe",
"wt.exe",
"WindowsTerminal.exe",
"elevate-shim.exe"
],
"SigningInfo": {
"Operations": [
{
"KeyCode": "CP-230012",
"OperationSetCode": "SigntoolSign",
"Parameters": [
{
"parameterName": "OpusName",
"parameterValue": "Microsoft"
},
{
"parameterName": "OpusInfo",
"parameterValue": "http://www.microsoft.com"
},
{
"parameterName": "FileDigest",
"parameterValue": "/fd \"SHA256\""
},
{
"parameterName": "PageHash",
"parameterValue": "/NPH"
},
{
"parameterName": "TimeStamp",
"parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
}
],
"ToolName": "sign",
"ToolVersion": "1.0"
},
{
"KeyCode": "CP-230012",
"OperationSetCode": "SigntoolVerify",
"Parameters": [],
"ToolName": "sign",
"ToolVersion": "1.0"
}
]
}
},
{
// THIRD PARTY SOFTWARE
"MatchedPath": [
"cpprest*.dll"
],
"SigningInfo": {
"Operations": [
{
"KeyCode": "CP-231522",
"OperationSetCode": "SigntoolSign",
"Parameters": [
{
"parameterName": "OpusName",
"parameterValue": "Microsoft"
},
{
"parameterName": "OpusInfo",
"parameterValue": "http://www.microsoft.com"
},
{
"parameterName": "FileDigest",
"parameterValue": "/fd \"SHA256\""
},
{
"parameterName": "PageHash",
"parameterValue": "/NPH"
},
{
"parameterName": "TimeStamp",
"parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
}
],
"ToolName": "sign",
"ToolVersion": "1.0"
},
{
"KeyCode": "CP-231522",
"OperationSetCode": "SigntoolVerify",
"Parameters": [],
"ToolName": "sign",
"ToolVersion": "1.0"
}
]
}
}
]
}

View File

@@ -63,6 +63,7 @@ parameters:
- Win11
variables:
MakeAppxPath: 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x86\MakeAppx.exe'
TerminalInternalPackageVersion: "0.0.7"
# If we are building a branch called "release-*", change the NuGet suffix
# to "preview". If we don't do that, XES will set the suffix to "release1"
@@ -270,6 +271,28 @@ jobs:
displayName: 'Generate SBOM manifest'
inputs:
BuildDropPath: '$(System.ArtifactsDirectory)/appx'
- pwsh: |-
$Package = (Get-ChildItem "$(Build.ArtifactStagingDirectory)/appx" -Recurse -Filter "Cascadia*.msix" | Select -First 1)
$PackageFilename = $Package.FullName
Write-Host "##vso[task.setvariable variable=WindowsTerminalPackagePath]${PackageFilename}"
& "$(MakeAppxPath)" unpack /p $PackageFilename /d "$(Build.SourcesDirectory)\UnpackedTerminalPackage"
displayName: Unpack the new Terminal package for signing
- task: EsrpCodeSigning@1
displayName: Submit Terminal's binaries for signing
inputs:
ConnectedServiceName: 9d6d2960-0793-4d59-943e-78dcb434840a
FolderPath: '$(Build.SourcesDirectory)\UnpackedTerminalPackage'
signType: batchSigning
batchSignPolicyFile: '$(Build.SourcesDirectory)\build\config\ESRPSigning_Terminal.json'
- pwsh: |-
$PackageFilename = "$(WindowsTerminalPackagePath)"
Remove-Item "$(Build.SourcesDirectory)\UnpackedTerminalPackage\CodeSignSummary*"
& "$(MakeAppxPath)" pack /h SHA256 /o /p $PackageFilename /d "$(Build.SourcesDirectory)\UnpackedTerminalPackage"
displayName: Re-pack the new Terminal package after signing
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (appx)
inputs:

View File

@@ -6,6 +6,10 @@ steps:
- task: NuGetAuthenticate@0
- script: |-
echo ##vso[task.setvariable variable=NUGET_RESTORE_MSBUILD_ARGS]/p:Platform=$(BuildPlatform)
displayName: Ensure NuGet restores for $(BuildPlatform)
# In the Microsoft Azure DevOps tenant, NuGetCommand is ambiguous.
# This should be `task: NuGetCommand@2`
- task: 333b11bd-d341-40d9-afcf-b32d5ce6f23b@2

View File

@@ -15,9 +15,9 @@
<VersionBuildRevision Condition="'$(TerminalTargetWindowsVersion)'=='Win11' and '$(VersionBuildRevision)'!=''">$([MSBuild]::Add($(VersionBuildRevision), 1))</VersionBuildRevision>
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
<XesBaseYearForStoreVersion>2022</XesBaseYearForStoreVersion>
<XesBaseYearForStoreVersion>2023</XesBaseYearForStoreVersion>
<VersionMajor>1</VersionMajor>
<VersionMinor>17</VersionMinor>
<VersionMinor>18</VersionMinor>
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
</PropertyGroup>
</Project>

View File

@@ -2,7 +2,6 @@
<!-- The packages.config acts as the global version for all of the NuGet packages contained within. -->
<packages>
<!-- Native packages -->
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.1.3" targetFramework="native" />
<package id="Microsoft.Internal.PGO-Helpers.Cpp" version="0.2.34" targetFramework="native" />
<package id="Microsoft.Taef" version="10.60.210621002" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.210825.3" targetFramework="native" />

View File

@@ -145,7 +145,7 @@ Our only backport successes really come from corporations with massive addressab
It's also costly in terms of time, effort, and testing for us to validate a modification to a released OS. We have a mindbogglingly massive amount of automated machinery dedicated to processing and validating the things that we check in while developing the current OS builds. But it's a special costly ask to spin up some to all of those activities to validate backported fixes. We do it all the time for Patch Tuesday, but in those patches, they only pass through the minimum number of fixes required to maximize the restoration of productivity/security/revenue/etc. because every additional fix adds additional complexity and additional risk.
So from our little team working hard to make developers happy, we virtually never make the cut for servicing. We're sorry, but we hope you can understand. It's just the reality of the situation to say "nope" when people ask for a backport. In our team's ideal world, you would all be running the latest console bits everywhere everytime we make a change. But that's just not how it is today.
So from our little team working hard to make developers happy, we virtually never make the cut for servicing. We're sorry, but we hope you can understand. It's just the reality of the situation to say "nope" when people ask for a backport. In our team's ideal world, you would all be running the latest console bits everywhere every time we make a change. But that's just not how it is today.
Original Source: https://github.com/microsoft/terminal/issues/279#issuecomment-439179675

View File

@@ -82,7 +82,14 @@
"properties": {
"colorScheme": {
"description": "The name of a color scheme to use when unfocused.",
"type": "string"
"oneOf": [
{
"$ref": "#/$defs/SchemePair"
},
{
"type": "string"
}
]
},
"foreground": {
"$ref": "#/$defs/Color",
@@ -233,6 +240,21 @@
},
"type": "object"
},
"SchemePair": {
"description": "Contains both a light and dark color scheme for the Terminal to use, depending on the theme of the application.",
"properties": {
"light": {
"default": "Campbell",
"description": "Name of the scheme to use when the app is using light theme",
"type": "string"
},
"dark": {
"default": "Campbell",
"description": "Name of the scheme to use when the app is using dark theme",
"type": "string"
}
}
},
"FontConfig": {
"properties": {
"face": {
@@ -539,6 +561,160 @@
},
"type": "object"
},
"NewTabMenuEntryType": {
"enum": [
"source",
"profile",
"folder",
"separator",
"remainingProfiles",
"matchProfiles"
]
},
"NewTabMenuEntry": {
"properties": {
"type": {
"description": "The type of menu entry",
"$ref": "#/$defs/NewTabMenuEntryType"
}
},
"required": [
"type"
],
"type": "object"
},
"FolderEntryInlining": {
"enum": [
"never",
"auto"
]
},
"FolderEntry": {
"description": "A folder entry in the new tab dropdown",
"allOf": [
{
"$ref": "#/$defs/NewTabMenuEntry"
},
{
"properties": {
"type": {
"type": "string",
"const": "folder"
},
"name": {
"type": "string",
"default": "",
"description": "The name of the folder to show in the menu"
},
"icon": {
"$ref": "#/$defs/Icon"
},
"entries": {
"type": "array",
"description": "The entries to put inside this folder",
"minItems": 1,
"items": {
"$ref": "#/$defs/NewTabMenuEntry"
}
},
"inline": {
"description": "When set to auto and the folder only has a single entry, the entry will show directly and no folder will be rendered",
"default": "never",
"$ref": "#/$defs/FolderEntryInlining"
},
"allowEmpty": {
"description": "Whether to render a folder without entries, or to hide it",
"default": "false",
"type": "boolean"
}
}
}
]
},
"SeparatorEntry": {
"description": "A separator in the new tab dropdown",
"allOf": [
{
"$ref": "#/$defs/NewTabMenuEntry"
},
{
"properties": {
"type": {
"type": "string",
"const": "separator"
}
}
}
]
},
"ProfileEntry": {
"description": "A profile in the new tab dropdown",
"allOf": [
{
"$ref": "#/$defs/NewTabMenuEntry"
},
{
"properties": {
"type": {
"type": "string",
"const": "profile"
},
"profile": {
"type": "string",
"default": "",
"description": "The name or GUID of the profile to show in this entry"
}
}
}
]
},
"RemainingProfilesEntry": {
"description": "The set of profiles that are not yet explicitly included in another entry, such as the profile or source entries. This entry can be used at most one time!",
"allOf": [
{
"$ref": "#/$defs/NewTabMenuEntry"
},
{
"properties": {
"type": {
"type": "string",
"const": "remainingProfiles"
}
}
}
]
},
"MatchProfilesEntry": {
"description": "A set of profiles all matching the given name, source, or command line, to show in the new tab dropdown",
"allOf": [
{
"$ref": "#/$defs/NewTabMenuEntry"
},
{
"properties": {
"type": {
"type": "string",
"const": "matchProfiles"
},
"name": {
"type": "string",
"default": "",
"description": "The name of the profiles to match"
},
"source": {
"type": "string",
"default": "",
"description": "The source of the profiles to match"
},
"commandline": {
"type": "string",
"default": "",
"description": "The command line of the profiles to match"
}
}
}
]
},
"SwitchToAdjacentTabArgs": {
"oneOf": [
{
@@ -1546,6 +1722,145 @@
}
]
},
"ShowCloseButton": {
"enum": [
"always",
"hover",
"never"
],
"type": "string"
},
"ThemeColor": {
"description": "A special kind of color for use in themes. Can be an #rrggbb color, #rrggbbaa color, or a special value. 'accent' is evaluated as the user's selected Accent color in the OS, and 'terminalBackground' will be evaluated as the background color of the active terminal pane.",
"oneOf": [
{
"pattern": "^#[A-Fa-f0-9]{3}(?:[A-Fa-f0-9]{3}(?:[A-Fa-f0-9]{2})?)?$",
"type": "string",
"format": "color",
"default": "#000000ff"
},
{
"const": "accent",
"type": "string"
},
{
"const": "terminalBackground",
"type": "string"
},
{
"type": "null"
}
]
},
"TabTheme": {
"additionalProperties": false,
"description": "A set of properties for customizing the appearance of the tabs",
"properties": {
"background": {
"description": "The color of a tab when it is the active tab",
"$ref": "#/$defs/ThemeColor"
},
"unfocusedBackground": {
"description": "The color of a tab when it is not the active tab",
"$ref": "#/$defs/ThemeColor"
},
"showCloseButton": {
"description": "Controls the visibility of the close button on the tab",
"$ref": "#/$defs/ShowCloseButton"
}
}
},
"TabRowTheme": {
"additionalProperties": false,
"description": "A set of properties for customizing the appearance of the tab row",
"properties": {
"background": {
"description": "The color of the tab row when the window is the foreground window.",
"$ref": "#/$defs/ThemeColor"
},
"unfocusedBackground": {
"description": "The color of the tab row when the window is inactive",
"$ref": "#/$defs/ThemeColor"
}
}
},
"WindowTheme": {
"additionalProperties": false,
"description": "A set of properties for customizing the appearance of the window itself",
"properties": {
"applicationTheme": {
"description": "Which UI theme the Terminal should use for controls",
"enum": [ "light", "dark", "system" ],
"type": "string"
},
"useMica": {
"description": "True if the Terminal should use a Mica backdrop for the window. This will apply underneath all controls (including the terminal panes and the titlebar)",
"type": "boolean",
"default": false
}
}
},
"Theme": {
"additionalProperties": false,
"description": "A set of properties for customizing the appearance of the window. This controls things like the titlebar, the tabs, the application theme.",
"properties": {
"name": {
"type": "string",
"description": "The name of the theme. This will be displayed in the settings UI.",
"not": {
"enum": [ "light", "dark", "system" ]
}
},
"tab": {
"$ref": "#/$defs/TabTheme"
},
"tabRow": {
"$ref": "#/$defs/TabRowTheme"
},
"window": {
"$ref": "#/$defs/WindowTheme"
}
}
},
"ThemePair": {
"additionalProperties": false,
"description": "A pair of Theme names, to allow the Terminal to switch theme based on the OS theme",
"properties": {
"light": {
"type": "string",
"description": "The name of the theme to use when the OS is in Light theme",
"default": "light"
},
"dark": {
"type": "string",
"description": "The name of the theme to use when the OS is in Dark theme",
"default": "dark"
}
}
},
"NewTabMenu": {
"description": "Defines the order and structure of the 'new tab' menu. It can consist of e.g. profiles, folders, and separators.",
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/$defs/FolderEntry"
},
{
"$ref": "#/$defs/SeparatorEntry"
},
{
"$ref": "#/$defs/ProfileEntry"
},
{
"$ref": "#/$defs/MatchProfilesEntry"
},
{
"$ref": "#/$defs/RemainingProfilesEntry"
}
]
}
},
"Keybinding": {
"additionalProperties": false,
"properties": {
@@ -1924,20 +2239,32 @@
},
"type": "array"
},
"newTabMenu": {
"$ref": "#/$defs/NewTabMenu"
},
"language": {
"default": "",
"description": "Sets an override for the app's preferred language, expressed as a BCP-47 language tag like en-US.",
"type": "string"
},
"theme": {
"default": "system",
"description": "Sets the theme of the application. The special value \"system\" refers to the active Windows system theme.",
"enum": [
"light",
"dark",
"system"
],
"type": "string"
"default": "dark",
"description": "Sets the theme of the application. This value should be the name of one of the themes defined in `themes`. The Terminal also includes the themes `dark`, `light`, and `system`.",
"oneOf": [
{
"type": "string"
},
{
"$ref": "#/$defs/ThemePair"
}
]
},
"themes": {
"description": "The list of available themes",
"items": {
"$ref": "#/$defs/Theme"
},
"type": "array"
},
"showTabsInTitlebar": {
"default": true,
@@ -2170,7 +2497,14 @@
"colorScheme": {
"default": "Campbell",
"description": "Name of the terminal color scheme to use. Color schemes are defined under \"schemes\".",
"type": "string"
"oneOf": [
{
"$ref": "#/$defs/SchemePair"
},
{
"type": "string"
}
]
},
"commandline": {
"description": "Executable used in the profile.",

View File

@@ -0,0 +1,410 @@
---
author: Mike Griese @zadjii-msft
created on: 2021-03-03
last updated: 2022-11-04
issue id: #2634
---
# Broadcast Input
## Abstract
"Broadcast Input" is a feature present on other terminals which allows the user
to send the same input to multiple tabs or panes at the same time. This can make
it simpler for the user to run the same command in multiple directories or
servers at the same time.
With a viable prototype in [#9222], it's important that we have a well-defined
plan for how we want this feature to be exposed before merging that PR. This
spec is intended to be a lighter-than-usual spec to build consensus on the
design of how the actions should be expressed.
## Background
### Inspiration
This spec is heavily inspired by the [iTerm2 implementation]. @carlos-zamora did
a great job of breaking down how iTerm2 works in [this comment].
SecureCRT also implements a similar feature using a "chat window" that can send
the input in the chat window to all tabs. This seemed like a less ergonomic
solution, so it was not seriously considered.
Additionally, Terminator (on \*nix) allows for a similar feature through the use
of "groups". From [@zljubisic]:
> In Linux terminator you can define groups, and than put each pane in one of
> defined groups. Afterwards, you can choose broadcasting to all panes, only
> certain group or no broadcast at all.
This also seemed like a less powerful version of broadcast input than the
iterm2-like version, so it was also not further investigated.
### User Stories
iTerm2 supports the following actions:
* **Story A:** _Send input to current session only_: The default setting.
* **Story B:** _Broadcast to all panes in all tabs_: Anything you type on the
keyboard goes to all sessions in this window.
* **Story C:** _Broadcast to all panes in current tab_: Anything you type on the
keyboard goes to all sessions in this tab.
* **Story D:** _Toggle broadcast input to current session_: Toggles whether this
session receives broadcasted keystrokes within this window.
## Solution Design
### Proposal 1: iTerm2-like Modal Input Broadcast
iTerm2 implements broadcast input as a type of "modal" system. The user is in
one of the following modes:
* Broadcast to all panes in all tabs
* Broadcast to all panes in the current tab
* Broadcast to some set of panes within the current tab
* Don't broadcast input at all (the default behavior)
These modes are vaguely per-tab state. There's a global "broadcast to all tabs &
panes" property. Then, each tab also has a pair of values:
* Should input be sent to all panes in this tab?
* If not, which panes should input be sent to?
It's not possible to send input to one pane in tab A, then another pane in tab
B, without enabling the global "broadcast to everyone" mode.
This seems to break down into the following actions:
```json
{ "action": "toggleBroadcastInput", "scope": "window" },
{ "action": "toggleBroadcastInput", "scope": "tab" },
{ "action": "toggleBroadcastInput", "scope": "pane" },
{ "action": "disableBroadcastInput" },
```
Which would be accompanied by the following internal properties:
* A window (`TerminalPage`-level) property for `broadcastToAllPanesAndTabs`
* A per-tab property for `broadcastToAllPanes`
* A per-tab set of panes to broadcast to
The scopes would work as follows:
* `"scope": "window"`: Toggle the window's "broadcast to all tabs and panes"
setting.
* `"scope": "tab"`: Toggle the tab's "broadcast to all panes in this tab"
setting.
- This does not modify the set of panes that the user is broadcasting to in
the tab, merely toggles the tab's setting. If the user has a set of panes
they're broadcasting to in this tab, then toggles this setting on and off,
we'll return to broadcasting to that set.
* `"scope": "pane"`: Add this pane to the set of panes being broadcasted to in
this tab.
- **TODO!: FOR DISCUSSION**: Should this disable the tab's
"broadcastToAllPanes" setting? Or should it leave that alone?
* `"disableBroadcastInput"`: Set the global setting to false, the tab's setting
to false, and clear the set of panes being broadcasted to for this tab.
- **TODO!** This could also just be `"action": "toggleBroadcastInput",
"scope": "none"`
#### Pros
* This is exactly how iTerm2 does it, so there's prior art.
* If you're not globally broadcasting, then you're only ever broadcasting to
some (sub)set of the panes in the current tab. So global broadcast mode is
the only time a user would need to worry about input being to be sent to
an inactive tab.
* You can have a set of panes to broadcast to in the first tab, then a
_separate_ set to broadcast to in a second tab. Broadcasting in one tab
does not affect the other.
#### Cons
* I frankly think the `tab`/`pane` interaction can be a little weird. Like for
this scenario:
- enable broadcast input for tab 1
- switch to tab 2
- enable broadcast input for a pane in tab 2
There's valid confusion to be had between the following two behaviors:
1. input goes to all of tab 1 _and_ the pane in tab 2
2. input only goes to the pane in tab 2
* You can't broadcast to a subset of panes in inactive tabs, in addition to
the active tab. All panes you want to broadcast to must be in the active
tab.
* Does creating a new split in a pane that's being broadcast to add that pane to
the broadcast set?
#### What would this mean for PR #9222?
The prototype PR [#9222] basically just implemented `{ "action":
"toggleBroadcastInput", "scope": "tab" }`. We could make `tab` the default
`scope` if no other one is specified, and then the PR would need basically no
modifications. Future PRs could add args to the `toggleBroadcastInput` action,
without breaking users who bind a key to that action now.
### Proposal 2: Broadcast Set
This was the design I had originally came up with before investigating iTerm2
much closer. This design involves a "broadcast set" of panes. All the panes in
the broadcast set would also get the `KeySent` and `CharSent` events, in
addition to the active pane. (The active pane may be a part of the broadcast
set). If a pane is read-only in the broadcast set, then it won't handle those
broadcasted events (obviously).
As far as actions, we're looking at something like:
* **A** Only send input to the active pane
* Remove all the panes from the broadcast set
* **B** send input to all panes in all tabs
* If all the panes are in the broadcast set, remove them all. Otherwise, add
all panes in all tabs to the broadcast set.
* **C** send input to all panes in the current tab
* If all the panes in the current tab are in the broadcast set, remove them
from the broadcast set. Otherwise, add all the panes from this tab to the
broadcast set.
* **D** toggle sending input to the current pane
* If this pane is in the broadcast set, remove it. Otherwise add it.
This seems to break down into the following actions:
```json
{ "action": "disableBroadcastInput" },
{ "action": "toggleBroadcastInput", "scope": "window" },
{ "action": "toggleBroadcastInput", "scope": "tab" },
{ "action": "toggleBroadcastInput", "scope": "pane" },
```
Which would be accompanied by the following internal properties:
* A window (`TerminalPage`-level) set of panes to broadcast to.
#### Pros:
* Mentally, you're either adding panes to the set of panes to broadcast to, or
removing them.
* You can broadcast to panes in multiple tabs, without broadcasting to _all_
panes in all tabs.
#### Cons:
* is _slightly_ different from iTerm2.
* Does creating a new split in a pane that's being broadcast to add that pane to
the broadcast set?
* You can't have a set of panes to broadcast to in the one tab, and a different
set in another tab. As an example:
1. in tab 1, you add panes A and B to the broadcast set. Typing in either one
goes to both A and B.
2. in tab 1, switch to pane C. Now input goes to A, B and C.
3. in tab 1, switch to pane D. Now input goes to A, B and D.
4. switch to tab 2, pane E. Now input goes to A, B and E.
You can't have like, a set with A & B (in 1), then E & F (in 2). So if someone
wants to type to both panes in 1, then both panes in 2, then both panes in 1,
they need to keep toggling which panes are in the broadcast set.
#### What would this mean for PR #9222?
Similar to Proposal 1, we'd use `tab` as the default value for `scope`. In the
future, when we add support for the other scopes, we'd change how the
broadcasting works, to use a set of panes to broadcast to, instead of just the
tab-level property.
### Proposal 3: It's iTerm2, but slightly different
While typing this up, I thought maybe it might make more sense if we took the
iTerm2 version, and changed it slightly:
* `"scope": "tab"`: If all the panes are in the broadcast set for this tab, then
remove them all. Otherwise, add all the panes in this tab to this tab's
broadcast set.
* `"scope": "pane"`: If this pane is in the broadcast set for a tab, then remove
it. Otherwise, add it.
With this, we get rid of the tab-level setting for "broadcast to all the panes
in this tab", and rely only on the broadcast set for that tab.
#### Pros:
* All the pros from proposal A
* Does away with the seemingly weird toggling between "all the panes in a tab"
and "some of the panes in a tab" that's possible with proposal A
#### Cons:
* You can't broadcast to a subset of panes in inactive tabs, in addition to
the active tab. All panes you want to broadcast to must be in the active
tab.
* is _slightly_ different from iTerm2. Just _slightly_.
* Does creating a new split in a pane that's being broadcast to add that pane to
the broadcast set?
#### What would this mean for PR #9222?
Same as with proposal A, we wouldn't change anything in the current PR. A future
PR that would add the other scope's to that action would need to change how the
broadcasting within a tab works, to use a set of panes to broadcast to, instead
of just the tab-level property.
## Conclusion
I'm proposing these settings for broader discussion. I'm not really sure which I
like most at this point. 1 & 3 have the advantage of being most similar to the
prior art, but 2 is more easily extendable to "groups" (see [Future
Considerations](#Future-Considerations)).
**TODO!**: Make a decision.
_**Fortunately**_: All these proposals actually use the same set of actions. So
it doesn't _really_ matter which we pick right now. We can unblock [#9222] as
the implementation of the `"tab"` scope, and address other scopes in the future.
We should still decide long-term which of these we'd like, but the actions seem
universal.
## UI/UX Design
This is supposed to be a quick & dirty spec, so I'm LARGELY skipping this.
As far as indicators go, we'll throw something like:
![NetworkTower Segoe UI Icon](broadcast-segoe-icon.png)
in the tab when a pane is being broadcasted to. If all tabs are being
broadcasted to, then they'll all have that icon. If a tab is inactive, and any
pane in that tab is being broadcast to, then show the icon in the tab.
It probably makes the most sense to have pane titlebars ([#4998]) also display
that icon.
In the original PR, it was suggested to use some variant of the [accent color]
to on the borders of panes that are currently receiving broadcasted input. We're
already using the accent color on the borders of the active pane.
`SystemAccentColorLight*`/`SystemAccentColorDark*` would provide a way of using
a similar hue with different lightness/saturation. This would be a decent visual
indicator that they're _not_ the active pane, but they are going to receive
input. Something a bit like:
![A sample of using the border to indicate the broadcasted-to panes](broadcast-input-borders.gif)
This should obviously be able to be overridden in the user's theme, similar to
the pane border colors.
iTerm2 also supports displaying "stripes" in the background of all the panes
that are being broadcast too. That's certainly another way of indicating this
feature to the user. I'm not sure how we'd layer it with the background image
though. **I recommend we ignore this for now, and leave this as a follow-up**.
### Tab context menu items
For reference, refer to the following from iTerm2:
![image](https://user-images.githubusercontent.com/2578976/64075757-fa971980-ccee-11e9-9e44-47aaf3bca76c.png)
We don't have a menu bar like on MacOS, but we do have a tab context menu. We
could add these items as a nested entry under each tab. If we wanted to do this,
we should also make sure to dynamically change the icon of the MenuItem to
reflect the current broadcast state.
## Potential Issues
<table>
<tr>
<td><strong>Compatibility</strong></td>
<td>
[comment]: # Will the proposed change break existing code/behaviors? If so, how, and is the breaking change "worth it"?
</td>
</tr>
</table>
[comment]: # If there are any other potential issues, make sure to include them here.
## Implementation plan
* [ ] Resurrect [#9222], and use that to implement `"scope": "tab"`. This is
implemented the same, regardless of which proposal we chose.
* [ ] Add a tab context menu entry for toggling broadcast input, with a dynamic
icon based on the current state.
* [ ] Implement `"scope": "window"`. Again, this is implemented the same regardless
of which proposal we pursue.
* [ ] Decide between the two proposals here.
* [ ] Implement `"scope": "pane"`.
Doing the first element here is probably the most important one for most users,
and can be done regardless of the proposal chosen here. As such, we could even
suggest the default value of `scope` be `tab`. If we did that, then we wouldn't
need to do any args at all in the initial version.
## Future Considerations
Let's look to iTerm2, who's supported this feature for years, for some
inspiration of future things we should be worried about. If their users have
asked for these features, then it's inevitable that our users will too 😉
* [iterm2#6709] - Broadcast Input to multiple windows
- This is pretty straightforward. Would require coordination with the Monarch
though, and I'm worried about the perf hit of tossing every keystroke across
the process boundary.
- I suppose this would be `{ "action": "toggleBroadcastInput", "scope":
"global" }`
* [iterm2#6451], [iterm2#5563] - "Broadcast commands"
- iTerm2 has an action that lets the user manually clear the terminal-side
buffer. (This is tracked on the Windows Terminal as [#1882]). It might make
sense for there to be a mode where some _actions_ are also broadcast to
panes, not just key strokes. But which actions would those be? Moving the
selection anchors? Copy doesn't really make sense. Paste _does_ though.
Maybe the open find dialog / next&prev search match actions?
- This probably would require it's own spec.
* [iterm2#6007] - Different stripe color for different broadcast modes
- Have one color to indicate when broadcasting in `global` scope, another in
`tab` scope, a third in `pane` scope.
- This might mesh well with theming ([#3327]), for properties like
`pane.broadcastBorderColor.globalScope`,
`pane.broadcastBorderColor.paneScope`. Don't love those names, but you get
the idea.
* **[iterm2#5639]: Broadcast groups**, [iterm2#3372] - Broadcast Input to
multiple but not all tabs
- This is probably the most interesting request. I think this one identifies a
major shortcoming of the above proposals. With proposal 2, there's only ever
one top-level broadcast group. With proposals 1 & 3, there's per-tab
broadcast groups. In neither proposal can you have multiple concurrent
side-by-side broadcast groups.
- Groups should probably work across tabs. This would suggest that Proposal 2
is closer to how groups would work. Instead of there being one top-level
set, there would be multiple. **I'm not sure how proposals 1&3 would
seamlessly transition into also supporting groups**.
- The major trick here is: how do we differentiate these different groups to
the user? If we used the broadcast icon with a number, maybe in the corner
of the tab? Like [📡: 1]? Can a pane be in multiple broadcast sets at the
same time?
- The natural arg idea would be `{ "action": "toggleBroadcastInput", "scope":
"tab", "group": 1 }` to say "add all panes in the tab to broadcast group 1",
or "remove all panes in the tab from broadcast group 1". If panes are in
another group, they'd be moved to the specified group. If all panes are in
that group, then remove them all.
- The UI for this would certainly get complex fast.
- This also matches the Terminator-style broadcasting to groups.
* Re: stripes in the background of the tab. We could expose a pane's current
broadcast state to the pixel shader, and a user could use a custom pixel
shader to add stripes behind the text in the shader code. That's one possible
solution.
## Resources
[comment]: # Be sure to add links to references, resources, footnotes, etc.
### Footnotes
<a name="footnote-1"><a>[1]:
[#1882]: https://github.com/microsoft/terminal/issues/1882
[#2634]: https://github.com/microsoft/terminal/issues/2634
[#4998]: https://github.com/microsoft/terminal/issues/4998
[#3327]: https://github.com/microsoft/terminal/issues/3327
[#9222]: https://github.com/microsoft/terminal/pull/9222
[this comment]: https://github.com/microsoft/terminal/issues/2634#issuecomment-789116413
[iTerm2 implementation]: https://iterm2.com/documentation-one-page.html#documentation-menu-items.html
[@zljubisic]: https://github.com/microsoft/terminal/pull/9222#issuecomment-789143189
[accent color]: https://docs.microsoft.com/en-us/windows/uwp/design/style/color#accent-color-palette
[iterm2#6709]: https://gitlab.com/gnachman/iterm2/-/issues/6709
[iterm2#6451]: https://gitlab.com/gnachman/iterm2/-/issues/6451
[iterm2#6007]: https://gitlab.com/gnachman/iterm2/-/issues/6007
[iterm2#5639]: https://gitlab.com/gnachman/iterm2/-/issues/5639
[iterm2#5563]: https://gitlab.com/gnachman/iterm2/-/issues/5563
[iterm2#3372]: https://gitlab.com/gnachman/iterm2/-/issues/3372

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,380 @@
---
author: Mike Griese @zadjii-msft
created on: 2021-08-31
last updated: 2021-08-31
issue id: #642
---
# Buffer Exporting and Logging
## Abstract
A common user need is the ability to export the history of a terminal session to
a file, for later inspection or validation. This is something that could be
triggered manually. Many terminal emulators provide the ability to automatically
log the output of a session to a file, so the history is always captured. This
spec will address improvements to the Windows Terminal to enable these kinds of
exporting and logging scenarios.
## Background
### Inspiration
Below are screenshots from the settings pages of three different terminal
emulators with similar features - PuTTY, SecureCRT, and ConEmu:
![PuTTY settings](PuTTY-logging-settings.png)
_figure 1: PuTTY settings_
![SecureCRT settings](SecureCRT-logging-settings.png)
_figure 2: SecureCRT settings_
![ConEmu settings](ConEmu-logging-settings.png)
_figure 3: ConEmu settings_
These applications all offer some settings in common. Primarily, the important
feature is the ability to specify a path to a log file which contains some
special string formatting. This allows the user to log to different files based
on the time & date of the session, or based on the session name.
### User Stories
* **Story A**: The user is able to use a context menu entry on the tab to export
the contents of the buffer to a file, which they are prompted for.
- This is explicitly what was requested in [#642]
* **Story B**: The user can bind an action to export the contents of the buffer
to a file, which they are prompted for.
- Very similar to **A**, but via the command palette or a keybinding.
* **Story C**: The user can export to an explicit file via an action
- similar to **B**, but allowing for declaring the path to a file rather than
prompting at runtime.
* **Story D**: The user can choose to append to a file when exporting, rather
than overwriting.
* **Story E**: The user can specify a format string in the path to the file to
export to, which the Terminal will automatically replace with variables like
the time, date, and profile name.
* **Story F**: When opening a specific profile, the user can automatically log
to a file
* **Story G**: The user can execute an action to start or stop logging to a
given file.
## Solution Design
I'm proposing the following actions and profile settings
* New Action: `exportBuffer()`.
- Export the contents of the buffer to a file.
- `path` (string, defaults to `""`): When empty, prompt the user for a name of
a file to export to, using a file picker. This path accepts special
formatting strings that will be substituted with certain variables
(discussed [below](#path-formatting)).
- `append` (boolean, defaults to `false`): When `false`, the file's contents
will be overwritten. When `true`, the buffer contents will be appended to
the end of the file.
* New Profile Settings object: `logSettings`
- This is an object that describes a set of behavior for logging a profile.
- `path`: Same as the `path` in the `ExportBufferArgs` above
- `append`: Same as the `append` in the `ExportBufferArgs` above
- `captureAllOutput`: (boolean, defaults to `false`) When true, don't log only
printable characters, also log non-printable escape characters written to
the Terminal.
- `captureInput`: (boolean, defaults to `false`) Additionally log input to the
Terminal to the file. Input will be formatted as the traditional VT
sequences, rather than the full `win32-input` encoding.
- `newFileEveryDay`: (boolean, defaults to `false`) This requires the `day` to
be an element of the path format string. When logging with this setting,
opens a new file at midnight and starts writing that one.
<!-- TODO! - `flushFrequently`: (boolean, defaults to `true`) -->
* New Profile setting: `logAutomatically` (boolean, default `false`). When true,
terminals with this profile will begin logging automatically.
* New Action: `toggleLogging()`.
- Start or stop logging to the configured file. If the terminal is already
logging with different settings than in this action, then stop logging
regardless (don't just start logging to the new file)
- This action accepts all the same args the profile's `logSettings` object.
- If _any_ args are provided, use those args. If _none_ are provided, then use
the logging settings present in the profile (if there are any).
- If there's not path provided (either in the args to the action or in the
profile), prompt the user to pick a file to log to.
### Examples
```json
{
"actions": [
{ "keys": "f1", "command": "exportBuffer" },
{ "keys": "f2", "command": { "action": "exportBuffer", "path": "c:\\logs\\${year}-${month}-${date}\\{profile}.txt" } },
{ "keys": "f3", "command": "toggleLogging" },
{ "keys": "f4", "command": { "action": "toggleLogging", "path": "c:\\logs\\${profile}.log", "append": true } },
],
"profiles": [
{
"name": "foo",
"logging": {
"path": "c:\\foo.txt",
"append": true
},
"automaticallyLog": false
},
{
"name": "bar",
"logging": {
"path": "c:\\logs\\${date}\\bar.txt",
"append": false
},
"automaticallyLog": true
}
]
}
```
Revisiting our original stories:
* **Story A**: This is already implemented in [#11062]
* **Story B**: This is the action bound to <kbd>f1</kbd>.
* **Story C**: This is the action bound to <kbd>f2</kbd>.
* **Story D**: This is the `append` property in the actions, profile settings.
* **Story E**: An example of this is in the action bound to <kbd>f2</kbd>,
<kbd>f4</kbd>, and in the profile "bar"'s logging settings.
* **Story F**: The profile "bar" is configured to automatically log when opened.
* **Story G**: This is the action bound to <kbd>f4</kbd>.
In addition,
* When opening the profile "foo", it will not automatically log to a file.
- Pressing <kbd>f3</kbd> will begin logging to `c:\foo.txt`
- Pressing <kbd>f4</kbd> will begin logging to `c:\logs\foo.log`
### Path formatting
[TODO!]: # TODO!
For discussion: What syntax do we want?
* PuTTY uses `&Y`, `&M`, `&D`, `&T`, `&H`, `&P` for year, month, day, time, host
and port respectively.
* SecureCRT uses:
- `%H` hostname
- `%S` session name
- `%Y` four-digit year
- `%M` two-digit month
- `%D` two-digit day of the month
- `%h` two-digit hour
- `%m` two-digit minute
- `%s` two-digit seconds
- `%t` three-digit milliseconds
- `%%` percent (%)
- `%envvar%` environment variable (for instance `%USERNAME%`)
We have some precedent for formatting with `${braces}`, a la the iterable
command in the Command Palette (e.g `${profile.name}`). Additionally, [#9287]
implements support for environment variables in the Terminal with the
`${env:VARIABLE}` syntax.
What variables do we want exposed, and how do we want users to be able to format
them?
This doc was initially authored assuming we'd go with a `${braces}` syntax, like:
- `${profile}` profile name
- `${year}` four-digit year
- `${month}` two-digit month
- `${day}` two-digit day of the month
- `${hour}` two-digit hour
- `${minute}` two-digit minute
- `${second}` two-digit second
- `${ms}` three-digit milliseconds
- `${env:variable}` environment variable (for instance `${env:USERPROFILE}`)
(inspired by [#9287])
### Exporting vs Logging
As far as specific implementation details goes, exporting is the easier work to
do. [#11062] already wires up the `TerminalApp` to retrieve the buffer contents
from the `TermControl`, so writing them at request is easy.
Logging is harder. We don't want the `TermControl` telling the `TerminalApp`
layer about every piece of output logged. Especially in the post-[#5000] world
where that's a cross-process hop. Instead, we'll want the `ControlCore` /
`ControlInteractivity` to do _logging_ themselves.
### Logging Mechanics
#### When do we log?
[TODO!]: # TODO!
When do we decide to actually log? Take for example typing in a `pwsh` or
`bash` prompt. Imagine the user types
<kbd>w</kbd><kbd>h</kbd><kbd>a</kbd><kbd>t</kbd>, then hits
<kbd>Bksp</kbd><kbd>Bksp</kbd>, such that the prompt is just `wh`. What should
the log contain? `what^h ^h^h ^h`<sup>[[1]](#footnote-1)</sup>? `wh`?
My worry with logging the backspaces is that conpty is sometimes a bit noisier
than it needs to be with using `^H` as a cursor positioning sequence. Should we
only log lines when the cursor newlines or otherwise moves from the line it is
currently on?
I'll need to look at what PuTTY emits for the "Printable output" option.
#### What happens when we _start_ logging?
If the user has a terminal that did not start with logging enabled, but then
started logging with `toggleLogging`, what should we log? All future output? Or
should we log the current buffer contents as well?
I'm inclined to lean towards simply "all future output", and ignore any current
buffer content. If the user rally wants to log the current buffer contents _and_
start logging, they can use a `multipleActions` action ([#11045]) to
`exportBuffer` to a file, then `toggleLogging` to that same file with
`"append":true`.
## Potential Issues
<table>
<tr>
<td><strong>Compatibility</strong></td>
<td>
Since this functionality is entirely new, nothing here should negatively affect
existing functionality.
</td>
</tr>
<tr>
<td><strong>Performance, Power, and Efficiency</strong></td>
<td>
When logging, it's expected there will be a measurable performance hit. We can
try to mitigate this by only writing to the file on a background thread,
separate from the connection or rendering thread. Since auto-logging will only
take place in the content process, we're not worried about the file writing
occurring on the UI thread.
</td>
</tr>
</table>
Also frequently requested is the ability to log timestamps of when commands are
executed. I don't think that this is a valuable feature for the Terminal to
implement ourselves. Windows Terminal is fundamentally just a _terminal
emulator_, it doesn't really know what's going on behind the scenes with
whatever client application (`cmd`, `powershell`, `bash`, `vim`) that is
connected to it. WT doesn't know when the user is typing in commands to the
shell, or if the user is just typing in text in `emacs` or something. There's no
way for the terminal to know that. It's _typically_ the client application's
responsibility to save it's own command history. `bash` and `powershell` both do
a pretty good job of saving this to another file to restore across sessions,
while `cmd.exe` doesn't.
Windows is a messy world and this model gets a little tricky here. `cmd.exe`
isn't actually managing it's own command history _at all_. `conhost` is doing
that work on behalf of the client applications. Some long time ago someone
thought it would be a good idea to have the `readline` functionality baked
directly into the console host. Whether that was a good idea or not remains to
be seen - it's certainly made things like `python.exe`'s REPL easier to
implement, since they don't need to maintain their own history buffer, but it
makes it hard to de-tangle behavior like this from the console itself.
I'm not sure how it would be possible to add a keybinding to the Windows
Terminal that would be able to save the console's _command_ history. Especially
considering the Terminal might _not_ be connected to a console host session at
all. If the Windows Terminal were directly running a `wsl` instance (something
that's not possible today, but something we've considered adding in the future),
then there wouldn't be a `conhost` in the process tree at all, and now
requesting the command history from the console wouldn't work _mysteriously_.
Furthermore, shells can always be configured to emit timestamps in their prompts
themselves. Since the Terminal has no knowledge of when a command is actually
entered, but the _shell_ does, it makes the most sense to configure the user's
_shell_ to emit that information. The Terminal will then dutifully log that
output along with everything else.
## Implementation Plan
Below is a rough outline of how I'd go about implementing these features. Each
lop-level checkbox could be its own PR, following from [#11062].
### Buffer exporting
* [ ] Add an `exportBuffer()` action that opens the file picker
* [ ] Add a string `path` parameter to `exportBuffer()` that allows the user to
press a key and immediately export the buffer to a whole path
- default to `""`, which indicates "open the file picker"
* [ ] add a boolean `append` (default to `false`) parameter to `exportBuffer`.
When true, export to the file given by appending, not overwriting the file
* [ ] Enable string formatting in the `path` parameter.
- What format do we want? `yyyy-mm-dd`? `%Y-%m-%D`? `&Y-&m-&D`? `${year}-${month}-${day}`?
- What are all the variables we want?
- Year, month, day, hour, minute - those are easy
- `WT_SESSION`, for a uuid for each session maybe?
- Profile name perhaps? Commandline?
* [ ] more...
### Automatic logging
* [ ] `toggleLogging()` Action for start/stop logging, with `path`, `append`
properties (like `exportBuffer()`)
- `ToggleLoggingArgs` contains a single member `LoggingSettings`, which
contains `path` and `append` properties. This will make sense below.
* [ ] add `LoggingSettings` property for "log all output" (default would just be
"log printable output")
* [ ] add `LoggingSettings` property for "log input" (Though, we'd probably want
to log it as normal VT encoded, not as `win32-input` encoded)
* [ ] Per-profile setting for `logSettings`, which can contain an entire
`LoggingSettings` (like the `ToggleLoggingArgs`). When `toggleLogging` with no
args, try to use the profile's `loggingSettings` instead.
* [ ] Per-profile setting for `automaticallyLog`, which would log by default
when the profile is opened
* [ ] `LoggingSettings` property for "New file every day", which only works when
the `{day}` is in the path string. When auto-logging with this setting, opens
a new file at midnight and starts writing that one.
<!-- * [ ] `LoggingSettings` property for "Flush log frequently", defaults to
`true`(?). This causes us to flush all output to the file, instead of just...
on close? on newline? It's unclear exactly when PuTTY flushes with this off.
Need more coffee. -->
### Future Considerations
* When logging begins, the Terminal could display a toast for "Logging to
{filename}", and a similar one for "Stopped logging to {filename}".
* There's no good way of displaying a UI element to indicate that a pane is
currently logging to a file. I don't believe PuTTY displays any sort of
indicator. SecureCRT only displays a checkbox within the context menus of the
application itself.
![SecureCRT context menu](SecureCRT-context-menu.png)
Maybe when logging to a file, we could replace the "Export Text" context menu
entry with "Stop Logging"
* We could maybe add a setting to disable logging from the alt buffer. This
might help make this setting more valuable for users who are using full-screen
applications like `vim`. Since those applications redraw the entire viewport
contents frequently, the log might be unnecessarily noisy. Disabling logging
while in the alt buffer would show that the user opened vim, and then they did
some things after vim exited.
* Logging all output will be VERY helpful to us in the future for trying to
recreate bugs on our end that users can repro but we can't!
## Resources
PuTTY Logging documentation: https://tartarus.org/~simon/putty-snapshots/htmldoc/Chapter4.html#config-logfilename
ConEmu Logging documentation: https://conemu.github.io/en/AnsiLogFiles.html
### Footnotes
<a name="footnote-1"><a>[1]: Remember that `^H` is non-destructive, so the
sequence `what^h ^h^h ^h` is can be read as:
* print "what"
* move the cursor back one
* print a space (overwriting 't')
* move the cursor back one (now it's on the space where 't' was)
* move the cursor back one
* print a space (overwriting 'a')
* move the cursor back one (now it's on the space where 'a' was)
[#642]: https://github.com/microsoft/terminal/issues/642
[#5000]: https://github.com/microsoft/terminal/issues/5000
[#9287]: https://github.com/microsoft/terminal/pull/9287
[#11045]: https://github.com/microsoft/terminal/pull/11045
[#11062]: https://github.com/microsoft/terminal/pull/11062

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@@ -60,7 +60,7 @@ it could host any arbitrary content.
### Security
I don't forsee this implementation by itself raising security concerns. This
I don't foresee this implementation by itself raising security concerns. This
feature is mostly concerned with adding support for arbitrary controls, not
actually implementing some arbitrary controls.

217
doc/terminal-a11y-2023.md Normal file
View File

@@ -0,0 +1,217 @@
# Windows Terminal Accessibility
## Abstract
The release of Windows Terminal has served as a way to reinvigorate the command-line ecosystem on Windows by providing a modern experience that is consistently updated. This experience has caused faster innovation in the Windows command-line ecosystem that can be seen across various areas like expanded virtual terminal sequence support and enhanced accessibility. Command-line apps can now leverage these innovations to create a better experience for the end-user.
Since accessibility is a very broad area, this document is intended to present recent innovations in the accessibility space for the command-line ecosystem. Furthermore, it will propose additional improvements that can be made alongside key stakeholders that could benefit from such changes.
## Windows Command-line Ecosystem
### First-party terminals
For many years, Console Host (Conhost) was the only first-party terminal on Windows. In 2019, Windows Terminal was released to the world as an open source first-party terminal. Windows Terminal was distributed through the Microsoft Store and received regular updates throughout the year, much more frequently than Conhost. In October 2022, Windows Terminal was enabled as the default terminal on Windows.
A significant amount of code is shared between Conhost and Windows Terminal to create the terminal area. To enable an accessible experience for this area, a shared UI Automation provider was introduced in 2019[^1], enabling accessibility tools to navigate and read contents from the terminal area. In 2020, Windows Terminal was updated to dispatch UIA events signaling when the cursor position, text output, or selection changed; this left the work of identifying what changed in the output to the attached screen reader application[^2]. In 2022, Windows Terminal was updated to dispatch UIA notifications with a payload of what text was written to the screen[^3].
### Internal Partners
There are many first-party command-line applications on Windows. The following are a few examples of those that are regularly updated:
- [**GitHub CLI**](https://cli.github.com/): a tool that can be used to query and interact with GitHub repos (open source)
- [**Winget**](https://github.com/microsoft/winget-cli): a tool to install applications and other packages
- [**PSReadLine**](https://github.com/PowerShell/PSReadLine): a PowerShell module that enhances the input line experience
- [**Windows Subsystem for Linux (WSL)**](https://learn.microsoft.com/en-us/windows/wsl/): a tool to manage and run GNU/Linux environments without a traditional virtual machine
- [**PowerShell**](https://github.com/PowerShell/PowerShell): a cross-platform command-line shell (open source)
Additionally, CMD is a Windows-specific command-line shell. Though it is no longer being updated, it is still widely used.
### External Command-line Applications
There are many third-party command-line applications on Windows that are regularly updated.
The following examples take over the entire viewport:
- [**Vim**](https://www.vim.org/): a modal-based text editor with an extensive plugin system
- **Emacs**: a shortcut-oriented text editor with an extensive plugin system
- [**Emacspeak**](https://emacspeak.sourceforge.net/): an Emacs subsystem that leverages Emacs' context-specific information to produce speech output
- **Midnight Commander**: a file manager with an extensive text user interface
- **Far Manager**: a file manager for Windows with an exclusive text user interface
- **tmux**: a terminal multiplexer
The following examples don't take over the entire viewport:
- [**Oh My Posh**](https://ohmyposh.dev/): a tool to customize shell prompts
- **git**: a tool for version control
The following examples operate as command-line shells:
- [**Bash**](https://www.gnu.org/software/bash/) is the default shell for most Linux distributions
- [**Fish shell**](https://fishshell.com/) provides a rich shell experience with features like autosuggestion support and VGA colors
- [**Z shell**](https://zsh.sourceforge.io/) is an extended Bourne shell
## Accessibility tools and features
### Screen Readers
Windows is predominantly dominated by three major screen readers: Narrator, JAWS, and NVDA.
- **Narrator**: a free first-party screen reader that ships with Windows.
- **JAWS**: a third-party screen reader that requires an annual license to use.
- **NVDA**: a free third-party screen reader that is open source.
It's important to note that most users generally use NVDA or JAWS[^4], so it's very important to consider the experience under both of those screen readers.
### Miscellaneous User Tools
Windows has many built-in features that enable a more accessible experiences. The following is a list of Windows accessibility settings and tools that may have an impact on apps:
- **Text size setting**: dictates the default size of text throughout all of Windows
- **Always show scrollbars setting**: forces the scrollbars to always be expanded and shown. Generally, this setting is automatically respected when using the Windows UI library.
- **Transparency effects setting**: allows transparency effects within the Windows shell
- **Animation effects setting**: allows animation effects
- **Text cursor indicator**: displays a colorful UI over the text cursor to make it stand out more and easier to find. Note, this is powered by UI Automation selection changed events because an empty selection is the cursor's position.
- **Magnifier**: zooms the monitor to provide a closer look at the current mouse and cursor position. Note, this is powered by UI Automation selection changed events because an empty selection is the cursor's position.
- **Color filters**: manipulates displayed colors to address color-blindness
- **High contrast themes**: ensures all displayed content has a high contrast ratio. Generally, this setting is automatically respected when using the Windows UI library. However, non-WinUI surfaces (like the terminal area) need to do additional work to respect this.
- **Voice access**: enables users to interact with their PC using their voice. This is powered by UI Automation to identify what controls can be interacted with and how.
### Verification Tools
[Accessibility Insights](https://accessibilityinsights.io/) is a modern tool that can be used to test and understand accessibility issues on Windows. It comes with a built-in color contrast analyzer, a UI Automation event listener, and a UI Automation tree explorer. Additionally, it can be used to explore and interact with different control patterns using the same API screen readers and other accessibility tools rely on. Accessibility Insights is also capable of running automated tests in CI and locally to detect common and simple issues.
## Proposed Accessibility Improvements
### Respect Text Size OS setting
Windows Terminal has a profile setting for the font size (`"font"."size": <integer>`) which is then used when instantiating new terminal sessions. There should be a way to make the terminal font size respect the text size setting in Windows found in the Settings App > Accessibility > Text Size.
A possible solution is to scale the session's text size at runtime based on the OS setting. Furthermore, the Windows Terminal's settings UI should warn the user when they are attempting to modify the font size profile setting.
This is tracked by [Windows accessibility "text size" parameter ignored by terminal · Issue #13600](https://github.com/microsoft/terminal/issues/13600)
### Respect High Contrast Mode
When high contrast mode is enabled, any WinUI surfaces are automatically changed to respect the newly desired contrast mode. However, the terminal area does not. In 2021, the Delta E algorithm was added to Windows Terminal as a tool to improve color contrast in the terminal area if desired[^5]. In 2022, it was exposed via an `adjustIndistinguishableColors` profile setting with a variety of possible configurations[^6].
There are several possible solutions to this issue, which all should be exposed to the user. Such solutions include the following:
- Enabling `adjustIndistinguishableColors` automatically when high contrast mode is enabled
- This leverages the Delta E algorithm to force an adequate contrast ratio. The algorithm would need to be expanded to expose the threshold.
- Reducing the colors used to match the Windows Contrast Theme colors
- Edge uses this heuristic and it has the added benefit that Windows Terminal is respecting the user's high contrast theme set in the OS.
- Implementing the `DECSTGLT` escape sequence which enables switching between different color modes (one of which is monochrome)
Additionally, we should automatically make the terminal fully opaque and ignore the acrylic or traditional transparency setting.
This is tracked by [[Epic] Improved High Contrast support · Issue #12999](https://github.com/microsoft/terminal/issues/12999)
### Enhanced UI Automation movement mechanisms
UI Automation has an `ITextRangeProvider` interface that can be used to represent a span of text. These text ranges can then be manipulated to explore the text area. Such manipulations include moving either (or both) endpoint(s) to encompass a text unit or move by unit. UI Automation supports the following text units:
- Character
- Format
- Word
- Line
- Paragraph
- Page
- Document
Windows Terminal and Conhost implement this `ITextRangeProvider` interface. However, this implementation doesn't support all of the available text units. Though this is standard across other implementations, this provides an opportunity for growth.
#### UI Automation: Movement by page
Movement by page is a relatively abstract concept in terminals. Arguably, the viewport could be leveraged to be considered a "page", which would provide users with a quick way to navigate the buffer.
This is tracked by [UIA: support movement by page · Issue #13756](https://github.com/microsoft/terminal/issues/13756)
#### UI Automation: Movement by format
The terminal area supports various text decorations including but not limited to underlined, bold, and italic text. These text attributes were exposed via the `ITextRangeProvider` interface in [PR #10366](https://github.com/microsoft/terminal/pull/10336). UI automation has support for navigating through contiguous spans adhering to a text attribute, which could be useful to expose to UIA clients.
This is tracked by [UIA Formatted Text Navigation · Issue #3567](https://github.com/microsoft/terminal/issues/3567).
#### Movement by prompt
[PR #12948](https://github.com/microsoft/terminal/pull/12948) added support for scrollbar marks in Windows Terminal. As a part of this, an `experimental.autoMarkPrompts` profile setting registers scrollbar marks with each prompt when enabled. This is a fantastic way to navigate through the scroll history quickly and easily.
Though UI automation doesn't have a text unit for prompts, we could define a "paragraph" as the space between two prompts.
This issue is tracked by [megathread: Scrollbar Marks · Issue #11000](https://github.com/microsoft/terminal/issues/11000).
### Mark Mode support for degenerate range
[PR #13053](https://github.com/microsoft/terminal/pull/13053) added support for mark mode in Windows Terminal. Mark mode allows users to create and modify selections by exclusively using the keyboard. However, screen reader users have reported it as a strange experience because it always has a cell of text selected; this results in the screen reader reading "x selected, y unselected" as opposed to the expected "x" when moving the cursor around.
Unfortunately, the changes required to fix this are very extensive because selections are stored as two inclusive terminal coordinates, which makes it impossible to represent an empty selection.
This is tracked by [A11y: windows terminal emits selection/deselection events in mark mode when navigating with arrow keys · Issue #13447](https://github.com/microsoft/terminal/issues/13447).
### Search improvements
The search dialog can be used to perform a runtime query of the text buffer. However, in its current form, it does not count the total number of matches like other developer tools (i.e. Visual Studio Code). This work item tracks two things:
1. Support for counting the total number of matches
2. Displaying the number of results in the search box and announcing it via a UIA notification to the screen reader
Additional search features may fall into this including but not limited to:
- Highlight the search results in the scroll bar
- Successful searches should read the line where text was found (similar to VS Code experience)
It seems that UI Automation has no guidance for a consistent, good search experience on Windows. Noting that VS Code and Microsoft Word have two different search experiences, it may be valuable to create documentation and guidance for other developer tools on Windows to follow, thus helping to create a more consistent search experience.
This is tracked by [Search should display the number of results it finds · Issue #6319](https://github.com/microsoft/terminal/issues/6319) and [[JAWS] Search results read "results found" instead of something useful · Issue #14153](https://github.com/microsoft/terminal/issues/14153).
### Shell suggestions
Shells provide different forms of autocompletion. However, that autocompletion isn't necessarily an accessible experience. For example, CMD's standard autocompletion experience rewrites the "word". The screen reader rereads the new "word" properly, however, the user must cycle between the options until the desired one is shown. As another example,
PowerShell supports menu completion, where possible completions are displayed as a table of text and relevant help text is displayed. However, screen readers struggle with this because the entire menu is redrawn every time, making it harder to understand what exactly is "selected" (as the concept of selection in this instance is a shell-side concept represented by visual manipulation).
A possible solution is to introduce a new VT sequence to have the shell provide a payload. This payload can contain things like the suggested completion as well as associated help text. Such data can then be leveraged by Windows Terminal to create UI elements. Doing so leverages WinUI's accessible design. By designing a new VT sequence, other terminal emulators and CLI apps can opt-in to this functionality too.
This is tracked by [Enhance shell autocompletion with a cool new user interface and shell completion protocol · Issue #3121](https://github.com/microsoft/terminal/issues/3121).
### Scripts Panel
Common command-line experiences revolve around inputting a command into the shell and executing it. Expert command-line users can remember complex commands off the top of their heads. Windows Terminal could help users by storing complex commands. Furthermore, these commands should be able to be easily shared between users.
From an accessibility standpoint, this can be very useful for users with mobility issues, as it is particularly difficult to write complex commands quickly.
This is tracked by [Feature Request - Scripts Panel · Issue #1595](https://github.com/microsoft/terminal/issues/1595).
### Broadcast Input
Windows Terminal supports running multiple sessions at once across various panes, tabs, and windows. The broadcast input feature allows users to send input to multiple sessions at once.
From an accessibility standpoint, this can be very useful for users with mobility issues, as it is time consuming to write commands to multiple sessions quickly, particularly if those commands are complex.
This is tracked by [Support broadcast input? · Issue #2634](https://github.com/microsoft/terminal/issues/2634).
### UI Automation Notification Related Improvements
In 2022, Windows Terminal added UI Automation notifications that contained a payload of text output[^3]. This was done for various reasons. For one, Narrator was not reading new output by the terminal, and would require changes on their end to accomplish this. Another reason is that NVDA is unable to handle too many text changed events, which Conhost and Windows Terminal are both culprits of since they dispatch an event when text is written to the terminal output.
UIA notifications have provided many compatibility benefits since screen readers automatically read notifications they receive. Additionally, this has provided the possibility for major performance enhancements as screen readers may no longer be required to diff the text buffer and figure out what has changed. NVDA has prototyped listening to notifications and ignoring text changed events entirely[^7]. However, it reveals underlying challenges with this new model such as how to handle passwords. The proposals listed in this section are intended to have Windows Terminal achieve improved performance and accessibility quality.
#### VT Screen Reader Control
Some command-line applications are simply too difficult to create a consistent accessible experience. Applications that draw decorative content, for example, may have that content read by a screen reader.
In 2019, Daniel Imms wrote a spec proposing a VT sequence that can partially control the attached screen reader[^8]. This VT sequence consists of three main formats:
1. Stop announcing incoming data to the screen reader. The screen reader will resume announcing incoming data if any key is pressed.
2. Resume announcing incoming data to the screen reader.
3. Announce the associated string payload immediately.
Additionally, all three formats include a string payload that will be announced immediately by the screen reader (as is done with the third format).
JAWS and Narrator both immediately read UIA notifications as that is how Windows Terminal presents newly output text. As described earlier, NVDA currently has notifications disabled, but is prototyping moving towards a world where they can drop text diffing entirely in favor of this.
With Windows Terminal now dispatching UIA notifications, it would be relatively trivial for Windows Terminal to extract the string payload from the relevant VT sequences and present it in a new UIA notification. Additionally, an internal flag would be enabled to suppress UIA notifications for text output until the flag is flipped via a key press or the relevant VT sequence is received.
This is tracked by [Unable to use applications that hook the arrow keys using Windows Console Host. · Issue #13666](https://github.com/microsoft/terminal/issues/13666)
#### Screen Reader Backpressure Control Proposal
NVDA has reported issues where Windows Terminal sends too may text changed events at once, causing NVDA to hang. Several considerations have been made in this area to address this issue:
- **Batch notifications**: unhelpful because Terminal doesn't know how much text is intended to be output
- **Diff text before sending a notification**: same problem as above
- **Provide an API to throttle notifications**: causes a security risk for denial of service attacks
Surprisingly, UI Automation doesn't have a built-in way to handle backpressure. However, the appropriate fix here seems to be either on the UI Automation side to fix this for all UIA clients, or on the NVDA side to improve event handling.
This is tracked by [UIA: reduce number of extraneous text change events in conhost · Issue #10822](https://github.com/microsoft/terminal/issues/10822)
## Proposed Priorities
The following table assigns priorities to the aforementioned work items. These priorities are solely based on accessibility impact to improve the Windows command-line ecosystem.
| Priority | Work Item | Reasoning |
|----------|-----------|-----------|
| 1 | VT Screen Reader Control | Several partners (both CLI apps and UIA clients) would benefit from this feature and have expressed interest in adopting it. |
| 1 | Shell suggestions | Several partners (both CLI apps and UIA clients) would benefit from this feature and have expressed interest in adopting it. |
| 1 | Search improvements | Search is very difficult to use in its current implementation. |
| 1 | Mark Mode support for degenerate range | Experience is barely usable and very unfriendly for non-sighted users. This is the only non-mouse method to select text. |
| 2 | Respect High Contrast Mode | Clear workarounds exist, but the golden path scenario fails where a user enables high contrast mode and content does not respect it. |
| 2 | Text Size OS Setting | Clear workarounds exist, but the golden path scenario fails where a user enables an OS setting and content does not respect it. |
| 2 | Broadcast Input | Users with mobility issues would greatly benefit from this. |
| 3 | Scripts Panel | Send-input commands currently exist. This would just expose it better. |
| 3 | UIA: Move to prompt | Additional UIA text unit support is good. |
| 3 | UIA: Move by page | Additional UIA text unit support is good. |
| 3 | UIA: Move by format | Additional UIA text unit support is good. |
Generally, the reasoning behind these priorities can be broken down as follows:
- Priority 1 bucket:
- These work items provide access to features within the command-line ecosystem that are practically unusable for users with accessibility needs.
- Priority 2 bucket:
- Active workarounds exist for these issues, but they could be cumbersome.
- Priority 3 bucket:
- Purely innovative ideas that provide an exceptional experience for users.
## References
[^1]: [Accessibility: Set-up UIA Tree by carlos-zamora · Pull Request #1691](https://github.com/microsoft/terminal/pull/1691)
[^2]: [Fire UIA Events for Output and Cursor Movement by carlos-zamora · Pull Request #4826](https://github.com/microsoft/terminal/pull/4826)
[^3]: [Use UIA notifications for text output by carlos-zamora · Pull Request #12358](https://github.com/microsoft/terminal/pull/12358)
[^4]: [WebAIM: Screen Reader User Survey #8 Results](https://webaim.org/projects/screenreadersurvey8/#:~:text=NVDA%20is%20now%20the%20most%20commonly%20used%20screen,respondents%20use%20more%20than%20one%20desktop%2Flaptop%20screen%20reader.)
[^5]: [Implement the Delta E algorithm to improve color perception by PankajBhojwani · Pull Request #11095](https://github.com/microsoft/terminal/pull/11095)
[^6]: [Change AdjustIndistinguishableColors to an enum setting instead of a boolean setting by PankajBhojwani · Pull Request #13512](https://github.com/microsoft/terminal/pull/13512)
[^7]: [Prototype for Windows Terminal: Use notifications instead of monitoring for new text by leonardder · Pull Request #14047 · nvaccess/nvda (github.com)](https://github.com/nvaccess/nvda/pull/14047)
[^8]: [Control Screen Reader from Applications (#18) · Issues · terminal-wg / specifications · GitLab](https://gitlab.freedesktop.org/terminal-wg/specifications/-/issues/18)

View File

@@ -12,19 +12,12 @@ using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Windows::UI::Xaml::Navigation;
namespace xaml = ::winrt::Windows::UI::Xaml;
namespace winrt::SampleApp::implementation
{
App::App()
{
// This is the same trick that Initialize() is about to use to figure out whether we're coming
// from a UWP context or from a Win32 context
// See https://github.com/windows-toolkit/Microsoft.Toolkit.Win32/blob/52611c57d89554f357f281d0c79036426a7d9257/Microsoft.Toolkit.Win32.UI.XamlApplication/XamlApplication.cpp#L42
const auto dispatcherQueue = ::winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
if (dispatcherQueue)
{
_isUwp = true;
}
Initialize();
// Disable XAML's automatic backplating of text when in High Contrast
@@ -33,6 +26,44 @@ namespace winrt::SampleApp::implementation
HighContrastAdjustment(::winrt::Windows::UI::Xaml::ApplicationHighContrastAdjustment::None);
}
void App::Initialize()
{
const auto dispatcherQueue = winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
if (!dispatcherQueue)
{
_windowsXamlManager = xaml::Hosting::WindowsXamlManager::InitializeForCurrentThread();
}
else
{
_isUwp = true;
}
}
void App::Close()
{
if (_bIsClosed)
{
return;
}
_bIsClosed = true;
if (_windowsXamlManager)
{
_windowsXamlManager.Close();
}
_windowsXamlManager = nullptr;
Exit();
{
MSG msg = {};
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
{
::DispatchMessageW(&msg);
}
}
}
SampleAppLogic App::Logic()
{
static SampleAppLogic logic;

View File

@@ -12,12 +12,22 @@ namespace winrt::SampleApp::implementation
{
public:
App();
void Initialize();
void Close();
void OnLaunched(Windows::ApplicationModel::Activation::LaunchActivatedEventArgs const&);
bool IsDisposed() const
{
return _bIsClosed;
}
SampleApp::SampleAppLogic Logic();
private:
bool _isUwp = false;
winrt::Windows::UI::Xaml::Hosting::WindowsXamlManager _windowsXamlManager = nullptr;
winrt::Windows::Foundation::Collections::IVector<winrt::Windows::UI::Xaml::Markup::IXamlMetadataProvider> _providers = winrt::single_threaded_vector<Windows::UI::Xaml::Markup::IXamlMetadataProvider>();
bool _bIsClosed = false;
};
}

View File

@@ -7,10 +7,12 @@ namespace SampleApp
{
// ADD ARBITRARY APP LOGIC TO SampleAppLogic.idl, NOT HERE.
// This is for XAML platform setup only.
[default_interface] runtimeclass App : Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication
[default_interface] runtimeclass App : Windows.UI.Xaml.Application, Windows.Foundation.IClosable
{
App();
SampleAppLogic Logic { get; };
Boolean IsDisposed { get; };
}
}

View File

@@ -2,20 +2,19 @@
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
the MIT License. See LICENSE in the project root for license information.
-->
<Toolkit:XamlApplication x:Class="SampleApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:TA="using:SampleApp"
xmlns:Toolkit="using:Microsoft.Toolkit.Win32.UI.XamlHost"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:SampleApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Application x:Class="SampleApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:TA="using:SampleApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:SampleApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<!--
If you want to prove this works, then add `RequestedTheme="Light"` to
the properties on the XamlApplication
-->
<Toolkit:XamlApplication.Resources>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
@@ -67,5 +66,5 @@
</ResourceDictionary>
</Toolkit:XamlApplication.Resources>
</Toolkit:XamlApplication>
</Application.Resources>
</Application>

View File

@@ -20,7 +20,6 @@
<PropertyGroup Label="NuGet Dependencies">
<TerminalCppWinrt>true</TerminalCppWinrt>
<TerminalXamlApplicationToolkit>true</TerminalXamlApplicationToolkit>
</PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />
@@ -128,6 +127,16 @@
<Private>false</Private>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
</Reference>
<Reference Include="$(WindowsSDK_MetadataPathVersioned)\Windows.UI.Xaml.Hosting.HostingContract\*\*.winmd">
<WinMDFile>true</WinMDFile>
<CopyLocal>false</CopyLocal>
<ReferenceGrouping>$(TargetPlatformMoniker)</ReferenceGrouping>
<ReferenceGroupingDisplayName>$(TargetPlatformDisplayName)</ReferenceGroupingDisplayName>
<ResolvedFrom>CppWinRTImplicitlyExpandTargetPlatform</ResolvedFrom>
<IsSystemReference>True</IsSystemReference>
</Reference>
</ItemGroup>
<!-- ====================== Compiler & Linker Flags ===================== -->
<ItemDefinitionGroup>

View File

@@ -13,7 +13,6 @@
</PropertyGroup>
<PropertyGroup Label="NuGet Dependencies">
<TerminalCppWinrt>true</TerminalCppWinrt>
<TerminalXamlApplicationToolkit>true</TerminalXamlApplicationToolkit>
</PropertyGroup>
<Import Project="..\..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />
@@ -45,7 +44,7 @@
</ItemGroup>
<!-- ========================= Project References ======================== -->
<ItemGroup>
<!-- Reference SampleAppLib here, so we can use it's App.winmd as
<!-- Reference SampleAppLib here, so we can use its App.winmd as
our App.winmd. This didn't work correctly in VS2017, you'd need to
manually reference the lib -->
<ProjectReference Include="$(OpenConsoleDir)scratch\ScratchIslandApp\SampleApp\SampleAppLib.vcxproj">

View File

@@ -46,7 +46,6 @@
#include "winrt/Windows.UI.Xaml.Markup.h"
#include "winrt/Windows.UI.ViewManagement.h"
#include <winrt/Microsoft.Toolkit.Win32.UI.XamlHost.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>

View File

@@ -90,7 +90,7 @@
Particularly tricky is Microsoft.Terminal.Core.winmd. That winmd doesn't
have its own DLL (it doesn't have any activatable classes, only structs and
interfaces). However, it too is necessary for Terminal.Control to be able to
marshall the Core types across the boundary.
marshal the Core types across the boundary.
-->
<Reference Include="Microsoft.Terminal.Core">
<HintPath>$(OpenConsoleCommonOutDir)TerminalCore\Microsoft.Terminal.Core.winmd</HintPath>

View File

@@ -25,19 +25,19 @@ constexpr til::inclusive_rect ScreenToBufferLine(const til::inclusive_rect& line
{
// Use shift right to quickly divide the Left and Right by 2 for double width lines.
const auto scale = lineRendition == LineRendition::SingleWidth ? 0 : 1;
return { line.Left >> scale, line.Top, line.Right >> scale, line.Bottom };
return { line.left >> scale, line.top, line.right >> scale, line.bottom };
}
constexpr til::point ScreenToBufferLine(const til::point& line, const LineRendition lineRendition)
{
// Use shift right to quickly divide the Left and Right by 2 for double width lines.
const auto scale = lineRendition == LineRendition::SingleWidth ? 0 : 1;
return { line.X >> scale, line.Y };
return { line.x >> scale, line.y };
}
constexpr til::inclusive_rect BufferToScreenLine(const til::inclusive_rect& line, const LineRendition lineRendition)
{
// Use shift left to quickly multiply the Left and Right by 2 for double width lines.
const auto scale = lineRendition == LineRendition::SingleWidth ? 0 : 1;
return { line.Left << scale, line.Top, (line.Right << scale) + scale, line.Bottom };
return { line.left << scale, line.top, (line.right << scale) + scale, line.bottom };
}

View File

@@ -82,7 +82,7 @@ OutputCellIterator::OutputCellIterator(const CHAR_INFO& charInfo, const size_t f
// - This is an iterator over a range of text only. No color data will be modified as the text is inserted.
// Arguments:
// - utf16Text - UTF-16 text range
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text) :
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text) noexcept :
_mode(Mode::LooseTextOnly),
_currentView(s_GenerateView(utf16Text)),
_run(utf16Text),
@@ -98,7 +98,7 @@ OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text) :
// Arguments:
// - utf16Text - UTF-16 text range
// - attribute - Color to apply over the entire range
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute& attribute, const size_t fillLimit) :
OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute& attribute, const size_t fillLimit) noexcept :
_mode(Mode::Loose),
_currentView(s_GenerateView(utf16Text, attribute)),
_run(utf16Text),
@@ -357,7 +357,7 @@ bool OutputCellIterator::_TryMoveTrailing() noexcept
// - view - View representing characters corresponding to a single glyph
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view)
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view) noexcept
{
return s_GenerateView(view, InvalidTextAttribute, TextAttributeBehavior::Current);
}
@@ -372,8 +372,7 @@ OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view)
// - attr - Color attributes to apply to the text
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view,
const TextAttribute attr)
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view, const TextAttribute attr) noexcept
{
return s_GenerateView(view, attr, TextAttributeBehavior::Stored);
}
@@ -389,9 +388,7 @@ OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view,
// - behavior - Behavior of the given text attribute (used when writing)
// Return Value:
// - Object representing the view into this cell
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view,
const TextAttribute attr,
const TextAttributeBehavior behavior)
OutputCellView OutputCellIterator::s_GenerateView(const std::wstring_view view, const TextAttribute attr, const TextAttributeBehavior behavior) noexcept
{
const auto glyph = til::utf16_next(view);
const auto dbcsAttr = IsGlyphFullWidth(glyph) ? DbcsAttribute::Leading : DbcsAttribute::Single;

View File

@@ -37,8 +37,8 @@ public:
OutputCellIterator(const TextAttribute& attr, const size_t fillLimit = 0) noexcept;
OutputCellIterator(const wchar_t& wch, const TextAttribute& attr, const size_t fillLimit = 0) noexcept;
OutputCellIterator(const CHAR_INFO& charInfo, const size_t fillLimit = 0) noexcept;
OutputCellIterator(const std::wstring_view utf16Text);
OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute& attribute, const size_t fillLimit = 0);
OutputCellIterator(const std::wstring_view utf16Text) noexcept;
OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute& attribute, const size_t fillLimit = 0) noexcept;
OutputCellIterator(const gsl::span<const WORD> legacyAttributes) noexcept;
OutputCellIterator(const gsl::span<const CHAR_INFO> charInfos) noexcept;
OutputCellIterator(const gsl::span<const OutputCell> cells);
@@ -100,15 +100,9 @@ private:
bool _TryMoveTrailing() noexcept;
static OutputCellView s_GenerateView(const std::wstring_view view);
static OutputCellView s_GenerateView(const std::wstring_view view,
const TextAttribute attr);
static OutputCellView s_GenerateView(const std::wstring_view view,
const TextAttribute attr,
const TextAttributeBehavior behavior);
static OutputCellView s_GenerateView(const std::wstring_view view) noexcept;
static OutputCellView s_GenerateView(const std::wstring_view view, const TextAttribute attr) noexcept;
static OutputCellView s_GenerateView(const std::wstring_view view, const TextAttribute attr, const TextAttributeBehavior behavior) noexcept;
static OutputCellView s_GenerateView(const wchar_t& wch) noexcept;
static OutputCellView s_GenerateViewLegacyAttr(const WORD& legacyAttr) noexcept;
static OutputCellView s_GenerateView(const TextAttribute& attr) noexcept;

View File

@@ -101,6 +101,13 @@ void Cursor::SetIsOn(const bool fIsOn) noexcept
void Cursor::SetBlinkingAllowed(const bool fBlinkingAllowed) noexcept
{
_fBlinkingAllowed = fBlinkingAllowed;
// GH#2642 - From what we've gathered from other terminals, when blinking is
// disabled, the cursor should remain On always, and have the visibility
// controlled by the IsVisible property. So when you do a printf "\e[?12l"
// to disable blinking, the cursor stays stuck On. At this point, only the
// cursor visibility property controls whether the user can see it or not.
// (Yes, the cursor can be On and NOT Visible)
_fIsOn = true;
_RedrawCursorAlways();
}
@@ -199,7 +206,7 @@ void Cursor::SetPosition(const til::point cPosition) noexcept
void Cursor::SetXPosition(const til::CoordType NewX) noexcept
{
_RedrawCursor();
_cPosition.X = NewX;
_cPosition.x = NewX;
_RedrawCursor();
ResetDelayEOLWrap();
}
@@ -207,7 +214,7 @@ void Cursor::SetXPosition(const til::CoordType NewX) noexcept
void Cursor::SetYPosition(const til::CoordType NewY) noexcept
{
_RedrawCursor();
_cPosition.Y = NewY;
_cPosition.y = NewY;
_RedrawCursor();
ResetDelayEOLWrap();
}
@@ -215,7 +222,7 @@ void Cursor::SetYPosition(const til::CoordType NewY) noexcept
void Cursor::IncrementXPosition(const til::CoordType DeltaX) noexcept
{
_RedrawCursor();
_cPosition.X += DeltaX;
_cPosition.x += DeltaX;
_RedrawCursor();
ResetDelayEOLWrap();
}
@@ -223,7 +230,7 @@ void Cursor::IncrementXPosition(const til::CoordType DeltaX) noexcept
void Cursor::IncrementYPosition(const til::CoordType DeltaY) noexcept
{
_RedrawCursor();
_cPosition.Y += DeltaY;
_cPosition.y += DeltaY;
_RedrawCursor();
ResetDelayEOLWrap();
}
@@ -231,7 +238,7 @@ void Cursor::IncrementYPosition(const til::CoordType DeltaY) noexcept
void Cursor::DecrementXPosition(const til::CoordType DeltaX) noexcept
{
_RedrawCursor();
_cPosition.X -= DeltaX;
_cPosition.x -= DeltaX;
_RedrawCursor();
ResetDelayEOLWrap();
}
@@ -239,7 +246,7 @@ void Cursor::DecrementXPosition(const til::CoordType DeltaX) noexcept
void Cursor::DecrementYPosition(const til::CoordType DeltaY) noexcept
{
_RedrawCursor();
_cPosition.Y -= DeltaY;
_cPosition.y -= DeltaY;
_RedrawCursor();
ResetDelayEOLWrap();
}

View File

@@ -18,19 +18,19 @@ using namespace Microsoft::Console::Types;
// - Once you've found something, you can perform actions like .Select() or .Color()
// Arguments:
// - textBuffer - The screen text buffer to search through (the "haystack")
// - uiaData - The IUiaData type reference, it is for providing selection methods
// - renderData - The IRenderData type reference, it is for providing selection methods
// - str - The search term you want to find (the "needle")
// - direction - The direction to search (upward or downward)
// - sensitivity - Whether or not you care about case
Search::Search(IUiaData& uiaData,
Search::Search(Microsoft::Console::Render::IRenderData& renderData,
const std::wstring_view str,
const Direction direction,
const Sensitivity sensitivity) :
_direction(direction),
_sensitivity(sensitivity),
_needle(s_CreateNeedleFromString(str)),
_uiaData(uiaData),
_coordAnchor(s_GetInitialAnchor(uiaData, direction))
_renderData(renderData),
_coordAnchor(s_GetInitialAnchor(renderData, direction))
{
_coordNext = _coordAnchor;
}
@@ -41,12 +41,12 @@ Search::Search(IUiaData& uiaData,
// - Once you've found something, you can perform actions like .Select() or .Color()
// Arguments:
// - textBuffer - The screen text buffer to search through (the "haystack")
// - uiaData - The IUiaData type reference, it is for providing selection methods
// - renderData - The IRenderData type reference, it is for providing selection methods
// - str - The search term you want to find (the "needle")
// - direction - The direction to search (upward or downward)
// - sensitivity - Whether or not you care about case
// - anchor - starting search location in screenInfo
Search::Search(IUiaData& uiaData,
Search::Search(Microsoft::Console::Render::IRenderData& renderData,
const std::wstring_view str,
const Direction direction,
const Sensitivity sensitivity,
@@ -55,7 +55,7 @@ Search::Search(IUiaData& uiaData,
_sensitivity(sensitivity),
_needle(s_CreateNeedleFromString(str)),
_coordAnchor(anchor),
_uiaData(uiaData)
_renderData(renderData)
{
_coordNext = _coordAnchor;
}
@@ -99,10 +99,10 @@ void Search::Select() const
{
// Convert buffer selection offsets into the equivalent screen coordinates
// required by SelectNewRegion, taking line renditions into account.
const auto& textBuffer = _uiaData.GetTextBuffer();
const auto& textBuffer = _renderData.GetTextBuffer();
const auto selStart = textBuffer.BufferToScreenPosition(_coordSelStart);
const auto selEnd = textBuffer.BufferToScreenPosition(_coordSelEnd);
_uiaData.SelectNewRegion(selStart, selEnd);
_renderData.SelectNewRegion(selStart, selEnd);
}
// Routine Description:
@@ -114,7 +114,7 @@ void Search::Color(const TextAttribute attr) const
// Only select if we've found something.
if (_coordSelEnd >= _coordSelStart)
{
_uiaData.ColorSelection(_coordSelStart, _coordSelEnd, attr);
_renderData.ColorSelection(_coordSelStart, _coordSelEnd, attr);
}
}
@@ -135,19 +135,19 @@ std::pair<til::point, til::point> Search::GetFoundLocation() const noexcept
// - If the screen buffer given already has a selection in it, it will be used to determine the anchor.
// - Otherwise, we will choose one of the ends of the screen buffer depending on direction.
// Arguments:
// - uiaData - The reference to the IUiaData interface type object
// - renderData - The reference to the IRenderData interface type object
// - direction - The intended direction of the search
// Return Value:
// - Coordinate to start the search from.
til::point Search::s_GetInitialAnchor(const IUiaData& uiaData, const Direction direction)
til::point Search::s_GetInitialAnchor(const Microsoft::Console::Render::IRenderData& renderData, const Direction direction)
{
const auto& textBuffer = uiaData.GetTextBuffer();
const auto textBufferEndPosition = uiaData.GetTextBufferEndPosition();
if (uiaData.IsSelectionActive())
const auto& textBuffer = renderData.GetTextBuffer();
const auto textBufferEndPosition = renderData.GetTextBufferEndPosition();
if (renderData.IsSelectionActive())
{
// Convert the screen position of the selection anchor into an equivalent
// buffer position to start searching, taking line rendition into account.
auto anchor = textBuffer.ScreenToBufferPosition(uiaData.GetSelectionAnchor());
auto anchor = textBuffer.ScreenToBufferPosition(renderData.GetSelectionAnchor());
if (direction == Direction::Forward)
{
@@ -158,8 +158,8 @@ til::point Search::s_GetInitialAnchor(const IUiaData& uiaData, const Direction d
textBuffer.GetSize().DecrementInBoundsCircular(anchor);
// If the selection starts at (0, 0), we need to make sure
// it does not exceed the text buffer end position
anchor.X = std::min(textBufferEndPosition.X, anchor.X);
anchor.Y = std::min(textBufferEndPosition.Y, anchor.Y);
anchor.x = std::min(textBufferEndPosition.x, anchor.x);
anchor.y = std::min(textBufferEndPosition.y, anchor.y);
}
return anchor;
}
@@ -196,7 +196,7 @@ bool Search::_FindNeedleInHaystackAt(const til::point pos, til::point& start, ti
for (const auto& needleChars : _needle)
{
// Haystack is the buffer. Needle is the string we were given.
const auto hayIter = _uiaData.GetTextBuffer().GetTextDataAt(bufferPos);
const auto hayIter = _renderData.GetTextBuffer().GetTextDataAt(bufferPos);
const auto hayChars = *hayIter;
// If we didn't match at any point of the needle, return false.
@@ -269,7 +269,7 @@ wchar_t Search::_ApplySensitivity(const wchar_t wch) const noexcept
// - coord - Updated by function to increment one position (will wrap X and Y direction)
void Search::_IncrementCoord(til::point& coord) const noexcept
{
_uiaData.GetTextBuffer().GetSize().IncrementInBoundsCircular(coord);
_renderData.GetTextBuffer().GetSize().IncrementInBoundsCircular(coord);
}
// Routine Description:
@@ -278,7 +278,7 @@ void Search::_IncrementCoord(til::point& coord) const noexcept
// - coord - Updated by function to decrement one position (will wrap X and Y direction)
void Search::_DecrementCoord(til::point& coord) const noexcept
{
_uiaData.GetTextBuffer().GetSize().DecrementInBoundsCircular(coord);
_renderData.GetTextBuffer().GetSize().DecrementInBoundsCircular(coord);
}
// Routine Description:
@@ -305,10 +305,10 @@ void Search::_UpdateNextPosition()
// We put the next position to:
// Forward: (0, 0)
// Backward: the position of the end of the text buffer
const auto bufferEndPosition = _uiaData.GetTextBufferEndPosition();
const auto bufferEndPosition = _renderData.GetTextBufferEndPosition();
if (_coordNext.Y > bufferEndPosition.Y ||
(_coordNext.Y == bufferEndPosition.Y && _coordNext.X > bufferEndPosition.X))
if (_coordNext.y > bufferEndPosition.y ||
(_coordNext.y == bufferEndPosition.y && _coordNext.x > bufferEndPosition.x))
{
if (_direction == Direction::Forward)
{

View File

@@ -17,10 +17,9 @@ Revision History:
#pragma once
#include <WinConTypes.h>
#include "TextAttribute.hpp"
#include "textBuffer.hpp"
#include "../types/IUiaData.h"
#include "../renderer/inc/IRenderData.hpp"
// This used to be in find.h.
#define SEARCH_STRING_LENGTH (80)
@@ -40,12 +39,12 @@ public:
CaseSensitive
};
Search(Microsoft::Console::Types::IUiaData& uiaData,
Search(Microsoft::Console::Render::IRenderData& renderData,
const std::wstring_view str,
const Direction dir,
const Sensitivity sensitivity);
Search(Microsoft::Console::Types::IUiaData& uiaData,
Search(Microsoft::Console::Render::IRenderData& renderData,
const std::wstring_view str,
const Direction dir,
const Sensitivity sensitivity,
@@ -66,7 +65,7 @@ private:
void _IncrementCoord(til::point& coord) const noexcept;
void _DecrementCoord(til::point& coord) const noexcept;
static til::point s_GetInitialAnchor(const Microsoft::Console::Types::IUiaData& uiaData, const Direction dir);
static til::point s_GetInitialAnchor(const Microsoft::Console::Render::IRenderData& renderData, const Direction dir);
static std::vector<std::wstring> s_CreateNeedleFromString(const std::wstring_view wstr);
@@ -79,7 +78,7 @@ private:
const std::vector<std::wstring> _needle;
const Direction _direction;
const Sensitivity _sensitivity;
Microsoft::Console::Types::IUiaData& _uiaData;
Microsoft::Console::Render::IRenderData& _renderData;
#ifdef UNIT_TESTING
friend class SearchTests;

View File

@@ -98,7 +98,7 @@ using PointTree = interval_tree::IntervalTree<til::point, size_t>;
// Return Value:
// - constructed object
// Note: may throw exception
TextBuffer::TextBuffer(const til::size screenBufferSize,
TextBuffer::TextBuffer(til::size screenBufferSize,
const TextAttribute defaultAttributes,
const UINT cursorSize,
const bool isActiveBuffer,
@@ -108,10 +108,14 @@ TextBuffer::TextBuffer(const til::size screenBufferSize,
_cursor{ cursorSize, *this },
_isActiveBuffer{ isActiveBuffer }
{
// Guard against resizing the text buffer to 0 columns/rows, which would break being able to insert text.
screenBufferSize.width = std::max(screenBufferSize.width, 1);
screenBufferSize.height = std::max(screenBufferSize.height, 1);
BufferAllocator allocator{ screenBufferSize };
_storage.reserve(allocator.height());
for (til::CoordType i = 0; i < screenBufferSize.Y; ++i, ++allocator)
for (til::CoordType i = 0; i < screenBufferSize.height; ++i, ++allocator)
{
_storage.emplace_back(allocator.chars(), allocator.indices(), allocator.width(), _currentAttributes);
}
@@ -215,10 +219,10 @@ TextBufferTextIterator TextBuffer::GetTextLineDataAt(const til::point at) const
TextBufferCellIterator TextBuffer::GetCellLineDataAt(const til::point at) const
{
til::inclusive_rect limit;
limit.Top = at.Y;
limit.Bottom = at.Y;
limit.Left = 0;
limit.Right = GetSize().RightInclusive();
limit.top = at.y;
limit.bottom = at.y;
limit.left = 0;
limit.right = GetSize().RightInclusive();
return TextBufferCellIterator(*this, at, Viewport::FromInclusive(limit));
}
@@ -262,11 +266,11 @@ bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribut
{
// To figure out if the sequence is valid, we have to look at the character that comes before the current one
const auto coordPrevPosition = _GetPreviousFromCursor();
auto& prevRow = GetRowByOffset(coordPrevPosition.Y);
auto& prevRow = GetRowByOffset(coordPrevPosition.y);
DbcsAttribute prevDbcsAttr = DbcsAttribute::Single;
try
{
prevDbcsAttr = prevRow.DbcsAttrAt(coordPrevPosition.X);
prevDbcsAttr = prevRow.DbcsAttrAt(coordPrevPosition.x);
}
catch (...)
{
@@ -319,7 +323,7 @@ bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribut
// Erase previous character into an N type.
try
{
prevRow.ClearCell(coordPrevPosition.X);
prevRow.ClearCell(coordPrevPosition.x);
}
catch (...)
{
@@ -356,13 +360,13 @@ bool TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute
if (dbcsAttribute == DbcsAttribute::Leading)
{
const auto cursorPosition = GetCursor().GetPosition();
const auto lineWidth = GetLineWidth(cursorPosition.Y);
const auto lineWidth = GetLineWidth(cursorPosition.y);
// If we're about to lead on the last column in the row, we need to add a padding space
if (cursorPosition.X == lineWidth - 1)
if (cursorPosition.x == lineWidth - 1)
{
// set that we're wrapping for double byte reasons
auto& row = GetRowByOffset(cursorPosition.Y);
auto& row = GetRowByOffset(cursorPosition.y);
row.SetDoubleBytePadded(true);
// then move the cursor forward and onto the next row
@@ -417,8 +421,8 @@ OutputCellIterator TextBuffer::Write(const OutputCellIterator givenIt,
it = WriteLine(it, lineTarget, wrap);
// Move to the next line down.
lineTarget.X = 0;
++lineTarget.Y;
lineTarget.x = 0;
++lineTarget.y;
}
return it;
@@ -445,8 +449,8 @@ OutputCellIterator TextBuffer::WriteLine(const OutputCellIterator givenIt,
}
// Get the row and write the cells
auto& row = GetRowByOffset(target.Y);
const auto newIt = row.WriteCells(givenIt, target.X, wrap, limitRight);
auto& row = GetRowByOffset(target.y);
const auto newIt = row.WriteCells(givenIt, target.x, wrap, limitRight);
// Take the cell distance written and notify that it needs to be repainted.
const auto written = newIt.GetCellDistance(givenIt);
@@ -475,8 +479,8 @@ bool TextBuffer::InsertCharacter(const std::wstring_view chars,
if (fSuccess)
{
// Get the current cursor position
const auto iRow = GetCursor().GetPosition().Y; // row stored as logical position, not array position
const auto iCol = GetCursor().GetPosition().X; // column logical and array positions are equal.
const auto iRow = GetCursor().GetPosition().y; // row stored as logical position, not array position
const auto iCol = GetCursor().GetPosition().x; // column logical and array positions are equal.
// Get the row associated with the given logical position
auto& Row = GetRowByOffset(iRow);
@@ -550,7 +554,7 @@ void TextBuffer::_SetWrapOnCurrentRow() noexcept
void TextBuffer::_AdjustWrapOnCurrentRow(const bool fSet) noexcept
{
// The vertical position of the cursor represents the current row we're manipulating.
const auto uiCurrentRowOffset = GetCursor().GetPosition().Y;
const auto uiCurrentRowOffset = GetCursor().GetPosition().y;
// Set the wrap status as appropriate
GetRowByOffset(uiCurrentRowOffset).SetWrapForced(fSet);
@@ -569,14 +573,14 @@ bool TextBuffer::IncrementCursor()
// Cursor position is stored as logical array indices (starts at 0) for the window
// Buffer Size is specified as the "length" of the array. It would say 80 for valid values of 0-79.
// So subtract 1 from buffer size in each direction to find the index of the final column in the buffer
const auto iFinalColumnIndex = GetLineWidth(GetCursor().GetPosition().Y) - 1;
const auto iFinalColumnIndex = GetLineWidth(GetCursor().GetPosition().y) - 1;
// Move the cursor one position to the right
GetCursor().IncrementXPosition(1);
auto fSuccess = true;
// If we've passed the final valid column...
if (GetCursor().GetPosition().X > iFinalColumnIndex)
if (GetCursor().GetPosition().x > iFinalColumnIndex)
{
// Then mark that we've been forced to wrap
_SetWrapOnCurrentRow();
@@ -603,7 +607,7 @@ bool TextBuffer::NewlineCursor()
GetCursor().IncrementYPosition(1);
// If we've passed the final valid row...
if (GetCursor().GetPosition().Y > iFinalRowIndex)
if (GetCursor().GetPosition().y > iFinalRowIndex)
{
// Stay on the final logical/offset row of the buffer.
GetCursor().SetYPosition(iFinalRowIndex);
@@ -677,28 +681,28 @@ til::point TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::C
til::point coordEndOfText;
// Search the given viewport by starting at the bottom.
coordEndOfText.Y = viewport.BottomInclusive();
coordEndOfText.y = viewport.BottomInclusive();
const auto& currRow = GetRowByOffset(coordEndOfText.Y);
const auto& currRow = GetRowByOffset(coordEndOfText.y);
// The X position of the end of the valid text is the Right draw boundary (which is one beyond the final valid character)
coordEndOfText.X = currRow.MeasureRight() - 1;
coordEndOfText.x = currRow.MeasureRight() - 1;
// If the X coordinate turns out to be -1, the row was empty, we need to search backwards for the real end of text.
const auto viewportTop = viewport.Top();
auto fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > viewportTop); // this row is empty, and we're not at the top
auto fDoBackUp = (coordEndOfText.x < 0 && coordEndOfText.y > viewportTop); // this row is empty, and we're not at the top
while (fDoBackUp)
{
coordEndOfText.Y--;
const auto& backupRow = GetRowByOffset(coordEndOfText.Y);
coordEndOfText.y--;
const auto& backupRow = GetRowByOffset(coordEndOfText.y);
// We need to back up to the previous row if this line is empty, AND there are more rows
coordEndOfText.X = backupRow.MeasureRight() - 1;
fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > viewportTop);
coordEndOfText.x = backupRow.MeasureRight() - 1;
fDoBackUp = (coordEndOfText.x < 0 && coordEndOfText.y > viewportTop);
}
// don't allow negative results
coordEndOfText.Y = std::max(coordEndOfText.Y, 0);
coordEndOfText.X = std::max(coordEndOfText.X, 0);
coordEndOfText.y = std::max(coordEndOfText.y, 0);
coordEndOfText.x = std::max(coordEndOfText.x, 0);
return coordEndOfText;
}
@@ -715,20 +719,20 @@ til::point TextBuffer::_GetPreviousFromCursor() const noexcept
auto coordPosition = GetCursor().GetPosition();
// If we're not at the left edge, simply move the cursor to the left by one
if (coordPosition.X > 0)
if (coordPosition.x > 0)
{
coordPosition.X--;
coordPosition.x--;
}
else
{
// Otherwise, only if we're not on the top row (e.g. we don't move anywhere in the top left corner. there is no previous)
if (coordPosition.Y > 0)
if (coordPosition.y > 0)
{
// move the cursor up one line
coordPosition.Y--;
coordPosition.y--;
// and to the right edge
coordPosition.X = GetLineWidth(coordPosition.Y) - 1;
coordPosition.x = GetLineWidth(coordPosition.y) - 1;
}
}
@@ -875,7 +879,7 @@ void TextBuffer::SetCurrentAttributes(const TextAttribute& currentAttributes) no
void TextBuffer::SetCurrentLineRendition(const LineRendition lineRendition)
{
const auto cursorPosition = GetCursor().GetPosition();
const auto rowIndex = cursorPosition.Y;
const auto rowIndex = cursorPosition.y;
auto& row = GetRowByOffset(rowIndex);
if (row.GetLineRendition() != lineRendition)
{
@@ -926,22 +930,22 @@ til::CoordType TextBuffer::GetLineWidth(const til::CoordType row) const noexcept
til::point TextBuffer::ClampPositionWithinLine(const til::point position) const noexcept
{
const auto rightmostColumn = GetLineWidth(position.Y) - 1;
return { std::min(position.X, rightmostColumn), position.Y };
const auto rightmostColumn = GetLineWidth(position.y) - 1;
return { std::min(position.x, rightmostColumn), position.y };
}
til::point TextBuffer::ScreenToBufferPosition(const til::point position) const noexcept
{
// Use shift right to quickly divide the X pos by 2 for double width lines.
const auto scale = IsDoubleWidthLine(position.Y) ? 1 : 0;
return { position.X >> scale, position.Y };
const auto scale = IsDoubleWidthLine(position.y) ? 1 : 0;
return { position.x >> scale, position.y };
}
til::point TextBuffer::BufferToScreenPosition(const til::point position) const noexcept
{
// Use shift left to quickly multiply the X pos by 2 for double width lines.
const auto scale = IsDoubleWidthLine(position.Y) ? 1 : 0;
return { position.X << scale, position.Y };
const auto scale = IsDoubleWidthLine(position.y) ? 1 : 0;
return { position.x << scale, position.y };
}
// Routine Description:
@@ -963,8 +967,12 @@ void TextBuffer::Reset()
// - newSize - new size of screen.
// Return Value:
// - Success if successful. Invalid parameter if screen buffer size is unexpected. No memory if allocation failed.
[[nodiscard]] NTSTATUS TextBuffer::ResizeTraditional(const til::size newSize) noexcept
[[nodiscard]] NTSTATUS TextBuffer::ResizeTraditional(til::size newSize) noexcept
{
// Guard against resizing the text buffer to 0 columns/rows, which would break being able to insert text.
newSize.width = std::max(newSize.width, 1);
newSize.height = std::max(newSize.height, 1);
try
{
BufferAllocator allocator{ newSize };
@@ -973,11 +981,11 @@ void TextBuffer::Reset()
const auto attributes = GetCurrentAttributes();
til::CoordType TopRow = 0; // new top row of the screen buffer
if (newSize.Y <= GetCursor().GetPosition().Y)
if (newSize.height <= GetCursor().GetPosition().y)
{
TopRow = GetCursor().GetPosition().Y - newSize.Y + 1;
TopRow = GetCursor().GetPosition().y - newSize.height + 1;
}
const auto TopRowIndex = (GetFirstRowIndex() + TopRow) % currentSize.Y;
const auto TopRowIndex = (GetFirstRowIndex() + TopRow) % currentSize.height;
// rotate rows until the top row is at index 0
std::rotate(_storage.begin(), _storage.begin() + TopRowIndex, _storage.end());
@@ -1088,7 +1096,7 @@ ROW& TextBuffer::_GetFirstRow() noexcept
// - the delimiter class for the given char
DelimiterClass TextBuffer::_GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const noexcept
{
return GetRowByOffset(pos.Y).DelimiterClassAt(pos.X, wordDelimiters);
return GetRowByOffset(pos.y).DelimiterClassAt(pos.x, wordDelimiters);
}
// Method Description:
@@ -1205,7 +1213,7 @@ til::point TextBuffer::_GetWordStartForSelection(const til::point target, const
const auto initialDelimiter = _GetDelimiterClassAt(result, wordDelimiters);
// expand left until we hit the left boundary or a different delimiter class
while (result.X > bufferSize.Left() && (_GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter))
while (result.x > bufferSize.Left() && (_GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter))
{
bufferSize.DecrementInBounds(result);
}
@@ -1323,7 +1331,7 @@ til::point TextBuffer::_GetWordEndForSelection(const til::point target, const st
const auto bufferSize = GetSize();
// can't expand right
if (target.X == bufferSize.RightInclusive())
if (target.x == bufferSize.RightInclusive())
{
return target;
}
@@ -1332,7 +1340,7 @@ til::point TextBuffer::_GetWordEndForSelection(const til::point target, const st
const auto initialDelimiter = _GetDelimiterClassAt(result, wordDelimiters);
// expand right until we hit the right boundary or a different delimiter class
while (result.X < bufferSize.RightInclusive() && (_GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter))
while (result.x < bufferSize.RightInclusive() && (_GetDelimiterClassAt(result, wordDelimiters) == initialDelimiter))
{
bufferSize.IncrementInBounds(result);
}
@@ -1606,25 +1614,25 @@ const std::vector<til::inclusive_rect> TextBuffer::GetTextRects(til::point start
std::make_tuple(start, end) :
std::make_tuple(end, start);
const auto textRectSize = 1 + lowerCoord.Y - higherCoord.Y;
const auto textRectSize = 1 + lowerCoord.y - higherCoord.y;
textRects.reserve(textRectSize);
for (auto row = higherCoord.Y; row <= lowerCoord.Y; row++)
for (auto row = higherCoord.y; row <= lowerCoord.y; row++)
{
til::inclusive_rect textRow;
textRow.Top = row;
textRow.Bottom = row;
textRow.top = row;
textRow.bottom = row;
if (blockSelection || higherCoord.Y == lowerCoord.Y)
if (blockSelection || higherCoord.y == lowerCoord.y)
{
// set the left and right margin to the left-/right-most respectively
textRow.Left = std::min(higherCoord.X, lowerCoord.X);
textRow.Right = std::max(higherCoord.X, lowerCoord.X);
textRow.left = std::min(higherCoord.x, lowerCoord.x);
textRow.right = std::max(higherCoord.x, lowerCoord.x);
}
else
{
textRow.Left = (row == higherCoord.Y) ? higherCoord.X : bufferSize.Left();
textRow.Right = (row == lowerCoord.Y) ? lowerCoord.X : bufferSize.RightInclusive();
textRow.left = (row == higherCoord.y) ? higherCoord.x : bufferSize.Left();
textRow.right = (row == lowerCoord.y) ? lowerCoord.x : bufferSize.RightInclusive();
}
// If we were passed screen coordinates, convert the given range into
@@ -1665,8 +1673,8 @@ std::vector<til::point_span> TextBuffer::GetTextSpans(til::point start, til::poi
for (auto rect : rects)
{
const til::point first = { rect.Left, rect.Top };
const til::point second = { rect.Right, rect.Bottom };
const til::point first = { rect.left, rect.top };
const til::point second = { rect.right, rect.bottom };
textSpans.emplace_back(first, second);
}
}
@@ -1687,16 +1695,16 @@ std::vector<til::point_span> TextBuffer::GetTextSpans(til::point start, til::poi
// equivalent buffer offsets, taking line rendition into account.
if (!bufferCoordinates)
{
higherCoord = ScreenToBufferLine(higherCoord, GetLineRendition(higherCoord.Y));
lowerCoord = ScreenToBufferLine(lowerCoord, GetLineRendition(lowerCoord.Y));
higherCoord = ScreenToBufferLine(higherCoord, GetLineRendition(higherCoord.y));
lowerCoord = ScreenToBufferLine(lowerCoord, GetLineRendition(lowerCoord.y));
}
til::inclusive_rect asRect = { higherCoord.X, higherCoord.Y, lowerCoord.X, lowerCoord.Y };
til::inclusive_rect asRect = { higherCoord.x, higherCoord.y, lowerCoord.x, lowerCoord.y };
_ExpandTextRow(asRect);
higherCoord.X = asRect.Left;
higherCoord.Y = asRect.Top;
lowerCoord.X = asRect.Right;
lowerCoord.Y = asRect.Bottom;
higherCoord.x = asRect.left;
higherCoord.y = asRect.top;
lowerCoord.x = asRect.right;
lowerCoord.y = asRect.bottom;
textSpans.emplace_back(higherCoord, lowerCoord);
}
@@ -1716,10 +1724,10 @@ void TextBuffer::_ExpandTextRow(til::inclusive_rect& textRow) const
const auto bufferSize = GetSize();
// expand left side of rect
til::point targetPoint{ textRow.Left, textRow.Top };
til::point targetPoint{ textRow.left, textRow.top };
if (GetCellDataAt(targetPoint)->DbcsAttr() == DbcsAttribute::Trailing)
{
if (targetPoint.X == bufferSize.Left())
if (targetPoint.x == bufferSize.Left())
{
bufferSize.IncrementInBounds(targetPoint);
}
@@ -1727,14 +1735,14 @@ void TextBuffer::_ExpandTextRow(til::inclusive_rect& textRow) const
{
bufferSize.DecrementInBounds(targetPoint);
}
textRow.Left = targetPoint.X;
textRow.left = targetPoint.x;
}
// expand right side of rect
targetPoint = { textRow.Right, textRow.Bottom };
targetPoint = { textRow.right, textRow.bottom };
if (GetCellDataAt(targetPoint)->DbcsAttr() == DbcsAttribute::Leading)
{
if (targetPoint.X == bufferSize.RightInclusive())
if (targetPoint.x == bufferSize.RightInclusive())
{
bufferSize.DecrementInBounds(targetPoint);
}
@@ -1742,7 +1750,7 @@ void TextBuffer::_ExpandTextRow(til::inclusive_rect& textRow) const
{
bufferSize.IncrementInBounds(targetPoint);
}
textRow.Right = targetPoint.X;
textRow.right = targetPoint.x;
}
}
@@ -1777,7 +1785,7 @@ const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF,
// for each row in the selection
for (size_t i = 0; i < rows; i++)
{
const auto iRow = selectionRects.at(i).Top;
const auto iRow = selectionRects.at(i).top;
const auto highlight = Viewport::FromInclusive(selectionRects.at(i));
@@ -2329,7 +2337,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
const auto cOldCursorPos = oldCursor.GetPosition();
const auto cOldLastChar = oldBuffer.GetLastNonSpaceCharacter(lastCharacterViewport);
const auto cOldRowsTotal = cOldLastChar.Y + 1;
const auto cOldRowsTotal = cOldLastChar.y + 1;
til::point cNewCursorPos;
auto fFoundCursorPos = false;
@@ -2348,9 +2356,9 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// If we're starting a new row, try and preserve the line rendition
// from the row in the original buffer.
const auto newBufferPos = newCursor.GetPosition();
if (newBufferPos.X == 0)
if (newBufferPos.x == 0)
{
auto& newRow = newBuffer.GetRowByOffset(newBufferPos.Y);
auto& newRow = newBuffer.GetRowByOffset(newBufferPos.y);
newRow.SetLineRendition(row.GetLineRendition());
}
@@ -2385,7 +2393,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
const auto copyRight = iRight;
for (; iOldCol < copyRight; iOldCol++)
{
if (iOldCol == cOldCursorPos.X && iOldRow == cOldCursorPos.Y)
if (iOldCol == cOldCursorPos.x && iOldRow == cOldCursorPos.y)
{
cNewCursorPos = newCursor.GetPosition();
fFoundCursorPos = true;
@@ -2427,9 +2435,9 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// line in the new buffer, then we didn't wrap. That's fine. just
// copy attributes from the old row till the end of the new row, and
// move on.
const auto newRowY = newCursor.GetPosition().Y;
const auto newRowY = newCursor.GetPosition().y;
auto& newRow = newBuffer.GetRowByOffset(newRowY);
auto newAttrColumn = newCursor.GetPosition().X;
auto newAttrColumn = newCursor.GetPosition().x;
const auto newWidth = newBuffer.GetLineWidth(newRowY);
// Stop when we get to the end of the buffer width, or the new position
// for inserting an attr would be past the right of the new buffer.
@@ -2458,7 +2466,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
{
if (iOldRow >= positionInfo.value().get().mutableViewportTop)
{
positionInfo.value().get().mutableViewportTop = newCursor.GetPosition().Y;
positionInfo.value().get().mutableViewportTop = newCursor.GetPosition().y;
foundOldMutable = true;
}
}
@@ -2467,7 +2475,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
{
if (iOldRow >= positionInfo.value().get().visibleViewportTop)
{
positionInfo.value().get().visibleViewportTop = newCursor.GetPosition().Y;
positionInfo.value().get().visibleViewportTop = newCursor.GetPosition().y;
foundOldVisible = true;
}
}
@@ -2482,7 +2490,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// only because we ran out of space.
if (iRight < cOldColsTotal && !row.WasWrapForced())
{
if (!fFoundCursorPos && (iRight == cOldCursorPos.X && iOldRow == cOldCursorPos.Y))
if (!fFoundCursorPos && (iRight == cOldCursorPos.x && iOldRow == cOldCursorPos.y))
{
cNewCursorPos = newCursor.GetPosition();
fFoundCursorPos = true;
@@ -2522,9 +2530,9 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// | |
// ^ and the cursor is now here.
const auto coordNewCursor = newCursor.GetPosition();
if (coordNewCursor.X == 0 && coordNewCursor.Y > 0)
if (coordNewCursor.x == 0 && coordNewCursor.y > 0)
{
if (newBuffer.GetRowByOffset(coordNewCursor.Y - 1).WasWrapForced())
if (newBuffer.GetRowByOffset(coordNewCursor.y - 1).WasWrapForced())
{
hr = newBuffer.NewlineCursor() ? hr : E_OUTOFMEMORY;
}
@@ -2538,7 +2546,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// printable character. This is to fix the `color 2f` scenario, where you
// change the buffer colors then resize and everything below the last
// printable char gets reset. See GH #12567
auto newRowY = newCursor.GetPosition().Y + 1;
auto newRowY = newCursor.GetPosition().y + 1;
const auto newHeight = newBuffer.GetSize().Height();
const auto oldHeight = oldBuffer.GetSize().Height();
for (;
@@ -2578,13 +2586,13 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
// Advance the cursor to the same offset as before
// get the number of newlines and spaces between the old end of text and the old cursor,
// then advance that many newlines and chars
auto iNewlines = cOldCursorPos.Y - cOldLastChar.Y;
const auto iIncrements = cOldCursorPos.X - cOldLastChar.X;
auto iNewlines = cOldCursorPos.y - cOldLastChar.y;
const auto iIncrements = cOldCursorPos.x - cOldLastChar.x;
const auto cNewLastChar = newBuffer.GetLastNonSpaceCharacter();
// If the last row of the new buffer wrapped, there's going to be one less newline needed,
// because the cursor is already on the next line
if (newBuffer.GetRowByOffset(cNewLastChar.Y).WasWrapForced())
if (newBuffer.GetRowByOffset(cNewLastChar.y).WasWrapForced())
{
iNewlines = std::max(iNewlines - 1, 0);
}
@@ -2592,7 +2600,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
{
// if this buffer didn't wrap, but the old one DID, then the d(columns) of the
// old buffer will be one more than in this buffer, so new need one LESS.
if (oldBuffer.GetRowByOffset(cOldLastChar.Y).WasWrapForced())
if (oldBuffer.GetRowByOffset(cOldLastChar.y).WasWrapForced())
{
iNewlines = std::max(iNewlines - 1, 0);
}

View File

@@ -44,7 +44,7 @@ TextBufferCellIterator::TextBufferCellIterator(const TextBuffer& buffer, til::po
// Throw if the coordinate is not limited to the inside of the given buffer.
THROW_HR_IF(E_INVALIDARG, !limits.IsInBounds(pos));
_attrIter += pos.X;
_attrIter += pos.x;
_GenerateView();
}
@@ -115,15 +115,15 @@ TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& move
// _SetPos() necessitates calling _GenerateView() and thus the construction
// of a new OutputCellView(). This has a high performance impact (ICache spill?).
// The code below inlines _bounds.IncrementInBounds as well as SetPos.
// In the hot path (_pos.Y doesn't change) we modify the _view directly.
// In the hot path (_pos.y doesn't change) we modify the _view directly.
// Hoist these integers which will be used frequently later.
const auto boundsRightInclusive = _bounds.RightInclusive();
const auto boundsLeft = _bounds.Left();
const auto boundsBottomInclusive = _bounds.BottomInclusive();
const auto boundsTop = _bounds.Top();
const auto oldX = _pos.X;
const auto oldY = _pos.Y;
const auto oldX = _pos.x;
const auto oldY = _pos.y;
// Under MSVC writing the individual members of a til::point generates worse assembly
// compared to having them be local variables. This causes a performance impact.
@@ -166,15 +166,15 @@ TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& move
_view.UpdateText(_pRow->GlyphAt(newX));
_view.UpdateDbcsAttribute(_pRow->DbcsAttrAt(newX));
_pos.X = newX;
_pos.x = newX;
}
else
{
// cold path (_GenerateView is slow)
_pRow = s_GetRow(_buffer, { newX, newY });
_attrIter = _pRow->AttrBegin() + newX;
_pos.X = newX;
_pos.Y = newY;
_pos.x = newX;
_pos.y = newY;
_GenerateView();
}
@@ -289,16 +289,16 @@ ptrdiff_t TextBufferCellIterator::operator-(const TextBufferCellIterator& it)
// - newPos - The new coordinate position.
void TextBufferCellIterator::_SetPos(const til::point newPos) noexcept
{
if (newPos.Y != _pos.Y)
if (newPos.y != _pos.y)
{
_pRow = s_GetRow(_buffer, newPos);
_attrIter = _pRow->AttrBegin();
_pos.X = 0;
_pos.x = 0;
}
if (newPos.X != _pos.X)
if (newPos.x != _pos.x)
{
const auto diff = gsl::narrow_cast<ptrdiff_t>(newPos.X) - gsl::narrow_cast<ptrdiff_t>(_pos.X);
const auto diff = gsl::narrow_cast<ptrdiff_t>(newPos.x) - gsl::narrow_cast<ptrdiff_t>(_pos.x);
_attrIter += diff;
}
@@ -317,15 +317,15 @@ void TextBufferCellIterator::_SetPos(const til::point newPos) noexcept
// - Pointer to the underlying CharRow structure
const ROW* TextBufferCellIterator::s_GetRow(const TextBuffer& buffer, const til::point pos) noexcept
{
return &buffer.GetRowByOffset(pos.Y);
return &buffer.GetRowByOffset(pos.y);
}
// Routine Description:
// - Updates the internal view. Call after updating row, attribute, or positions.
void TextBufferCellIterator::_GenerateView() noexcept
{
_view = OutputCellView(_pRow->GlyphAt(_pos.X),
_pRow->DbcsAttrAt(_pos.X),
_view = OutputCellView(_pRow->GlyphAt(_pos.x),
_pRow->DbcsAttrAt(_pos.x),
*_attrIter,
TextAttributeBehavior::Stored);
}

View File

@@ -830,7 +830,7 @@ class ReflowTests
for (size_t bufferIndex{ 1 }; bufferIndex < testCase.buffers.size(); ++bufferIndex)
{
const auto& testBuffer{ til::at(testCase.buffers, bufferIndex) };
Log::Comment(NoThrowString().Format(L"[%zu.%zu] Resizing to %dx%d", i, bufferIndex, testBuffer.size.X, testBuffer.size.Y));
Log::Comment(NoThrowString().Format(L"[%zu.%zu] Resizing to %dx%d", i, bufferIndex, testBuffer.size.width, testBuffer.size.height));
auto newBuffer{ _textBufferByReflowingTextBuffer(*textBuffer, testBuffer.size) };

View File

@@ -9,7 +9,10 @@
#include <wil/stl.h>
#include <wil/resource.h>
#include <wil/win32_helpers.h>
#include <gsl/gsl_util>
#include <gsl/pointers>
#include <shellapi.h>
#include <appmodel.h>
// BODGY
//
@@ -25,18 +28,68 @@
// process can successfully elevate.
#pragma warning(suppress : 26461) // we can't change the signature of wWinMain
int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR pCmdLine, int)
int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
{
// All of the args passed to us (something like `new-tab -p {guid}`) are in
// pCmdLine
// This will invoke an elevated terminal in two possible ways. See GH#14501
// In both scenarios, it passes the entire cmdline as-is to the new process.
//
// #1 discover and invoke the app using the GetCurrentApplicationUserModelId
// api using shell:AppsFolder\package!appid
// cmd: shell:AppsFolder\WindowsTerminalDev_8wekyb3d8bbwe!App
// params: new-tab -p {guid}
//
// #2 find and execute WindowsTerminal.exe
// cmd: {same path as this binary}\WindowsTerminal.exe
// params: new-tab -p {guid}
// Get the path to WindowsTerminal.exe, which should live next to us.
std::filesystem::path module{ wil::GetModuleFileNameW<std::wstring>(nullptr) };
// Swap elevate-shim.exe for WindowsTerminal.exe
module.replace_filename(L"WindowsTerminal.exe");
// see if we're a store app we can invoke with shell:AppsFolder
std::wstring appUserModelId;
const auto result = wil::AdaptFixedSizeToAllocatedResult<std::wstring, APPLICATION_USER_MODEL_ID_MAX_LENGTH>(
appUserModelId, [&](PWSTR value, size_t valueLength, gsl::not_null<size_t*> valueLengthNeededWithNull) noexcept -> HRESULT {
UINT32 length = gsl::narrow_cast<UINT32>(valueLength);
const LONG rc = GetCurrentApplicationUserModelId(&length, value);
switch (rc)
{
case ERROR_SUCCESS:
*valueLengthNeededWithNull = length;
return S_OK;
case ERROR_INSUFFICIENT_BUFFER:
*valueLengthNeededWithNull = length;
return S_FALSE; // trigger allocation loop
case APPMODEL_ERROR_NO_APPLICATION:
return E_FAIL; // we are not running as a store app
default:
return E_UNEXPECTED;
}
});
LOG_IF_FAILED(result);
std::wstring cmd = {};
if (result == S_OK && appUserModelId.length() > 0)
{
// scenario #1
cmd = L"shell:AppsFolder\\" + appUserModelId;
}
else
{
// scenario #2
// Get the path to WindowsTerminal.exe, which should live next to us.
std::filesystem::path module{
wil::GetModuleFileNameW<std::wstring>(nullptr)
};
// Swap elevate-shim.exe for WindowsTerminal.exe
module.replace_filename(L"WindowsTerminal.exe");
cmd = module;
}
// Go!
// The cmdline argument passed to WinMain is stripping the first argument.
// Using GetCommandLine() instead for lParameters
// disable warnings from SHELLEXECUTEINFOW struct. We can't fix that.
#pragma warning(push)
#pragma warning(disable : 26476) // Macro uses naked union over variant.
@@ -46,8 +99,10 @@ int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR pCmdLine, int)
seInfo.cbSize = sizeof(seInfo);
seInfo.fMask = SEE_MASK_DEFAULT;
seInfo.lpVerb = L"runas"; // This asks the shell to elevate the process
seInfo.lpFile = module.c_str(); // This is `...\WindowsTerminal.exe`
seInfo.lpParameters = pCmdLine; // This is `new-tab -p {guid}`
seInfo.lpFile = cmd.c_str(); // This is `shell:AppsFolder\...` or `...\WindowsTerminal.exe`
seInfo.lpParameters = GetCommandLine(); // This is `new-tab -p {guid}`
seInfo.nShow = SW_SHOWNORMAL;
LOG_IF_WIN32_BOOL_FALSE(ShellExecuteExW(&seInfo));
return 0;
}

View File

@@ -217,6 +217,30 @@ namespace SettingsModelLocalTests
{
"name": "Different reference",
"colorScheme": "One Half Dark"
},
{
"name": "rename neither",
"colorScheme":
{
"dark": "One Half Dark",
"light": "One Half Light"
}
},
{
"name": "rename only light",
"colorScheme":
{
"dark": "One Half Dark",
"light": "Campbell"
}
},
{
"name": "rename only dark",
"colorScheme":
{
"dark": "Campbell",
"light": "One Half Light"
}
}
]
},
@@ -289,6 +313,28 @@ namespace SettingsModelLocalTests
"selectionBackground": "#FFFFFF",
"white": "#DCDFE4",
"yellow": "#E5C07B"
},
{
"name": "One Half Light",
"foreground": "#383A42",
"background": "#FAFAFA",
"cursorColor": "#4F525D",
"black": "#383A42",
"red": "#E45649",
"green": "#50A14F",
"yellow": "#C18301",
"blue": "#0184BC",
"purple": "#A626A4",
"cyan": "#0997B3",
"white": "#FAFAFA",
"brightBlack": "#4F525D",
"brightRed": "#DF6C75",
"brightGreen": "#98C379",
"brightYellow": "#E4C07A",
"brightBlue": "#61AFEF",
"brightPurple": "#C577DD",
"brightCyan": "#56B5C1",
"brightWhite": "#FFFFFF"
}
]
})json" };
@@ -296,31 +342,63 @@ namespace SettingsModelLocalTests
const auto settings{ winrt::make_self<CascadiaSettings>(settingsString) };
const auto newName{ L"Campbell (renamed)" };
settings->UpdateColorSchemeReferences(L"Campbell", newName);
VERIFY_ARE_EQUAL(newName, settings->ProfileDefaults().DefaultAppearance().ColorSchemeName());
VERIFY_IS_TRUE(settings->ProfileDefaults().DefaultAppearance().HasColorSchemeName());
VERIFY_ARE_EQUAL(newName, settings->ProfileDefaults().DefaultAppearance().DarkColorSchemeName());
VERIFY_ARE_EQUAL(newName, settings->ProfileDefaults().DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(settings->ProfileDefaults().DefaultAppearance().HasDarkColorSchemeName());
VERIFY_IS_TRUE(settings->ProfileDefaults().DefaultAppearance().HasLightColorSchemeName());
const auto& profiles{ settings->AllProfiles() };
{
const auto& prof{ profiles.GetAt(0) };
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().ColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasColorSchemeName());
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(1) };
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().ColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasColorSchemeName());
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(2) };
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().ColorSchemeName());
VERIFY_IS_FALSE(prof.DefaultAppearance().HasColorSchemeName());
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_IS_FALSE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_FALSE(prof.DefaultAppearance().HasLightColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(3) };
VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().ColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasColorSchemeName());
VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(4) };
VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_ARE_EQUAL(L"One Half Light", prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(5) };
VERIFY_ARE_EQUAL(L"One Half Dark", prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
}
{
const auto& prof{ profiles.GetAt(6) };
VERIFY_ARE_EQUAL(newName, prof.DefaultAppearance().DarkColorSchemeName());
VERIFY_ARE_EQUAL(L"One Half Light", prof.DefaultAppearance().LightColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasDarkColorSchemeName());
VERIFY_IS_TRUE(prof.DefaultAppearance().HasLightColorSchemeName());
}
}
}

View File

@@ -744,7 +744,8 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(3u, settings->AllProfiles().Size());
for (const auto& profile : settings->AllProfiles())
{
VERIFY_ARE_EQUAL(L"Campbell", profile.DefaultAppearance().ColorSchemeName());
VERIFY_ARE_EQUAL(L"Campbell", profile.DefaultAppearance().DarkColorSchemeName());
VERIFY_ARE_EQUAL(L"Campbell", profile.DefaultAppearance().LightColorSchemeName());
}
}
@@ -959,6 +960,10 @@ namespace SettingsModelLocalTests
},
{
"name": "profile3",
"closeOnExit": "automatic"
},
{
"name": "profile4",
"closeOnExit": null
}
]
@@ -968,9 +973,10 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(CloseOnExitMode::Graceful, settings->AllProfiles().GetAt(0).CloseOnExit());
VERIFY_ARE_EQUAL(CloseOnExitMode::Always, settings->AllProfiles().GetAt(1).CloseOnExit());
VERIFY_ARE_EQUAL(CloseOnExitMode::Never, settings->AllProfiles().GetAt(2).CloseOnExit());
VERIFY_ARE_EQUAL(CloseOnExitMode::Automatic, settings->AllProfiles().GetAt(3).CloseOnExit());
// Unknown modes parse as "Graceful"
VERIFY_ARE_EQUAL(CloseOnExitMode::Graceful, settings->AllProfiles().GetAt(3).CloseOnExit());
// Unknown modes parse as "Automatic"
VERIFY_ARE_EQUAL(CloseOnExitMode::Automatic, settings->AllProfiles().GetAt(4).CloseOnExit());
}
void DeserializationTests::TestCloseOnExitCompatibilityShim()
@@ -1524,7 +1530,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(0u, settings->Warnings().Size());
VERIFY_ARE_EQUAL(3u, settings->AllProfiles().Size());
// Because the "parent" command didn't have a name, it couldn't be
// placed into the list of commands. It and it's children are just
// placed into the list of commands. It and its children are just
// ignored.
VERIFY_ARE_EQUAL(0u, settings->ActionMap().NameMap().Size());
}

View File

@@ -0,0 +1,107 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "../TerminalSettingsModel/NewTabMenuEntry.h"
#include "../TerminalSettingsModel/FolderEntry.h"
#include "../TerminalSettingsModel/CascadiaSettings.h"
#include "../types/inc/colorTable.hpp"
#include "JsonTestClass.h"
#include <defaults.h>
using namespace Microsoft::Console;
using namespace winrt::Microsoft::Terminal;
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
namespace SettingsModelLocalTests
{
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class NewTabMenuTests : public JsonTestClass
{
// Use a custom AppxManifest to ensure that we can activate winrt types
// from our test. This property will tell taef to manually use this as
// the AppxManifest for this test class.
// This does not yet work for anything XAML-y. See TabTests.cpp for more
// details on that.
BEGIN_TEST_CLASS(NewTabMenuTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS()
TEST_METHOD(DefaultsToRemainingProfiles);
TEST_METHOD(ParseEmptyFolder);
};
void NewTabMenuTests::DefaultsToRemainingProfiles()
{
Log::Comment(L"If the user doesn't customize the menu, put one entry for each profile");
static constexpr std::string_view settingsString{ R"json({
})json" };
try
{
const auto settings{ winrt::make_self<CascadiaSettings>(settingsString, DefaultJson) };
VERIFY_ARE_EQUAL(0u, settings->Warnings().Size());
const auto& entries = settings->GlobalSettings().NewTabMenu();
VERIFY_ARE_EQUAL(1u, entries.Size());
VERIFY_ARE_EQUAL(winrt::Microsoft::Terminal::Settings::Model::NewTabMenuEntryType::RemainingProfiles, entries.GetAt(0).Type());
}
catch (const SettingsException& ex)
{
auto loadError = ex.Error();
loadError;
throw ex;
}
catch (const SettingsTypedDeserializationException& e)
{
auto deserializationErrorMessage = til::u8u16(e.what());
Log::Comment(NoThrowString().Format(deserializationErrorMessage.c_str()));
throw e;
}
}
void NewTabMenuTests::ParseEmptyFolder()
{
Log::Comment(L"GH #14557 - An empty folder entry shouldn't crash");
static constexpr std::string_view settingsString{ R"json({
"newTabMenu": [
{ "type": "folder" }
]
})json" };
try
{
const auto settings{ winrt::make_self<CascadiaSettings>(settingsString, DefaultJson) };
VERIFY_ARE_EQUAL(0u, settings->Warnings().Size());
const auto& entries = settings->GlobalSettings().NewTabMenu();
VERIFY_ARE_EQUAL(1u, entries.Size());
}
catch (const SettingsException& ex)
{
auto loadError = ex.Error();
loadError;
throw ex;
}
catch (const SettingsTypedDeserializationException& e)
{
auto deserializationErrorMessage = til::u8u16(e.what());
Log::Comment(NoThrowString().Format(deserializationErrorMessage.c_str()));
throw e;
}
}
}

View File

@@ -134,7 +134,7 @@ namespace SettingsModelLocalTests
"font": {
"face": "Cascadia Mono",
"size": 12,
"size": 12.0,
"weight": "normal"
},
"padding": "8, 8, 8, 8",
@@ -250,8 +250,8 @@ namespace SettingsModelLocalTests
// complex command with key chords
static constexpr std::string_view actionsString4A{ R"([
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+c" },
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+d" }
{ "command": { "action": "adjustFontSize", "delta": 1.0 }, "keys": "ctrl+c" },
{ "command": { "action": "adjustFontSize", "delta": 1.0 }, "keys": "ctrl+d" }
])" };
// GH#13323 - these can be fragile. In the past, the order these get
// re-serialized as has been not entirely stable. We don't really care
@@ -260,7 +260,7 @@ namespace SettingsModelLocalTests
// itself. Feel free to change as needed.
static constexpr std::string_view actionsString4B{ R"([
{ "command": { "action": "findMatch", "direction": "prev" }, "keys": "ctrl+shift+r" },
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+d" }
{ "command": { "action": "adjustFontSize", "delta": 1.0 }, "keys": "ctrl+d" }
])" };
// command with name and icon and multiple key chords
@@ -284,8 +284,8 @@ namespace SettingsModelLocalTests
{
"name": "Change font size...",
"commands": [
{ "command": { "action": "adjustFontSize", "delta": 1 } },
{ "command": { "action": "adjustFontSize", "delta": -1 } },
{ "command": { "action": "adjustFontSize", "delta": 1.0 } },
{ "command": { "action": "adjustFontSize", "delta": -1.0 } },
{ "command": "resetFontSize" },
]
}
@@ -406,6 +406,12 @@ namespace SettingsModelLocalTests
"$schema" : "https://aka.ms/terminal-profiles-schema",
"defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}",
"disabledProfileSources": [ "Windows.Terminal.Wsl" ],
"newTabMenu":
[
{
"type": "remainingProfiles"
}
],
"profiles": {
"defaults": {
"font": {
@@ -478,7 +484,7 @@ namespace SettingsModelLocalTests
"name": "Profile with legacy font settings",
"fontFace": "Cascadia Mono",
"fontSize": 12,
"fontSize": 12.0,
"fontWeight": "normal"
})" };
@@ -488,7 +494,7 @@ namespace SettingsModelLocalTests
"font": {
"face": "Cascadia Mono",
"size": 12,
"size": 12.0,
"weight": "normal"
}
})" };

View File

@@ -8,7 +8,7 @@
dependencies, like MUX, can be aggregated correctly, and resources properly
combined into a resources.pri file.
TestHostApp will manually copy the output of this project into it's own
TestHostApp will manually copy the output of this project into its own
OutDir, so we can run the tests from there. -->
<PropertyGroup>
@@ -39,6 +39,7 @@
<ClCompile Include="KeyBindingsTests.cpp" />
<ClCompile Include="CommandTests.cpp" />
<ClCompile Include="DeserializationTests.cpp" />
<ClCompile Include="NewTabMenuTests.cpp" />
<ClCompile Include="SerializationTests.cpp" />
<ClCompile Include="TerminalSettingsTests.cpp" />
<ClCompile Include="ThemeTests.cpp" />

View File

@@ -779,21 +779,22 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(6u, settings->ActiveProfiles().Size());
VERIFY_ARE_EQUAL(2u, settings->GlobalSettings().ColorSchemes().Size());
auto createTerminalSettings = [&](const auto& profile, const auto& schemes) {
auto createTerminalSettings = [&](const auto& profile, const auto& schemes, const auto& Theme) {
auto terminalSettings{ winrt::make_self<implementation::TerminalSettings>() };
terminalSettings->_ApplyProfileSettings(profile);
terminalSettings->_ApplyAppearanceSettings(profile.DefaultAppearance(), schemes);
terminalSettings->_ApplyAppearanceSettings(profile.DefaultAppearance(), schemes, Theme);
return terminalSettings;
};
const auto activeProfiles = settings->ActiveProfiles();
const auto colorSchemes = settings->GlobalSettings().ColorSchemes();
const auto terminalSettings0 = createTerminalSettings(activeProfiles.GetAt(0), colorSchemes);
const auto terminalSettings1 = createTerminalSettings(activeProfiles.GetAt(1), colorSchemes);
const auto terminalSettings2 = createTerminalSettings(activeProfiles.GetAt(2), colorSchemes);
const auto terminalSettings3 = createTerminalSettings(activeProfiles.GetAt(3), colorSchemes);
const auto terminalSettings4 = createTerminalSettings(activeProfiles.GetAt(4), colorSchemes);
const auto terminalSettings5 = createTerminalSettings(activeProfiles.GetAt(5), colorSchemes);
const auto currentTheme = settings->GlobalSettings().CurrentTheme();
const auto terminalSettings0 = createTerminalSettings(activeProfiles.GetAt(0), colorSchemes, currentTheme);
const auto terminalSettings1 = createTerminalSettings(activeProfiles.GetAt(1), colorSchemes, currentTheme);
const auto terminalSettings2 = createTerminalSettings(activeProfiles.GetAt(2), colorSchemes, currentTheme);
const auto terminalSettings3 = createTerminalSettings(activeProfiles.GetAt(3), colorSchemes, currentTheme);
const auto terminalSettings4 = createTerminalSettings(activeProfiles.GetAt(4), colorSchemes, currentTheme);
const auto terminalSettings5 = createTerminalSettings(activeProfiles.GetAt(5), colorSchemes, currentTheme);
VERIFY_ARE_EQUAL(til::color(0x12, 0x34, 0x56), terminalSettings0->CursorColor()); // from color scheme
VERIFY_ARE_EQUAL(DEFAULT_CURSOR_COLOR, terminalSettings1->CursorColor()); // default

View File

@@ -64,7 +64,8 @@ namespace SettingsModelLocalTests
},
"window":
{
"applicationTheme": "light"
"applicationTheme": "light",
"useMica": true
}
})" };
@@ -80,6 +81,7 @@ namespace SettingsModelLocalTests
VERIFY_IS_NOT_NULL(theme->Window());
VERIFY_ARE_EQUAL(winrt::Windows::UI::Xaml::ElementTheme::Light, theme->Window().RequestedTheme());
VERIFY_ARE_EQUAL(true, theme->Window().UseMica());
}
void ThemeTests::ParseEmptyTheme()
@@ -161,7 +163,8 @@ namespace SettingsModelLocalTests
},
"window":
{
"applicationTheme": "light"
"applicationTheme": "light",
"useMica": true
}
},
{
@@ -172,14 +175,16 @@ namespace SettingsModelLocalTests
},
"window":
{
"applicationTheme": "light"
"applicationTheme": "light",
"useMica": true
}
},
{
"name": "backgroundOmittedEntirely",
"window":
{
"applicationTheme": "light"
"applicationTheme": "light",
"useMica": true
}
}
]
@@ -234,7 +239,8 @@ namespace SettingsModelLocalTests
"tabRow": {},
"window":
{
"applicationTheme": "light"
"applicationTheme": "light",
"useMica": true
}
}
]

View File

@@ -44,6 +44,7 @@ Author(s):
#include <Windows.Graphics.Imaging.Interop.h>
#include <winrt/windows.ui.core.h>
#include <winrt/Windows.ui.input.h>
#include <winrt/Windows.UI.ViewManagement.h>
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Controls.Primitives.h>
#include <winrt/Windows.ui.xaml.media.h>

View File

@@ -8,7 +8,7 @@
dependencies, like MUX, can be aggregated correctly, and resources properly
combined into a resources.pri file.
TestHostApp will manually copy the output of this project into it's own
TestHostApp will manually copy the output of this project into its own
OutDir, so we can run the tests from there. -->
<PropertyGroup>
@@ -23,7 +23,6 @@
<PropertyGroup Label="NuGet Dependencies">
<!-- TerminalCppWinrt is intentionally not set -->
<TerminalXamlApplicationToolkit>true</TerminalXamlApplicationToolkit>
</PropertyGroup>
<Import Project="$(SolutionDir)\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />

View File

@@ -24,25 +24,6 @@ static constexpr bool _IsMouseMessage(UINT uMsg)
uMsg == WM_MOUSEMOVE || uMsg == WM_MOUSEWHEEL || uMsg == WM_MOUSEHWHEEL;
}
// Helper static function to ensure that all ambiguous-width glyphs are reported as narrow.
// See microsoft/terminal#2066 for more info.
static bool _IsGlyphWideForceNarrowFallback(const std::wstring_view /* glyph */) noexcept
{
return false; // glyph is not wide.
}
static bool _EnsureStaticInitialization()
{
// use C++11 magic statics to make sure we only do this once.
static auto initialized = []() {
// *** THIS IS A SINGLETON ***
SetGlyphWidthFallback(_IsGlyphWideForceNarrowFallback);
return true;
}();
return initialized;
}
LRESULT CALLBACK HwndTerminal::HwndTerminalWndProc(
HWND hwnd,
UINT uMsg,
@@ -121,11 +102,11 @@ try
}
break;
case WM_RBUTTONDOWN:
if (terminal->_terminal->IsSelectionActive())
if (const auto& termCore{ terminal->_terminal }; termCore && termCore->IsSelectionActive())
{
try
{
const auto bufferData = terminal->_terminal->RetrieveSelectedTextFromBuffer(false);
const auto bufferData = termCore->RetrieveSelectedTextFromBuffer(false);
LOG_IF_FAILED(terminal->_CopyTextToSystemClipboard(bufferData, true));
TerminalClearSelection(terminal);
}
@@ -175,7 +156,7 @@ static bool RegisterTermClass(HINSTANCE hInstance) noexcept
return RegisterClassW(&wc) != 0;
}
HwndTerminal::HwndTerminal(HWND parentHwnd) :
HwndTerminal::HwndTerminal(HWND parentHwnd) noexcept :
_desiredFont{ L"Consolas", 0, DEFAULT_FONT_WEIGHT, 14, CP_UTF8 },
_actualFont{ L"Consolas", 0, DEFAULT_FONT_WEIGHT, { 0, 14 }, CP_UTF8, false },
_uiaProvider{ nullptr },
@@ -183,8 +164,6 @@ HwndTerminal::HwndTerminal(HWND parentHwnd) :
_pfnWriteCallback{ nullptr },
_multiClickTime{ 500 } // this will be overwritten by the windows system double-click time
{
_EnsureStaticInitialization();
auto hInstance = wil::GetModuleInstanceHandle();
if (RegisterTermClass(hInstance))
@@ -281,6 +260,10 @@ CATCH_LOG();
void HwndTerminal::RegisterScrollCallback(std::function<void(int, int, int)> callback)
{
if (!_terminal)
{
return;
}
_terminal->SetScrollPositionChangedCallback(callback);
}
@@ -304,7 +287,7 @@ void HwndTerminal::RegisterWriteCallback(const void _stdcall callback(wchar_t*))
_pfnWriteCallback = callback;
}
::Microsoft::Console::Types::IUiaData* HwndTerminal::GetUiaData() const noexcept
::Microsoft::Console::Render::IRenderData* HwndTerminal::GetRenderData() const noexcept
{
return _terminal.get();
}
@@ -316,6 +299,10 @@ HWND HwndTerminal::GetHwnd() const noexcept
void HwndTerminal::_UpdateFont(int newDpi)
{
if (!_terminal)
{
return;
}
_currentDpi = newDpi;
auto lock = _terminal->LockForWriting();
@@ -332,8 +319,12 @@ IRawElementProviderSimple* HwndTerminal::_GetUiaProvider() noexcept
{
try
{
if (!_terminal)
{
return nullptr;
}
auto lock = _terminal->LockForWriting();
LOG_IF_FAILED(::Microsoft::WRL::MakeAndInitialize<HwndTerminalAutomationPeer>(&_uiaProvider, this->GetUiaData(), this));
LOG_IF_FAILED(::Microsoft::WRL::MakeAndInitialize<HwndTerminalAutomationPeer>(&_uiaProvider, this->GetRenderData(), this));
_uiaEngine = std::make_unique<::Microsoft::Console::Render::UiaEngine>(_uiaProvider.Get());
LOG_IF_FAILED(_uiaEngine->Enable());
_renderer->AddRenderEngine(_uiaEngine.get());
@@ -350,6 +341,7 @@ IRawElementProviderSimple* HwndTerminal::_GetUiaProvider() noexcept
HRESULT HwndTerminal::Refresh(const til::size windowSize, _Out_ til::size* dimensions)
{
RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _terminal);
RETURN_HR_IF_NULL(E_INVALIDARG, dimensions);
auto lock = _terminal->LockForWriting();
@@ -365,21 +357,30 @@ HRESULT HwndTerminal::Refresh(const til::size windowSize, _Out_ til::size* dimen
const auto viewInPixels = Viewport::FromDimensions(windowSize);
const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels);
// Guard against resizing the window to 0 columns/rows, which the text buffer classes don't really support.
auto size = vp.Dimensions();
size.width = std::max(size.width, 1);
size.height = std::max(size.height, 1);
// If this function succeeds with S_FALSE, then the terminal didn't
// actually change size. No need to notify the connection of this
// no-op.
// TODO: MSFT:20642295 Resizing the buffer will corrupt it
// I believe we'll need support for CSI 2J, and additionally I think
// we're resetting the viewport to the top
RETURN_IF_FAILED(_terminal->UserResize({ vp.Width(), vp.Height() }));
dimensions->X = vp.Width();
dimensions->Y = vp.Height();
RETURN_IF_FAILED(_terminal->UserResize(size));
dimensions->width = size.width;
dimensions->height = size.height;
return S_OK;
}
void HwndTerminal::SendOutput(std::wstring_view data)
{
if (!_terminal)
{
return;
}
_terminal->Write(data);
}
@@ -453,8 +454,8 @@ HRESULT _stdcall TerminalTriggerResizeWithDimension(_In_ void* terminal, _In_ ti
const auto viewInCharacters = Viewport::FromDimensions(dimensionsInCharacters);
const auto viewInPixels = publicTerminal->_renderEngine->GetViewportInPixels(viewInCharacters);
dimensionsInPixels->cx = viewInPixels.Width();
dimensionsInPixels->cy = viewInPixels.Height();
dimensionsInPixels->width = viewInPixels.Width();
dimensionsInPixels->height = viewInPixels.Height();
til::size unused;
@@ -473,11 +474,11 @@ HRESULT _stdcall TerminalCalculateResize(_In_ void* terminal, _In_ til::CoordTyp
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, { width, height });
const auto viewInPixels = Viewport::FromDimensions({ width, height });
const auto viewInCharacters = publicTerminal->_renderEngine->GetViewportInCharacters(viewInPixels);
dimensions->X = viewInCharacters.Width();
dimensions->Y = viewInCharacters.Height();
dimensions->width = viewInCharacters.Width();
dimensions->height = viewInCharacters.Height();
return S_OK;
}
@@ -490,8 +491,10 @@ void _stdcall TerminalDpiChanged(void* terminal, int newDpi)
void _stdcall TerminalUserScroll(void* terminal, int viewTop)
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
publicTerminal->_terminal->UserScrollViewport(viewTop);
if (const auto publicTerminal = static_cast<const HwndTerminal*>(terminal); publicTerminal && publicTerminal->_terminal)
{
publicTerminal->_terminal->UserScrollViewport(viewTop);
}
}
const unsigned int HwndTerminal::_NumberOfClicks(til::point point, std::chrono::steady_clock::time_point timestamp) noexcept
@@ -513,6 +516,7 @@ const unsigned int HwndTerminal::_NumberOfClicks(til::point point, std::chrono::
HRESULT HwndTerminal::_StartSelection(LPARAM lParam) noexcept
try
{
RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _terminal);
const til::point cursorPosition{
GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam),
@@ -556,6 +560,7 @@ CATCH_RETURN();
HRESULT HwndTerminal::_MoveSelection(LPARAM lParam) noexcept
try
{
RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _terminal);
const til::point cursorPosition{
GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam),
@@ -594,6 +599,10 @@ CATCH_RETURN();
void HwndTerminal::_ClearSelection() noexcept
try
{
if (!_terminal)
{
return;
}
auto lock{ _terminal->LockForWriting() };
_terminal->ClearSelection();
_renderer->TriggerSelection();
@@ -608,15 +617,21 @@ void _stdcall TerminalClearSelection(void* terminal)
bool _stdcall TerminalIsSelectionActive(void* terminal)
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
const auto selectionActive = publicTerminal->_terminal->IsSelectionActive();
return selectionActive;
if (const auto publicTerminal = static_cast<const HwndTerminal*>(terminal); publicTerminal && publicTerminal->_terminal)
{
return publicTerminal->_terminal->IsSelectionActive();
}
return false;
}
// Returns the selected text in the terminal.
const wchar_t* _stdcall TerminalGetSelection(void* terminal)
{
auto publicTerminal = static_cast<HwndTerminal*>(terminal);
if (!publicTerminal || !publicTerminal->_terminal)
{
return nullptr;
}
const auto bufferData = publicTerminal->_terminal->RetrieveSelectedTextFromBuffer(false);
publicTerminal->_ClearSelection();
@@ -668,12 +683,17 @@ bool HwndTerminal::_CanSendVTMouseInput() const noexcept
{
// Only allow the transit of mouse events if shift isn't pressed.
const auto shiftPressed = GetKeyState(VK_SHIFT) < 0;
return !shiftPressed && _focused && _terminal->IsTrackingMouseInput();
return !shiftPressed && _focused && _terminal && _terminal->IsTrackingMouseInput();
}
bool HwndTerminal::_SendMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept
try
{
if (!_terminal)
{
return false;
}
til::point cursorPosition{
GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam),
@@ -706,6 +726,11 @@ catch (...)
void HwndTerminal::_SendKeyEvent(WORD vkey, WORD scanCode, WORD flags, bool keyDown) noexcept
try
{
if (!_terminal)
{
return;
}
auto modifiers = getControlKeyState();
if (WI_IsFlagSet(flags, ENHANCED_KEY))
{
@@ -722,7 +747,11 @@ CATCH_LOG();
void HwndTerminal::_SendCharEvent(wchar_t ch, WORD scanCode, WORD flags) noexcept
try
{
if (_terminal->IsSelectionActive())
if (!_terminal)
{
return;
}
else if (_terminal->IsSelectionActive())
{
_ClearSelection();
if (ch == UNICODE_ESC)
@@ -770,6 +799,10 @@ void _stdcall DestroyTerminal(void* terminal)
void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, til::CoordType fontSize, int newDpi)
{
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
if (!publicTerminal || !publicTerminal->_terminal)
{
return;
}
{
auto lock = publicTerminal->_terminal->LockForWriting();
@@ -805,7 +838,7 @@ void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR font
void _stdcall TerminalBlinkCursor(void* terminal)
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
if (!publicTerminal->_terminal->IsCursorBlinkingAllowed() && publicTerminal->_terminal->IsCursorVisible())
if (!publicTerminal || !publicTerminal->_terminal || (!publicTerminal->_terminal->IsCursorBlinkingAllowed() && publicTerminal->_terminal->IsCursorVisible()))
{
return;
}
@@ -816,6 +849,10 @@ void _stdcall TerminalBlinkCursor(void* terminal)
void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible)
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
if (!publicTerminal || !publicTerminal->_terminal)
{
return;
}
publicTerminal->_terminal->SetCursorOn(visible);
}
@@ -847,6 +884,7 @@ void __stdcall TerminalKillFocus(void* terminal)
HRESULT HwndTerminal::_CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, const bool fAlsoCopyFormatting)
try
{
RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _terminal);
std::wstring finalString;
// Concatenate strings into one giant string to put onto the clipboard.
@@ -884,7 +922,7 @@ try
if (fAlsoCopyFormatting)
{
const auto& fontData = _actualFont;
const int iFontHeightPoints = fontData.GetUnscaledSize().Y; // this renderer uses points already
const int iFontHeightPoints = fontData.GetUnscaledSize().height; // this renderer uses points already
const auto bgColor = _terminal->GetAttributeColors({}).second;
auto HTMLToPlaceOnClip = TextBuffer::GenHTML(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor);
@@ -1003,7 +1041,11 @@ double HwndTerminal::GetScaleFactor() const noexcept
void HwndTerminal::ChangeViewport(const til::inclusive_rect& NewWindow)
{
_terminal->UserScrollViewport(NewWindow.Top);
if (!_terminal)
{
return;
}
_terminal->UserScrollViewport(NewWindow.top);
}
HRESULT HwndTerminal::GetHostUiaProvider(IRawElementProviderSimple** provider) noexcept

View File

@@ -49,7 +49,7 @@ __declspec(dllexport) void _stdcall TerminalKillFocus(void* terminal);
struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo
{
public:
HwndTerminal(HWND hwnd);
HwndTerminal(HWND hwnd) noexcept;
HwndTerminal(const HwndTerminal&) = default;
HwndTerminal(HwndTerminal&&) = default;
@@ -63,7 +63,7 @@ public:
HRESULT Refresh(const til::size windowSize, _Out_ til::size* dimensions);
void RegisterScrollCallback(std::function<void(int, int, int)> callback);
void RegisterWriteCallback(const void _stdcall callback(wchar_t*));
::Microsoft::Console::Types::IUiaData* GetUiaData() const noexcept;
::Microsoft::Console::Render::IRenderData* GetRenderData() const noexcept;
HWND GetHwnd() const noexcept;
static LRESULT CALLBACK HwndTerminalWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept;

View File

@@ -148,7 +148,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
}
// Method Description:
// - Tell this window to display it's window ID. We'll raise a
// - Tell this window to display its window ID. We'll raise a
// DisplayWindowIdRequested event, which will get handled in the AppHost,
// and used to tell the app to display the ID toast.
// Arguments:

View File

@@ -12,7 +12,7 @@ Abstract:
ShouldCreateWindow is false, that implies that some other window process was
given the commandline for handling, and the caller should just exit.
- If ShouldCreateWindow is true, the Id property may or may not contain an ID
that the new window should use as it's ID.
that the new window should use as its ID.
--*/

View File

@@ -109,7 +109,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
try
{
// MSFT:38542548 _We believe_ that this is the source of the
// crash here. After we get the result, stash it's values into a
// crash here. After we get the result, stash its values into a
// local copy, so that we can check them later. If the Monarch
// dies between now and the inspection of
// `result.ShouldCreateWindow` below, we don't want to explode
@@ -310,7 +310,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
}
// This right here will just tell us to stash the args away for the
// future. The AppHost hasnt yet set up the callbacks, and the rest
// future. The AppHost hasn't yet set up the callbacks, and the rest
// of the app hasn't started at all. We'll note them and come back
// later.
_peasant.ExecuteCommandline(args);
@@ -488,7 +488,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
{
try
{
// Wrap this in it's own try/catch, because this can throw.
// Wrap this in its own try/catch, because this can throw.
_createMonarchAndCallbacks();
}
catch (...)
@@ -572,7 +572,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// monarch.
try
{
// This might fail to even ask the monarch for it's PID.
// This might fail to even ask the monarch for its PID.
wil::unique_handle hMonarch{ OpenProcess(PROCESS_ALL_ACCESS,
FALSE,
static_cast<DWORD>(_monarch.GetPID())) };
@@ -647,7 +647,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
catch (...)
{
// Theoretically, if window[1] dies when we're trying to get
// it's PID we'll get here. If we just try to do the election
// its PID we'll get here. If we just try to do the election
// once here, it's possible we might elect window[2], but have
// it die before we add ourselves as a peasant. That
// _performElection call will throw, and we wouldn't catch it

View File

@@ -49,7 +49,7 @@
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj">
<Project>{18D09A24-8240-42D6-8CB6-236EEE820263}</Project>
</ProjectReference>
<!-- Reference Microsoft.Terminal.RemotingLib here, so we can use it's winmd as
<!-- Reference Microsoft.Terminal.RemotingLib here, so we can use its winmd as
our winmd. This didn't work correctly in VS2017, you'd need to
manually reference the lib -->
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\Remoting\Microsoft.Terminal.RemotingLib.vcxproj">

View File

@@ -12,19 +12,12 @@ using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Windows::UI::Xaml::Navigation;
namespace xaml = ::winrt::Windows::UI::Xaml;
namespace winrt::TerminalApp::implementation
{
App::App()
{
// This is the same trick that Initialize() is about to use to figure out whether we're coming
// from a UWP context or from a Win32 context
// See https://github.com/windows-toolkit/Microsoft.Toolkit.Win32/blob/52611c57d89554f357f281d0c79036426a7d9257/Microsoft.Toolkit.Win32.UI.XamlApplication/XamlApplication.cpp#L42
const auto dispatcherQueue = ::winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
if (dispatcherQueue)
{
_isUwp = true;
}
Initialize();
// Disable XAML's automatic backplating of text when in High Contrast
@@ -33,12 +26,50 @@ namespace winrt::TerminalApp::implementation
HighContrastAdjustment(::winrt::Windows::UI::Xaml::ApplicationHighContrastAdjustment::None);
}
void App::Initialize()
{
const auto dispatcherQueue = winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
if (!dispatcherQueue)
{
_windowsXamlManager = xaml::Hosting::WindowsXamlManager::InitializeForCurrentThread();
}
else
{
_isUwp = true;
}
}
AppLogic App::Logic()
{
static AppLogic logic;
return logic;
}
void App::Close()
{
if (_bIsClosed)
{
return;
}
_bIsClosed = true;
if (_windowsXamlManager)
{
_windowsXamlManager.Close();
}
_windowsXamlManager = nullptr;
Exit();
{
MSG msg = {};
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
{
::DispatchMessageW(&msg);
}
}
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
@@ -54,7 +85,7 @@ namespace winrt::TerminalApp::implementation
{
auto logic = Logic();
logic.RunAsUwp(); // Must set UWP status first, settings might change based on it.
logic.LoadSettings();
logic.ReloadSettings();
logic.Create();
auto page = logic.GetRoot().as<TerminalPage>();

View File

@@ -5,6 +5,7 @@
#include "App.g.h"
#include "App.base.h"
#include <winrt/Windows.UI.Xaml.Hosting.h>
namespace winrt::TerminalApp::implementation
{
@@ -13,11 +14,22 @@ namespace winrt::TerminalApp::implementation
public:
App();
void OnLaunched(const Windows::ApplicationModel::Activation::LaunchActivatedEventArgs&);
void Initialize();
TerminalApp::AppLogic Logic();
void Close();
bool IsDisposed() const
{
return _bIsClosed;
}
private:
bool _isUwp = false;
winrt::Windows::UI::Xaml::Hosting::WindowsXamlManager _windowsXamlManager = nullptr;
winrt::Windows::Foundation::Collections::IVector<winrt::Windows::UI::Xaml::Markup::IXamlMetadataProvider> _providers = winrt::single_threaded_vector<Windows::UI::Xaml::Markup::IXamlMetadataProvider>();
bool _bIsClosed = false;
};
}

View File

@@ -7,10 +7,12 @@ namespace TerminalApp
{
// ADD ARBITRARY APP LOGIC TO AppLogic.idl, NOT HERE.
// This is for XAML platform setup only.
[default_interface] runtimeclass App : Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication
[default_interface] runtimeclass App : Windows.UI.Xaml.Application, Windows.Foundation.IClosable
{
App();
AppLogic Logic { get; };
Boolean IsDisposed { get; };
}
}

View File

@@ -2,20 +2,19 @@
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
the MIT License. See LICENSE in the project root for license information.
-->
<Toolkit:XamlApplication x:Class="TerminalApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:TA="using:TerminalApp"
xmlns:Toolkit="using:Microsoft.Toolkit.Win32.UI.XamlHost"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:TerminalApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Application x:Class="TerminalApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:TA="using:TerminalApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:TerminalApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<!--
If you want to prove this works, then add `RequestedTheme="Light"` to
the properties on the XamlApplication
-->
<Toolkit:XamlApplication.Resources>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
@@ -139,7 +138,7 @@
evaluated as SolidBackgroundFillColorBase. If we try
to use those resources directly though, we don't get
the properly themed versions. Presumably because the
App itself can't have it's RequestedTheme changed at
App itself can't have its RequestedTheme changed at
runtime.
However, after more discussion with the WinUI
@@ -153,7 +152,7 @@
Color="#2e2e2e" />
<StaticResource x:Key="UnfocusedBorderBrush"
ResourceKey="ApplicationPageBackgroundThemeBrush" />
ResourceKey="TabViewBackground" />
<SolidColorBrush x:Key="SettingsUiTabBrush"
Color="#0c0c0c" />
@@ -169,7 +168,7 @@
Color="#e8e8e8" />
<StaticResource x:Key="UnfocusedBorderBrush"
ResourceKey="ApplicationPageBackgroundThemeBrush" />
ResourceKey="TabViewBackground" />
<SolidColorBrush x:Key="SettingsUiTabBrush"
Color="#ffffff" />
@@ -202,5 +201,5 @@
</ResourceDictionary>
</Toolkit:XamlApplication.Resources>
</Toolkit:XamlApplication>
</Application.Resources>
</Application>

View File

@@ -510,7 +510,10 @@ namespace winrt::TerminalApp::implementation
void TerminalPage::_HandleFind(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
_Find();
if (const auto activeTab{ _GetFocusedTabImpl() })
{
_Find(*activeTab);
}
args.Handled(true);
}

View File

@@ -97,7 +97,7 @@ int AppCommandlineArgs::ParseCommand(const Commandline& command)
}
// Method Description:
// - Calls App::exit() for the provided command, and collects it's output into
// - Calls App::exit() for the provided command, and collects its output into
// our _exitMessage buffer.
// Arguments:
// - command: Either the root App object, or a subcommand for which to call exit() on.

View File

@@ -52,6 +52,7 @@ static const std::array settingsLoadWarningsLabels {
USES_RESOURCE(L"FailedToParseStartupActions"),
USES_RESOURCE(L"FailedToParseSubCommands"),
USES_RESOURCE(L"UnknownTheme"),
USES_RESOURCE(L"DuplicateRemainingProfilesEntry"),
};
static const std::array settingsLoadErrorsLabels {
USES_RESOURCE(L"NoProfilesText"),
@@ -191,7 +192,7 @@ namespace winrt::TerminalApp::implementation
_reloadSettings = std::make_shared<ThrottledFuncTrailing<>>(winrt::Windows::System::DispatcherQueue::GetForCurrentThread(), std::chrono::milliseconds(100), [weakSelf = get_weak()]() {
if (auto self{ weakSelf.get() })
{
self->_ReloadSettings();
self->ReloadSettings();
}
});
@@ -615,7 +616,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
ReloadSettings();
}
winrt::Windows::Foundation::Size proposedSize{};
@@ -696,7 +697,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
ReloadSettings();
}
// GH#4620/#5801 - If the user passed --maximized or --fullscreen on the
@@ -730,7 +731,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
ReloadSettings();
}
auto initialPosition{ _settings.GlobalSettings().InitialPosition() };
@@ -760,7 +761,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
ReloadSettings();
}
// If the position has been specified on the commandline, don't center on launch
return _settings.GlobalSettings().CenterOnLaunch() && !_appArgs.GetPosition().has_value();
@@ -776,7 +777,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
ReloadSettings();
}
return _settings.GlobalSettings().ShowTabsInTitlebar();
@@ -787,7 +788,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
ReloadSettings();
}
return _settings.GlobalSettings().AlwaysOnTop();
@@ -867,36 +868,6 @@ namespace winrt::TerminalApp::implementation
return hr;
}
// Method Description:
// - Initialized our settings. See CascadiaSettings for more details.
// Additionally hooks up our callbacks for keybinding events to the
// keybindings object.
// NOTE: This must be called from a MTA if we're running as a packaged
// application. The Windows.Storage APIs require a MTA. If this isn't
// happening during startup, it'll need to happen on a background thread.
void AppLogic::LoadSettings()
{
// Attempt to load the settings.
// If it fails,
// - use Default settings,
// - don't persist them (LoadAll won't save them in this case).
// - _settingsLoadedResult will be set to an error, indicating that
// we should display the loading error.
// * We can't display the error now, because we might not have a
// UI yet. We'll display the error in _OnLoaded.
_settingsLoadedResult = _TryLoadSettings();
if (FAILED(_settingsLoadedResult))
{
_settings = CascadiaSettings::LoadDefaults();
}
_loadedInitialSettings = true;
// Register for directory change notification.
_RegisterSettingsChange();
}
// Call this function after loading your _settings.
// It handles any CPU intensive settings updates (like updating the Jumplist)
// which should thus only occur if the settings file actually changed.
@@ -920,7 +891,7 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - Registers for changes to the settings folder and upon a updated settings
// profile calls _ReloadSettings().
// profile calls ReloadSettings().
// Arguments:
// - <none>
// Return Value:
@@ -1055,7 +1026,14 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - Reloads the settings from the settings.json file.
void AppLogic::_ReloadSettings()
// - When this is called the first time, this initializes our settings. See
// CascadiaSettings for more details. Additionally hooks up our callbacks
// for keybinding events to the keybindings object.
// - NOTE: when called initially, this must be called from a MTA if we're
// running as a packaged application. The Windows.Storage APIs require a
// MTA. If this isn't happening during startup, it'll need to happen on
// a background thread.
void AppLogic::ReloadSettings()
{
// Attempt to load our settings.
// If it fails,
@@ -1064,11 +1042,28 @@ namespace winrt::TerminalApp::implementation
// - display a loading error
_settingsLoadedResult = _TryLoadSettings();
const auto initialLoad = !_loadedInitialSettings;
_loadedInitialSettings = true;
if (FAILED(_settingsLoadedResult))
{
const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle");
const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText");
_ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult);
if (initialLoad)
{
_settings = CascadiaSettings::LoadDefaults();
}
else
{
const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle");
const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText");
_ShowLoadErrorsDialog(titleKey, textKey, _settingsLoadedResult);
return;
}
}
if (initialLoad)
{
// Register for directory change notification.
_RegisterSettingsChange();
return;
}
@@ -1375,7 +1370,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
ReloadSettings();
}
return AppLogic::_doFindTargetWindow(args, _settings.GlobalSettings().WindowingBehavior());
@@ -1532,7 +1527,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
ReloadSettings();
}
return _settings.GlobalSettings().AutoHideWindow();
@@ -1553,7 +1548,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
ReloadSettings();
}
return _root != nullptr ? _root->ShouldImmediatelyHandoffToElevated(_settings) : false;
@@ -1666,7 +1661,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
ReloadSettings();
}
return _settings.GlobalSettings().MinimizeToNotificationArea();
@@ -1677,7 +1672,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
ReloadSettings();
}
return _settings.GlobalSettings().AlwaysShowNotificationIcon();
@@ -1693,7 +1688,7 @@ namespace winrt::TerminalApp::implementation
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
ReloadSettings();
}
return _settings.GlobalSettings().CurrentTheme();
}

View File

@@ -62,7 +62,8 @@ namespace winrt::TerminalApp::implementation
bool IsUwp() const noexcept;
void RunAsUwp();
bool IsElevated() const noexcept;
void LoadSettings();
void ReloadSettings();
[[nodiscard]] Microsoft::Terminal::Settings::Model::CascadiaSettings GetSettings() const noexcept;
void Quit();
@@ -195,7 +196,6 @@ namespace winrt::TerminalApp::implementation
void _ProcessLazySettingsChanges();
void _RegisterSettingsChange();
fire_and_forget _DispatchReloadSettings();
void _ReloadSettings();
void _OpenSettingsUI();
bool _hasCommandLineArguments{ false };

View File

@@ -59,7 +59,7 @@ namespace TerminalApp
void Quit();
void LoadSettings();
void ReloadSettings();
Windows.UI.Xaml.UIElement GetRoot();
void SetInboundListener();

View File

@@ -5,6 +5,8 @@
#include "Pane.h"
#include "AppLogic.h"
#include "Utils.h"
#include <Mmsystem.h>
using namespace winrt::Windows::Foundation;
@@ -44,14 +46,7 @@ Pane::Pane(const Profile& profile, const TermControl& control, const bool lastFo
_connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler });
_warningBellToken = _control.WarningBell({ this, &Pane::_ControlWarningBellHandler });
// On the first Pane's creation, lookup resources we'll use to theme the
// Pane, including the brushed to use for the focused/unfocused border
// color.
if (s_focusedBorderBrush == nullptr || s_unfocusedBorderBrush == nullptr)
{
_SetupResources();
}
_closeTerminalRequestedToken = _control.CloseTerminalRequested({ this, &Pane::_CloseTerminalRequestedHandler });
// Register an event with the control to have it inform us when it gains focus.
_gotFocusRevoker = _control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler });
@@ -993,7 +988,7 @@ void Pane::_ControlConnectionStateChangedHandler(const winrt::Windows::Foundatio
// actually no longer _our_ control, and instead could be a descendant.
//
// When the control's new Pane takes ownership of the control, the new
// parent will register it's own event handler. That event handler will get
// parent will register its own event handler. That event handler will get
// fired after this handler returns, and will properly cleanup state.
if (!_IsLeaf())
{
@@ -1036,6 +1031,26 @@ void Pane::_ControlConnectionStateChangedHandler(const winrt::Windows::Foundatio
}
}
void Pane::_CloseTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*args*/)
{
std::unique_lock lock{ _createCloseLock };
// It's possible that this event handler started being executed, then before
// we got the lock, another thread created another child. So our control is
// actually no longer _our_ control, and instead could be a descendant.
//
// When the control's new Pane takes ownership of the control, the new
// parent will register its own event handler. That event handler will get
// fired after this handler returns, and will properly cleanup state.
if (!_IsLeaf())
{
return;
}
Close();
}
winrt::fire_and_forget Pane::_playBellSound(winrt::Windows::Foundation::Uri uri)
{
auto weakThis{ weak_from_this() };
@@ -1281,7 +1296,7 @@ TermControl Pane::GetTerminalControl()
}
// Method Description:
// - Recursively remove the "Active" state from this Pane and all it's children.
// - Recursively remove the "Active" state from this Pane and all its children.
// - Updates our visuals to match our new state, including highlighting our borders.
// Arguments:
// - <none>
@@ -1586,6 +1601,7 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
// Add our new event handler before revoking the old one.
_connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler });
_warningBellToken = _control.WarningBell({ this, &Pane::_ControlWarningBellHandler });
_closeTerminalRequestedToken = _control.CloseTerminalRequested({ this, &Pane::_CloseTerminalRequestedHandler });
// Revoke the old event handlers. Remove both the handlers for the panes
// themselves closing, and remove their handlers for their controls
@@ -1601,6 +1617,7 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
{
p->_control.ConnectionStateChanged(p->_connectionStateChangedToken);
p->_control.WarningBell(p->_warningBellToken);
p->_control.CloseTerminalRequested(p->_closeTerminalRequestedToken);
}
});
}
@@ -1609,6 +1626,7 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
remainingChild->Closed(remainingChildClosedToken);
remainingChild->_control.ConnectionStateChanged(remainingChild->_connectionStateChangedToken);
remainingChild->_control.WarningBell(remainingChild->_warningBellToken);
remainingChild->_control.CloseTerminalRequested(remainingChild->_closeTerminalRequestedToken);
// If we or either of our children was focused, we want to take that
// focus from them.
@@ -1690,6 +1708,7 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching)
{
p->_control.ConnectionStateChanged(p->_connectionStateChangedToken);
p->_control.WarningBell(p->_warningBellToken);
p->_control.CloseTerminalRequested(p->_closeTerminalRequestedToken);
}
});
}
@@ -2448,6 +2467,8 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect
_connectionStateChangedToken.value = 0;
_control.WarningBell(_warningBellToken);
_warningBellToken.value = 0;
_control.CloseTerminalRequested(_closeTerminalRequestedToken);
_closeTerminalRequestedToken.value = 0;
// Remove our old GotFocus handler from the control. We don't want the
// control telling us that it's now focused, we want it telling its new
@@ -2551,7 +2572,7 @@ void Pane::Maximize(std::shared_ptr<Pane> zoomedPane)
}
// Always recurse into both children. If the (un)zoomed pane was one of
// our direct children, we'll still want to update it's borders.
// our direct children, we'll still want to update its borders.
_firstChild->Maximize(zoomedPane);
_secondChild->Maximize(zoomedPane);
}
@@ -2588,7 +2609,7 @@ void Pane::Restore(std::shared_ptr<Pane> zoomedPane)
}
// Always recurse into both children. If the (un)zoomed pane was one of
// our direct children, we'll still want to update it's borders.
// our direct children, we'll still want to update its borders.
_firstChild->Restore(zoomedPane);
_secondChild->Restore(zoomedPane);
}
@@ -3077,16 +3098,16 @@ float Pane::_ClampSplitPosition(const bool widthOrHeight, const float requestedV
// * The Brush we'll use for inactive Panes - TabViewBackground (to match the
// color of the titlebar)
// Arguments:
// - <none>
// - requestedTheme: this should be the currently active Theme for the app
// Return Value:
// - <none>
void Pane::_SetupResources()
void Pane::SetupResources(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme)
{
const auto res = Application::Current().Resources();
const auto accentColorKey = winrt::box_value(L"SystemAccentColor");
if (res.HasKey(accentColorKey))
{
const auto colorFromResources = res.Lookup(accentColorKey);
const auto colorFromResources = ThemeLookup(res, requestedTheme, accentColorKey);
// If SystemAccentColor is _not_ a Color for some reason, use
// Transparent as the color, so we don't do this process again on
// the next pane (by leaving s_focusedBorderBrush nullptr)
@@ -3104,7 +3125,10 @@ void Pane::_SetupResources()
const auto unfocusedBorderBrushKey = winrt::box_value(L"UnfocusedBorderBrush");
if (res.HasKey(unfocusedBorderBrushKey))
{
auto obj = res.Lookup(unfocusedBorderBrushKey);
// MAKE SURE TO USE ThemeLookup, so that we get the correct resource for
// the requestedTheme, not just the value from the resources (which
// might not respect the settings' requested theme)
auto obj = ThemeLookup(res, requestedTheme, unfocusedBorderBrushKey);
s_unfocusedBorderBrush = obj.try_as<winrt::Windows::UI::Xaml::Media::SolidColorBrush>();
}
else

View File

@@ -136,6 +136,8 @@ public:
bool ContainsReadOnly() const;
static void SetupResources(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme);
// Method Description:
// - A helper method for ad-hoc recursion on a pane tree. Walks the pane
// tree, calling a function on each pane in a depth-first pattern.
@@ -237,6 +239,7 @@ private:
winrt::event_token _firstClosedToken{ 0 };
winrt::event_token _secondClosedToken{ 0 };
winrt::event_token _warningBellToken{ 0 };
winrt::event_token _closeTerminalRequestedToken{ 0 };
winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker;
winrt::Windows::UI::Xaml::UIElement::LostFocus_revoker _lostFocusRevoker;
@@ -290,6 +293,7 @@ private:
const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
void _ControlLostFocusHandler(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
void _CloseTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/);
std::pair<float, float> _CalcChildrenSizes(const float fullSize) const;
SnapChildrenSizeResult _CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const;
@@ -337,8 +341,6 @@ private:
return false;
}
static void _SetupResources();
struct PanePoint
{
float x;

View File

@@ -232,19 +232,19 @@
<value>Warnings were found while parsing your keybindings:</value>
</data>
<data name="TooManyKeysForChord" xml:space="preserve">
<value>&#x2022; Found a keybinding with too many strings for the "keys" array. There should only be one string value in the "keys" array.</value>
<comment>{Locked="\"keys\"","&#x2022;"} This glyph is a bullet, used in a bulleted list.</comment>
<value> Found a keybinding with too many strings for the "keys" array. There should only be one string value in the "keys" array.</value>
<comment>{Locked="\"keys\"",""} This glyph is a bullet, used in a bulleted list.</comment>
</data>
<data name="FailedToParseSubCommands" xml:space="preserve">
<value>&#x2022; Failed to parse all subcommands of nested command.</value>
<value> Failed to parse all subcommands of nested command.</value>
</data>
<data name="MissingRequiredParameter" xml:space="preserve">
<value>&#x2022; Found a keybinding that was missing a required parameter value. This keybinding will be ignored.</value>
<comment>{Locked="&#x2022;"} This glyph is a bullet, used in a bulleted list.</comment>
<value> Found a keybinding that was missing a required parameter value. This keybinding will be ignored.</value>
<comment>{Locked=""} This glyph is a bullet, used in a bulleted list.</comment>
</data>
<data name="UnknownTheme" xml:space="preserve">
<value>&#x2022; The specified "theme" was not found in the list of themes. Temporarily falling back to the default value.</value>
<comment>{Locked="&#x2022;"} This glyph is a bullet, used in a bulleted list.</comment>
<value> The specified "theme" was not found in the list of themes. Temporarily falling back to the default value.</value>
<comment>{Locked=""} This glyph is a bullet, used in a bulleted list.</comment>
</data>
<data name="LegacyGlobalsProperty" xml:space="preserve">
<value>The "globals" property is deprecated - your settings might need updating. </value>
@@ -752,6 +752,10 @@
<value>Suggestions found: {0}</value>
<comment>{0} will be replaced with a number.</comment>
</data>
<data name="DuplicateRemainingProfilesEntry" xml:space="preserve">
<value>The "newTabMenu" field contains more than one entry of type "remainingProfiles". Only the first one will be considered.</value>
<comment>{Locked="newTabMenu"} {Locked="remainingProfiles"}</comment>
</data>
<data name="AboutToolTip" xml:space="preserve">
<value>Open a dialog containing product information</value>
</data>
@@ -788,4 +792,7 @@
<data name="TabCloseToolTip" xml:space="preserve">
<value>Close this tab</value>
</data>
</root>
<data name="NewTabMenuFolderEmpty" xml:space="preserve">
<value>Empty...</value>
</data>
</root>

View File

@@ -356,16 +356,12 @@ namespace winrt::TerminalApp::implementation
Media::SolidColorBrush hoverTabBrush{};
Media::SolidColorBrush subtleFillColorSecondaryBrush;
Media::SolidColorBrush subtleFillColorTertiaryBrush;
// calculate the luminance of the current color and select a font
// color based on that
// see https://www.w3.org/TR/WCAG20/#relativeluminancedef
if (TerminalApp::ColorHelper::IsBrightColor(color))
{
fontBrush.Color(winrt::Windows::UI::Colors::Black());
auto secondaryFontColor = winrt::Windows::UI::Colors::Black();
// For alpha value see: https://github.com/microsoft/microsoft-ui-xaml/blob/7a33ad772d77d908aa6b316ec24e6d2eb3ebf571/dev/CommonStyles/Common_themeresources_any.xaml#L269
secondaryFontColor.A = 0x9E;
secondaryFontBrush.Color(secondaryFontColor);
auto subtleFillColorSecondary = winrt::Windows::UI::Colors::Black();
subtleFillColorSecondary.A = 0x09;
subtleFillColorSecondaryBrush.Color(subtleFillColorSecondary);
@@ -375,11 +371,6 @@ namespace winrt::TerminalApp::implementation
}
else
{
fontBrush.Color(winrt::Windows::UI::Colors::White());
auto secondaryFontColor = winrt::Windows::UI::Colors::White();
// For alpha value see: https://github.com/microsoft/microsoft-ui-xaml/blob/7a33ad772d77d908aa6b316ec24e6d2eb3ebf571/dev/CommonStyles/Common_themeresources_any.xaml#L14
secondaryFontColor.A = 0xC5;
secondaryFontBrush.Color(secondaryFontColor);
auto subtleFillColorSecondary = winrt::Windows::UI::Colors::White();
subtleFillColorSecondary.A = 0x0F;
subtleFillColorSecondaryBrush.Color(subtleFillColorSecondary);
@@ -388,6 +379,25 @@ namespace winrt::TerminalApp::implementation
subtleFillColorTertiaryBrush.Color(subtleFillColorTertiary);
}
// The tab font should be based on the evaluated appearance of the tab color layered on tab row.
const auto layeredTabColor = til::color{ color }.layer_over(_tabRowColor);
if (TerminalApp::ColorHelper::IsBrightColor(layeredTabColor))
{
fontBrush.Color(winrt::Windows::UI::Colors::Black());
auto secondaryFontColor = winrt::Windows::UI::Colors::Black();
// For alpha value see: https://github.com/microsoft/microsoft-ui-xaml/blob/7a33ad772d77d908aa6b316ec24e6d2eb3ebf571/dev/CommonStyles/Common_themeresources_any.xaml#L269
secondaryFontColor.A = 0x9E;
secondaryFontBrush.Color(secondaryFontColor);
}
else
{
fontBrush.Color(winrt::Windows::UI::Colors::White());
auto secondaryFontColor = winrt::Windows::UI::Colors::White();
// For alpha value see: https://github.com/microsoft/microsoft-ui-xaml/blob/7a33ad772d77d908aa6b316ec24e6d2eb3ebf571/dev/CommonStyles/Common_themeresources_any.xaml#L14
secondaryFontColor.A = 0xC5;
secondaryFontBrush.Color(secondaryFontColor);
}
selectedTabBrush.Color(color);
// Start with the current tab color, set to Opacity=.3

View File

@@ -95,17 +95,22 @@ namespace winrt::TerminalApp::implementation
// - Sets up state, event handlers, etc on a tab object that was just made.
// Arguments:
// - newTabImpl: the uninitialized tab.
void TerminalPage::_InitializeTab(winrt::com_ptr<TerminalTab> newTabImpl)
// - insertPosition: Optional parameter to indicate the position of tab.
void TerminalPage::_InitializeTab(winrt::com_ptr<TerminalTab> newTabImpl, uint32_t insertPosition)
{
newTabImpl->Initialize();
uint32_t insertPosition = _tabs.Size();
if (_settings.GlobalSettings().NewTabPosition() == NewTabPosition::AfterCurrentTab)
// If insert position is not passed, calculate it
if (insertPosition == -1)
{
auto currentTabIndex = _GetFocusedTabIndex();
if (currentTabIndex.has_value())
insertPosition = _tabs.Size();
if (_settings.GlobalSettings().NewTabPosition() == NewTabPosition::AfterCurrentTab)
{
insertPosition = currentTabIndex.value() + 1;
auto currentTabIndex = _GetFocusedTabIndex();
if (currentTabIndex.has_value())
{
insertPosition = currentTabIndex.value() + 1;
}
}
}
@@ -189,9 +194,9 @@ namespace winrt::TerminalApp::implementation
if (page && tab)
{
// Passing null args to the ExportBuffer handler will default it
// to prompting for the path
page->_HandleExportBuffer(nullptr, nullptr);
// Passing empty string as the path to export tab will make it
// prompt for the path
page->_ExportTab(*tab, L"");
}
});
@@ -201,7 +206,8 @@ namespace winrt::TerminalApp::implementation
if (page && tab)
{
page->_Find();
page->_SetFocusedTab(*tab);
page->_Find(*tab);
}
});
@@ -259,12 +265,13 @@ namespace winrt::TerminalApp::implementation
// - Create a new tab using a specified pane as the root.
// Arguments:
// - pane: The pane to use as the root.
void TerminalPage::_CreateNewTabFromPane(std::shared_ptr<Pane> pane)
// - insertPosition: Optional parameter to indicate the position of tab.
void TerminalPage::_CreateNewTabFromPane(std::shared_ptr<Pane> pane, uint32_t insertPosition)
{
if (pane)
{
auto newTabImpl = winrt::make_self<TerminalTab>(pane);
_InitializeTab(newTabImpl);
_InitializeTab(newTabImpl, insertPosition);
}
}
@@ -337,7 +344,7 @@ namespace winrt::TerminalApp::implementation
// In the future, it may be preferable to just duplicate the
// current control's live settings (which will include changes
// made through VT).
_CreateNewTabFromPane(_MakePane(nullptr, tab, nullptr));
_CreateNewTabFromPane(_MakePane(nullptr, tab, nullptr), tab.TabViewIndex() + 1);
const auto runtimeTabText{ tab.GetTabText() };
if (!runtimeTabText.empty())

View File

@@ -19,7 +19,6 @@
</PropertyGroup>
<PropertyGroup Label="NuGet Dependencies">
<TerminalCppWinrt>true</TerminalCppWinrt>
<TerminalXamlApplicationToolkit>true</TerminalXamlApplicationToolkit>
<TerminalMUX>true</TerminalMUX>
</PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
@@ -382,6 +381,14 @@
<Private>false</Private>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
</Reference>
<Reference Include="$(WindowsSDK_MetadataPathVersioned)\Windows.UI.Xaml.Hosting.HostingContract\*\*.winmd">
<WinMDFile>true</WinMDFile>
<CopyLocal>false</CopyLocal>
<ReferenceGrouping>$(TargetPlatformMoniker)</ReferenceGrouping>
<ReferenceGroupingDisplayName>$(TargetPlatformDisplayName)</ReferenceGroupingDisplayName>
<ResolvedFrom>CppWinRTImplicitlyExpandTargetPlatform</ResolvedFrom>
<IsSystemReference>True</IsSystemReference>
</Reference>
</ItemGroup>
<!-- ====================== Compiler & Linker Flags ===================== -->
<ItemDefinitionGroup>

View File

@@ -824,79 +824,14 @@ namespace winrt::TerminalApp::implementation
auto newTabFlyout = WUX::Controls::MenuFlyout{};
newTabFlyout.Placement(WUX::Controls::Primitives::FlyoutPlacementMode::BottomEdgeAlignedLeft);
auto actionMap = _settings.ActionMap();
const auto defaultProfileGuid = _settings.GlobalSettings().DefaultProfile();
// the number of profiles should not change in the loop for this to work
const auto profileCount = gsl::narrow_cast<int>(_settings.ActiveProfiles().Size());
for (auto profileIndex = 0; profileIndex < profileCount; profileIndex++)
// Create profile entries from the NewTabMenu configuration using a
// recursive helper function. This returns a std::vector of FlyoutItemBases,
// that we then add to our Flyout.
auto entries = _settings.GlobalSettings().NewTabMenu();
auto items = _CreateNewTabFlyoutItems(entries);
for (const auto& item : items)
{
const auto profile = _settings.ActiveProfiles().GetAt(profileIndex);
auto profileMenuItem = WUX::Controls::MenuFlyoutItem{};
// Add the keyboard shortcuts based on the number of profiles defined
// Look for a keychord that is bound to the equivalent
// NewTab(ProfileIndex=N) action
NewTerminalArgs newTerminalArgs{ profileIndex };
NewTabArgs newTabArgs{ newTerminalArgs };
auto profileKeyChord{ actionMap.GetKeyBindingForAction(ShortcutAction::NewTab, newTabArgs) };
// make sure we find one to display
if (profileKeyChord)
{
_SetAcceleratorForMenuItem(profileMenuItem, profileKeyChord);
}
auto profileName = profile.Name();
profileMenuItem.Text(profileName);
// If there's an icon set for this profile, set it as the icon for
// this flyout item.
if (!profile.Icon().empty())
{
auto icon = IconPathConverter::IconWUX(profile.Icon());
Automation::AutomationProperties::SetAccessibilityView(icon, Automation::Peers::AccessibilityView::Raw);
profileMenuItem.Icon(icon);
}
if (profile.Guid() == defaultProfileGuid)
{
// Contrast the default profile with others in font weight.
profileMenuItem.FontWeight(FontWeights::Bold());
}
auto newTabRun = WUX::Documents::Run();
newTabRun.Text(RS_(L"NewTabRun/Text"));
auto newPaneRun = WUX::Documents::Run();
newPaneRun.Text(RS_(L"NewPaneRun/Text"));
newPaneRun.FontStyle(FontStyle::Italic);
auto newWindowRun = WUX::Documents::Run();
newWindowRun.Text(RS_(L"NewWindowRun/Text"));
newWindowRun.FontStyle(FontStyle::Italic);
auto elevatedRun = WUX::Documents::Run();
elevatedRun.Text(RS_(L"ElevatedRun/Text"));
elevatedRun.FontStyle(FontStyle::Italic);
auto textBlock = WUX::Controls::TextBlock{};
textBlock.Inlines().Append(newTabRun);
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
textBlock.Inlines().Append(newPaneRun);
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
textBlock.Inlines().Append(newWindowRun);
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
textBlock.Inlines().Append(elevatedRun);
auto toolTip = WUX::Controls::ToolTip{};
toolTip.Content(textBlock);
WUX::Controls::ToolTipService::SetToolTip(profileMenuItem, toolTip);
profileMenuItem.Click([profileIndex, weakThis{ get_weak() }](auto&&, auto&&) {
if (auto page{ weakThis.get() })
{
NewTerminalArgs newTerminalArgs{ profileIndex };
page->_OpenNewTerminalViaDropdown(newTerminalArgs);
}
});
newTabFlyout.Items().Append(profileMenuItem);
newTabFlyout.Items().Append(item);
}
// add menu separator
@@ -932,6 +867,7 @@ namespace winrt::TerminalApp::implementation
settingsItem.Click({ this, &TerminalPage::_SettingsButtonOnClick });
newTabFlyout.Items().Append(settingsItem);
auto actionMap = _settings.ActionMap();
const auto settingsKeyChord{ actionMap.GetKeyBindingForAction(ShortcutAction::OpenSettings, OpenSettingsArgs{ SettingsTarget::SettingsUI }) };
if (settingsKeyChord)
{
@@ -991,6 +927,215 @@ namespace winrt::TerminalApp::implementation
_newTabButton.Flyout(newTabFlyout);
}
// Method Description:
// - For a given list of tab menu entries, this method will create the corresponding
// list of flyout items. This is a recursive method that calls itself when it comes
// across a folder entry.
std::vector<WUX::Controls::MenuFlyoutItemBase> TerminalPage::_CreateNewTabFlyoutItems(IVector<NewTabMenuEntry> entries)
{
std::vector<WUX::Controls::MenuFlyoutItemBase> items;
if (entries == nullptr || entries.Size() == 0)
{
return items;
}
for (const auto& entry : entries)
{
if (entry == nullptr)
{
continue;
}
switch (entry.Type())
{
case NewTabMenuEntryType::Separator:
{
items.push_back(WUX::Controls::MenuFlyoutSeparator{});
break;
}
// A folder has a custom name and icon, and has a number of entries that require
// us to call this method recursively.
case NewTabMenuEntryType::Folder:
{
const auto folderEntry = entry.as<FolderEntry>();
const auto folderEntries = folderEntry.Entries();
// If the folder is empty, we should skip the entry if AllowEmpty is false, or
// when the folder should inline.
// The IsEmpty check includes semantics for nested (empty) folders
if (folderEntries.Size() == 0 && (!folderEntry.AllowEmpty() || folderEntry.Inlining() == FolderEntryInlining::Auto))
{
break;
}
// Recursively generate flyout items
auto folderEntryItems = _CreateNewTabFlyoutItems(folderEntries);
// If the folder should auto-inline and there is only one item, do so.
if (folderEntry.Inlining() == FolderEntryInlining::Auto && folderEntries.Size() == 1)
{
for (auto const& folderEntryItem : folderEntryItems)
{
items.push_back(folderEntryItem);
}
break;
}
// Otherwise, create a flyout
auto folderItem = WUX::Controls::MenuFlyoutSubItem{};
folderItem.Text(folderEntry.Name());
auto icon = _CreateNewTabFlyoutIcon(folderEntry.Icon());
folderItem.Icon(icon);
for (const auto& folderEntryItem : folderEntryItems)
{
folderItem.Items().Append(folderEntryItem);
}
// If the folder is empty, and by now we know we set AllowEmpty to true,
// create a placeholder item here
if (folderEntries.Size() == 0)
{
auto placeholder = WUX::Controls::MenuFlyoutItem{};
placeholder.Text(RS_(L"NewTabMenuFolderEmpty"));
placeholder.IsEnabled(false);
folderItem.Items().Append(placeholder);
}
items.push_back(folderItem);
break;
}
// Any "collection entry" will simply make us add each profile in the collection
// separately. This collection is stored as a map <int, Profile>, so the correct
// profile index is already known.
case NewTabMenuEntryType::RemainingProfiles:
case NewTabMenuEntryType::MatchProfiles:
{
const auto remainingProfilesEntry = entry.as<ProfileCollectionEntry>();
if (remainingProfilesEntry.Profiles() == nullptr)
{
break;
}
for (auto&& [profileIndex, remainingProfile] : remainingProfilesEntry.Profiles())
{
items.push_back(_CreateNewTabFlyoutProfile(remainingProfile, profileIndex));
}
break;
}
// A single profile, the profile index is also given in the entry
case NewTabMenuEntryType::Profile:
{
const auto profileEntry = entry.as<ProfileEntry>();
if (profileEntry.Profile() == nullptr)
{
break;
}
auto profileItem = _CreateNewTabFlyoutProfile(profileEntry.Profile(), profileEntry.ProfileIndex());
items.push_back(profileItem);
break;
}
}
}
return items;
}
// Method Description:
// - This method creates a flyout menu item for a given profile with the given index.
// It makes sure to set the correct icon, keybinding, and click-action.
WUX::Controls::MenuFlyoutItem TerminalPage::_CreateNewTabFlyoutProfile(const Profile profile, int profileIndex)
{
auto profileMenuItem = WUX::Controls::MenuFlyoutItem{};
// Add the keyboard shortcuts based on the number of profiles defined
// Look for a keychord that is bound to the equivalent
// NewTab(ProfileIndex=N) action
NewTerminalArgs newTerminalArgs{ profileIndex };
NewTabArgs newTabArgs{ newTerminalArgs };
auto profileKeyChord{ _settings.ActionMap().GetKeyBindingForAction(ShortcutAction::NewTab, newTabArgs) };
// make sure we find one to display
if (profileKeyChord)
{
_SetAcceleratorForMenuItem(profileMenuItem, profileKeyChord);
}
auto profileName = profile.Name();
profileMenuItem.Text(profileName);
// If there's an icon set for this profile, set it as the icon for
// this flyout item
if (!profile.Icon().empty())
{
const auto icon = _CreateNewTabFlyoutIcon(profile.Icon());
profileMenuItem.Icon(icon);
}
if (profile.Guid() == _settings.GlobalSettings().DefaultProfile())
{
// Contrast the default profile with others in font weight.
profileMenuItem.FontWeight(FontWeights::Bold());
}
auto newTabRun = WUX::Documents::Run();
newTabRun.Text(RS_(L"NewTabRun/Text"));
auto newPaneRun = WUX::Documents::Run();
newPaneRun.Text(RS_(L"NewPaneRun/Text"));
newPaneRun.FontStyle(FontStyle::Italic);
auto newWindowRun = WUX::Documents::Run();
newWindowRun.Text(RS_(L"NewWindowRun/Text"));
newWindowRun.FontStyle(FontStyle::Italic);
auto elevatedRun = WUX::Documents::Run();
elevatedRun.Text(RS_(L"ElevatedRun/Text"));
elevatedRun.FontStyle(FontStyle::Italic);
auto textBlock = WUX::Controls::TextBlock{};
textBlock.Inlines().Append(newTabRun);
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
textBlock.Inlines().Append(newPaneRun);
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
textBlock.Inlines().Append(newWindowRun);
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
textBlock.Inlines().Append(elevatedRun);
auto toolTip = WUX::Controls::ToolTip{};
toolTip.Content(textBlock);
WUX::Controls::ToolTipService::SetToolTip(profileMenuItem, toolTip);
profileMenuItem.Click([profileIndex, weakThis{ get_weak() }](auto&&, auto&&) {
if (auto page{ weakThis.get() })
{
NewTerminalArgs newTerminalArgs{ profileIndex };
page->_OpenNewTerminalViaDropdown(newTerminalArgs);
}
});
return profileMenuItem;
}
// Method Description:
// - Helper method to create an IconElement that can be passed to MenuFlyoutItems and
// MenuFlyoutSubItems
IconElement TerminalPage::_CreateNewTabFlyoutIcon(const winrt::hstring& iconSource)
{
if (iconSource.empty())
{
return nullptr;
}
auto icon = IconPathConverter::IconWUX(iconSource);
Automation::AutomationProperties::SetAccessibilityView(icon, Automation::Peers::AccessibilityView::Raw);
return icon;
}
// Function Description:
// Called when the openNewTabDropdown keybinding is used.
// Shows the dropdown flyout.
@@ -1145,7 +1290,7 @@ namespace winrt::TerminalApp::implementation
// 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.
// restored the CWD to its original value.
auto newWorkingDirectory{ settings.StartingDirectory() };
if (newWorkingDirectory.size() == 0 || newWorkingDirectory.size() == 1 &&
!(newWorkingDirectory[0] == L'~' || newWorkingDirectory[0] == L'/'))
@@ -2110,7 +2255,7 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - Place `copiedData` into the clipboard as text. Triggered when a
// terminal control raises it's CopyToClipboard event.
// terminal control raises its CopyToClipboard event.
// Arguments:
// - copiedData: the new string content to place on the clipboard.
winrt::fire_and_forget TerminalPage::_CopyToClipboardHandler(const IInspectable /*sender*/,
@@ -3015,15 +3160,15 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - Called when the user tries to do a search using keybindings.
// This will tell the current focused terminal control to create
// a search box and enable find process.
// This will tell the active terminal control of the passed tab
// to create a search box and enable find process.
// Arguments:
// - <none>
// - tab: the tab where the search box should be created
// Return Value:
// - <none>
void TerminalPage::_Find()
void TerminalPage::_Find(const TerminalTab& tab)
{
if (const auto& control{ _GetActiveControl() })
if (const auto& control{ tab.GetActiveTerminalControl() })
{
control.CreateSearchBoxControl();
}
@@ -4175,6 +4320,22 @@ namespace winrt::TerminalApp::implementation
const auto theme = _settings.GlobalSettings().CurrentTheme();
auto requestedTheme{ theme.RequestedTheme() };
{
// Update the brushes that Pane's use...
Pane::SetupResources(requestedTheme);
// ... then trigger a visual update for all the pane borders to
// apply the new ones.
for (const auto& tab : _tabs)
{
if (auto terminalTab{ _GetTerminalTabImpl(tab) })
{
terminalTab->GetRootPane()->WalkTree([&](auto&& pane) {
pane->UpdateVisuals();
});
}
}
}
const auto res = Application::Current().Resources();
// Use our helper to lookup the theme-aware version of the resource.

View File

@@ -240,9 +240,13 @@ namespace winrt::TerminalApp::implementation
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowLargePasteWarningDialog();
void _CreateNewTabFlyout();
std::vector<winrt::Windows::UI::Xaml::Controls::MenuFlyoutItemBase> _CreateNewTabFlyoutItems(winrt::Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::NewTabMenuEntry> entries);
winrt::Windows::UI::Xaml::Controls::IconElement _CreateNewTabFlyoutIcon(const winrt::hstring& icon);
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _CreateNewTabFlyoutProfile(const Microsoft::Terminal::Settings::Model::Profile profile, int profileIndex);
void _OpenNewTabDropdown();
HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
void _CreateNewTabFromPane(std::shared_ptr<Pane> pane);
void _CreateNewTabFromPane(std::shared_ptr<Pane> pane, uint32_t insertPosition = -1);
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettings settings);
winrt::fire_and_forget _OpenNewWindow(const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs);
@@ -284,7 +288,7 @@ namespace winrt::TerminalApp::implementation
void _RemoveTab(const winrt::TerminalApp::TabBase& tab);
winrt::fire_and_forget _RemoveTabs(const std::vector<winrt::TerminalApp::TabBase> tabs);
void _InitializeTab(winrt::com_ptr<TerminalTab> newTabImpl);
void _InitializeTab(winrt::com_ptr<TerminalTab> newTabImpl, uint32_t insertPosition = -1);
void _RegisterTerminalEvents(Microsoft::Terminal::Control::TermControl term);
void _RegisterTabEvents(TerminalTab& hostingTab);
@@ -384,7 +388,7 @@ namespace winrt::TerminalApp::implementation
void _OnCommandLineExecutionRequested(const IInspectable& sender, const winrt::hstring& commandLine);
void _OnSwitchToTabRequested(const IInspectable& sender, const winrt::TerminalApp::TabBase& tab);
void _Find();
void _Find(const TerminalTab& tab);
winrt::Microsoft::Terminal::Control::TermControl _InitControl(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings,
const winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection& connection);

View File

@@ -82,7 +82,7 @@
<!--
GH#12775 et. al: After switching to ControlsV2, it seems that
delay-loading a dialog causes the ContentDialog to be assigned a
Height equal to it's content size. If we DON'T assign the
Height equal to its content size. If we DON'T assign the
ContentDialog a Row, I believe it's assigned Row 0 by default. So,
when the dialog gets opened, the dialog seemingly causes a giant
hole to appear in the body of the app.

View File

@@ -14,7 +14,6 @@
</PropertyGroup>
<PropertyGroup Label="NuGet Dependencies">
<TerminalCppWinrt>true</TerminalCppWinrt>
<TerminalXamlApplicationToolkit>true</TerminalXamlApplicationToolkit>
<TerminalMUX>true</TerminalMUX>
</PropertyGroup>
<Import Project="..\..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
@@ -71,7 +70,7 @@
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\dll\TerminalControl.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalSettingsEditor\Microsoft.Terminal.Settings.Editor.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalSettingsModel\dll\Microsoft.Terminal.Settings.Model.vcxproj" />
<!-- Reference TerminalAppLib here, so we can use it's TerminalApp.winmd as
<!-- Reference TerminalAppLib here, so we can use its TerminalApp.winmd as
our TerminalApp.winmd. This didn't work correctly in VS2017, you'd need to
manually reference the lib -->
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\TerminalAppLib.vcxproj">

View File

@@ -50,7 +50,6 @@
#include <winrt/Windows.Media.Core.h>
#include <winrt/Windows.Media.Playback.h>
#include <winrt/Microsoft.Toolkit.Win32.UI.XamlHost.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>

View File

@@ -9,7 +9,7 @@ using namespace winrt;
using namespace winrt::Windows::Foundation;
using namespace winrt::Microsoft::Terminal::TerminalConnection;
static til::point GetConsoleScreenSize(HANDLE outputHandle)
static til::size GetConsoleScreenSize(HANDLE outputHandle)
{
CONSOLE_SCREEN_BUFFER_INFOEX csbiex{};
csbiex.cbSize = sizeof(csbiex);
@@ -34,7 +34,7 @@ static ConnectionState RunConnectionToCompletion(const ITerminalConnection& conn
reader.SetWindowSizeChangedCallback([&]() {
const auto size = GetConsoleScreenSize(outputHandle);
connection.Resize(size.Y, size.X);
connection.Resize(size.height, size.width);
});
while (true)
@@ -98,8 +98,8 @@ int wmain(int /*argc*/, wchar_t** /*argv*/)
AzureConnection azureConn{};
winrt::Windows::Foundation::Collections::ValueSet vs{};
vs.Insert(L"initialRows", winrt::Windows::Foundation::PropertyValue::CreateUInt32(gsl::narrow_cast<uint32_t>(size.Y)));
vs.Insert(L"initialCols", winrt::Windows::Foundation::PropertyValue::CreateUInt32(gsl::narrow_cast<uint32_t>(size.X)));
vs.Insert(L"initialRows", winrt::Windows::Foundation::PropertyValue::CreateUInt32(gsl::narrow_cast<uint32_t>(size.height)));
vs.Insert(L"initialCols", winrt::Windows::Foundation::PropertyValue::CreateUInt32(gsl::narrow_cast<uint32_t>(size.width)));
azureConn.Initialize(vs);
const auto state = RunConnectionToCompletion(azureConn, conOut, conIn);

View File

@@ -32,7 +32,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
#pragma warning(push)
#pragma warning(disable : 26490)
// C++/WinRT just loves it's void**, nothing we can do here _except_ reinterpret_cast
// C++/WinRT just loves its void**, nothing we can do here _except_ reinterpret_cast
auto raw = reinterpret_cast<::IInspectable**>(pointer);
#pragma warning(pop)

View File

@@ -86,6 +86,14 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
return _isStateOneOf(ConnectionState::Connected);
}
void _resetConnectionState()
{
{
std::lock_guard<std::mutex> stateLock{ _stateMutex };
_connectionState = ConnectionState::NotConnected;
}
}
private:
std::atomic<ConnectionState> _connectionState{ ConnectionState::NotConnected };
mutable std::mutex _stateMutex;

View File

@@ -2,17 +2,17 @@
// Licensed under the MIT license.
#include "pch.h"
#include "ConptyConnection.h"
#include <conpty-static.h>
#include <winternl.h>
#include "ConptyConnection.g.cpp"
#include "CTerminalHandoff.h"
#include "../../types/inc/utils.hpp"
#include "../../types/inc/Environment.hpp"
#include "LibraryResources.h"
#include "../../types/inc/Environment.hpp"
#include "../../types/inc/utils.hpp"
#include "ConptyConnection.g.cpp"
using namespace ::Microsoft::Console;
using namespace std::string_view_literals;
@@ -24,13 +24,9 @@ static constexpr auto _errorFormat = L"{0} ({0:#010x})"sv;
// There is a number of ways that the Conpty connection can be terminated (voluntarily or not):
// 1. The connection is Close()d
// 2. The pseudoconsole or process cannot be spawned during Start()
// 3. The client process exits with a code.
// (Successful (0) or any other code)
// 4. The read handle is terminated.
// (This usually happens when the pseudoconsole host crashes.)
// 3. The read handle is terminated (when OpenConsole exits)
// In each of these termination scenarios, we need to be mindful of tripping the others.
// Closing the pseudoconsole in response to the client exiting (3) can trigger (4).
// Close() (1) will cause the automatic triggering of (3) and (4).
// Close() (1) will cause the automatic triggering of (3).
// In a lot of cases, we use the connection state to stop "flapping."
//
// To figure out where we handle these, search for comments containing "EXIT POINT"
@@ -205,8 +201,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
const HANDLE hServerProcess,
const HANDLE hClientProcess,
TERMINAL_STARTUP_INFO startupInfo) :
_initialRows{ 25 },
_initialCols{ 80 },
_rows{ 25 },
_cols{ 80 },
_guid{ Utils::CreateGuid() },
_inPipe{ hIn },
_outPipe{ hOut }
@@ -268,8 +264,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
_commandline = winrt::unbox_value_or<winrt::hstring>(settings.TryLookup(L"commandline").try_as<Windows::Foundation::IPropertyValue>(), _commandline);
_startingDirectory = winrt::unbox_value_or<winrt::hstring>(settings.TryLookup(L"startingDirectory").try_as<Windows::Foundation::IPropertyValue>(), _startingDirectory);
_startingTitle = winrt::unbox_value_or<winrt::hstring>(settings.TryLookup(L"startingTitle").try_as<Windows::Foundation::IPropertyValue>(), _startingTitle);
_initialRows = winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialRows").try_as<Windows::Foundation::IPropertyValue>(), _initialRows);
_initialCols = winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialCols").try_as<Windows::Foundation::IPropertyValue>(), _initialCols);
_rows = winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialRows").try_as<Windows::Foundation::IPropertyValue>(), _rows);
_cols = winrt::unbox_value_or<uint32_t>(settings.TryLookup(L"initialCols").try_as<Windows::Foundation::IPropertyValue>(), _cols);
_guid = winrt::unbox_value_or<winrt::guid>(settings.TryLookup(L"guid").try_as<Windows::Foundation::IPropertyValue>(), _guid);
_environment = settings.TryLookup(L"environment").try_as<Windows::Foundation::Collections::ValueSet>();
if constexpr (Feature_VtPassthroughMode::IsEnabled())
@@ -307,9 +303,16 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void ConptyConnection::Start()
try
{
bool usingExistingBuffer = false;
if (_isStateAtOrBeyond(ConnectionState::Closed))
{
_resetConnectionState();
usingExistingBuffer = true;
}
_transitionToState(ConnectionState::Connecting);
const til::size dimensions{ gsl::narrow<til::CoordType>(_initialCols), gsl::narrow<til::CoordType>(_initialRows) };
const til::size dimensions{ gsl::narrow<til::CoordType>(_cols), gsl::narrow<til::CoordType>(_rows) };
// If we do not have pipes already, then this is a fresh connection... not an inbound one that is a received
// handoff from an already-started PTY process.
@@ -317,6 +320,16 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
DWORD flags = PSEUDOCONSOLE_RESIZE_QUIRK | PSEUDOCONSOLE_WIN32_INPUT_MODE;
// If we're using an existing buffer, we want the new connection
// to reuse the existing cursor. When not setting this flag, the
// PseudoConsole sends a clear screen VT code which our renderer
// interprets into making all the previous lines be outside the
// current viewport.
if (usingExistingBuffer)
{
flags |= PSEUDOCONSOLE_INHERIT_CURSOR;
}
if constexpr (Feature_VtPassthroughMode::IsEnabled())
{
if (_passthroughMode)
@@ -363,6 +376,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
}
}
THROW_IF_FAILED(ConptyReleasePseudoConsole(_hPC.get()));
_startTime = std::chrono::high_resolution_clock::now();
// Create our own output handling thread
@@ -387,19 +402,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
LOG_IF_FAILED(SetThreadDescription(_hOutputThread.get(), L"ConptyConnection Output Thread"));
_clientExitWait.reset(CreateThreadpoolWait(
[](PTP_CALLBACK_INSTANCE /*callbackInstance*/, PVOID context, PTP_WAIT /*wait*/, TP_WAIT_RESULT /*waitResult*/) noexcept {
const auto pInstance = static_cast<ConptyConnection*>(context);
if (pInstance)
{
pInstance->_ClientTerminated();
}
},
this,
nullptr));
SetThreadpoolWait(_clientExitWait.get(), _piClient.hProcess, nullptr);
_transitionToState(ConnectionState::Connected);
}
catch (...)
@@ -440,40 +442,26 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
winrt::hstring exitText{ fmt::format(std::wstring_view{ RS_(L"ProcessExited") }, fmt::format(_errorFormat, status)) };
_TerminalOutputHandlers(L"\r\n");
_TerminalOutputHandlers(exitText);
_TerminalOutputHandlers(L"\r\n");
_TerminalOutputHandlers(RS_(L"CtrlDToClose"));
_TerminalOutputHandlers(L"\r\n");
}
CATCH_LOG();
}
// Method Description:
// - called when the client application (not necessarily its pty) exits for any reason
void ConptyConnection::_ClientTerminated() noexcept
void ConptyConnection::_LastConPtyClientDisconnected() noexcept
try
{
if (_isStateAtOrBeyond(ConnectionState::Closing))
{
// This termination was expected.
return;
}
// EXIT POINT
DWORD exitCode{ 0 };
GetExitCodeProcess(_piClient.hProcess, &exitCode);
// Signal the closing or failure of the process.
// Load bearing. Terminating the pseudoconsole will make the output thread exit unexpectedly,
// so we need to signal entry into the correct closing state before we do that.
_transitionToState(exitCode == 0 ? ConnectionState::Closed : ConnectionState::Failed);
// Close the pseudoconsole and wait for all output to drain.
_hPC.reset();
if (auto localOutputThreadHandle = std::move(_hOutputThread))
{
LOG_LAST_ERROR_IF(WAIT_FAILED == WaitForSingleObject(localOutputThreadHandle.get(), INFINITE));
}
// exitCode might be STILL_ACTIVE if a client has called FreeConsole() and
// thus caused the tab to close, even though the CLI app is still running.
_transitionToState(exitCode == 0 || exitCode == STILL_ACTIVE ? ConnectionState::Closed : ConnectionState::Failed);
_indicateExitWithStatus(exitCode);
_piClient.reset();
}
CATCH_LOG()
@@ -492,15 +480,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void ConptyConnection::Resize(uint32_t rows, uint32_t columns)
{
// If we haven't started connecting at all, it's still fair to update
// the initial rows and columns before we set things up.
if (!_isStateAtOrBeyond(ConnectionState::Connecting))
{
_initialRows = rows;
_initialCols = columns;
}
// Otherwise, we can really only dispatch a resize if we're already connected.
else if (_isConnected())
// Always keep these in case we ever want to disconnect/restart
_rows = rows;
_cols = columns;
if (_isConnected())
{
THROW_IF_FAILED(ConptyResizePseudoConsole(_hPC.get(), { Utils::ClampToShortMax(columns, 1), Utils::ClampToShortMax(rows, 1) }));
}
@@ -548,39 +532,46 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void ConptyConnection::Close() noexcept
try
{
if (_transitionToState(ConnectionState::Closing))
_transitionToState(ConnectionState::Closing);
// .reset()ing either of these two will signal ConPTY to send out a CTRL_CLOSE_EVENT to all attached clients.
// FYI: The other members of this class are concurrently read by the _hOutputThread
// thread running in the background and so they're not safe to be .reset().
_hPC.reset();
_inPipe.reset();
if (_hOutputThread)
{
// EXIT POINT
// _clientExitWait holds a CreateThreadpoolWait() which holds a weak reference to "this".
// This manual reset() ensures we wait for it to be teared down via WaitForThreadpoolWaitCallbacks().
_clientExitWait.reset();
_hPC.reset(); // tear down the pseudoconsole (this is like clicking X on a console window)
// CloseHandle() on pipes blocks until any current WriteFile()/ReadFile() has returned.
// CancelSynchronousIo prevents us from deadlocking ourselves.
// At this point in Close(), _inPipe won't be used anymore since the UI parts are torn down.
// _outPipe is probably still stuck in ReadFile() and might currently be written to.
if (_hOutputThread)
// Loop around `CancelSynchronousIo()` just in case the signal to shut down was missed.
// This may happen if we called `CancelSynchronousIo()` while not being stuck
// in `ReadFile()` and if OpenConsole refuses to exit in a timely manner.
for (;;)
{
// ConptyConnection::Close() blocks the UI thread, because `_TerminalOutputHandlers` might indirectly
// reference UI objects like `ControlCore`. CancelSynchronousIo() allows us to have the background
// thread exit as fast as possible by aborting any ongoing writes coming from OpenConsole.
CancelSynchronousIo(_hOutputThread.get());
}
_inPipe.reset(); // break the pipes
_outPipe.reset();
if (_hOutputThread)
{
// Waiting for the output thread to exit ensures that all pending _TerminalOutputHandlers()
// calls have returned and won't notify our caller (ControlCore) anymore. This ensures that
// we don't call a destroyed event handler asynchronously from a background thread (GH#13880).
LOG_LAST_ERROR_IF(WAIT_FAILED == WaitForSingleObject(_hOutputThread.get(), INFINITE));
_hOutputThread.reset();
}
const auto result = WaitForSingleObject(_hOutputThread.get(), 1000);
if (result == WAIT_OBJECT_0)
{
break;
}
_transitionToState(ConnectionState::Closed);
LOG_LAST_ERROR();
}
}
// Now that the background thread is done, we can safely clean up the other system objects, without
// race conditions, or fear of deadlocking ourselves (e.g. by calling CloseHandle() on _outPipe).
_outPipe.reset();
_hOutputThread.reset();
_piClient.reset();
_transitionToState(ConnectionState::Closed);
}
CATCH_LOG()
@@ -637,15 +628,19 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
if (readFail) // reading failed (we must check this first, because read will also be 0.)
{
// EXIT POINT
const auto lastError = GetLastError();
if (lastError != ERROR_BROKEN_PIPE)
if (lastError == ERROR_BROKEN_PIPE)
{
_LastConPtyClientDisconnected();
return S_OK;
}
else
{
// EXIT POINT
_indicateExitWithStatus(HRESULT_FROM_WIN32(lastError)); // print a message
_transitionToState(ConnectionState::Failed);
return gsl::narrow_cast<DWORD>(HRESULT_FROM_WIN32(lastError));
}
// else we call convertUTF8ChunkToUTF16 with an empty string_view to convert possible remaining partials to U+FFFD
}
const auto result{ til::u8u16(std::string_view{ _buffer.data(), read }, _u16Str, _u8State) };
@@ -690,6 +685,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
winrt::event_token ConptyConnection::NewConnection(const NewConnectionHandler& handler) { return _newConnectionHandlers.add(handler); };
void ConptyConnection::NewConnection(const winrt::event_token& token) { _newConnectionHandlers.remove(token); };
void ConptyConnection::closePseudoConsoleAsync(HPCON hPC) noexcept
{
::ConptyClosePseudoConsoleTimeout(hPC, 0);
}
HRESULT ConptyConnection::NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client, TERMINAL_STARTUP_INFO startupInfo) noexcept
try
{

View File

@@ -6,16 +6,8 @@
#include "ConptyConnection.g.h"
#include "ConnectionStateHolder.h"
#include <conpty-static.h>
#include "ITerminalHandoff.h"
namespace wil
{
// These belong in WIL upstream, so when we reingest the change that has them we'll get rid of ours.
using unique_static_pseudoconsole_handle = wil::unique_any<HPCON, decltype(&::ConptyClosePseudoConsole), ::ConptyClosePseudoConsoleNoWait>;
}
namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
struct ConptyConnection : ConptyConnectionT<ConptyConnection>, ConnectionStateHolder<ConptyConnection>
@@ -65,15 +57,16 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler);
private:
static void closePseudoConsoleAsync(HPCON hPC) noexcept;
static HRESULT NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client, TERMINAL_STARTUP_INFO startupInfo) noexcept;
static winrt::hstring _commandlineFromProcess(HANDLE process);
HRESULT _LaunchAttachedClient() noexcept;
void _indicateExitWithStatus(unsigned int status) noexcept;
void _ClientTerminated() noexcept;
void _LastConPtyClientDisconnected() noexcept;
til::CoordType _initialRows{};
til::CoordType _initialCols{};
til::CoordType _rows{};
til::CoordType _cols{};
uint64_t _initialParentHwnd{ 0 };
hstring _commandline{};
hstring _startingDirectory{};
@@ -90,8 +83,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
wil::unique_hfile _outPipe; // The pipe for reading output from
wil::unique_handle _hOutputThread;
wil::unique_process_information _piClient;
wil::unique_static_pseudoconsole_handle _hPC;
wil::unique_threadpool_wait _clientExitWait;
wil::unique_any<HPCON, decltype(closePseudoConsoleAsync), closePseudoConsoleAsync> _hPC;
til::u8state _u8State{};
std::wstring _u16Str{};

View File

@@ -207,6 +207,10 @@
<value>[process exited with code {0}]</value>
<comment>The first argument {0} is the error code of the process. When there is no error, the number ZERO will be displayed. </comment>
</data>
<data name="CtrlDToClose" xml:space="preserve">
<value>You can now close this terminal with Ctrl+D, or press Enter to restart.</value>
<comment>"Ctrl+D" and "Enter" represent keys the user will press (control+D and Enter).</comment>
</data>
<data name="ProcessFailedToLaunch" xml:space="preserve">
<value>[error {0} when launching `{1}']</value>
<comment>The first argument {0} is the error code. The second argument {1} is the user-specified path to a program.

View File

@@ -63,25 +63,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return result;
}
// Helper static function to ensure that all ambiguous-width glyphs are reported as narrow.
// See microsoft/terminal#2066 for more info.
static bool _IsGlyphWideForceNarrowFallback(const std::wstring_view /* glyph */)
{
return false; // glyph is not wide.
}
static bool _EnsureStaticInitialization()
{
// use C++11 magic statics to make sure we only do this once.
static auto initialized = []() {
// *** THIS IS A SINGLETON ***
SetGlyphWidthFallback(_IsGlyphWideForceNarrowFallback);
return true;
}();
return initialized;
}
TextColor SelectionColor::AsTextColor() const noexcept
{
if (_IsIndex16)
@@ -101,8 +82,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_desiredFont{ DEFAULT_FONT_FACE, 0, DEFAULT_FONT_WEIGHT, DEFAULT_FONT_SIZE, CP_UTF8 },
_actualFont{ DEFAULT_FONT_FACE, 0, DEFAULT_FONT_WEIGHT, { 0, DEFAULT_FONT_SIZE }, CP_UTF8, false }
{
_EnsureStaticInitialization();
_settings = winrt::make_self<implementation::ControlSettings>(settings, unfocusedAppearance);
_terminal = std::make_shared<::Microsoft::Terminal::Core::Terminal>();
@@ -402,6 +381,25 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const WORD scanCode,
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers)
{
const wchar_t CtrlD = 0x4;
const wchar_t Enter = '\r';
if (_connection.State() >= winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::Closed)
{
if (ch == CtrlD)
{
_CloseTerminalRequestedHandlers(*this, nullptr);
return true;
}
if (ch == Enter)
{
_connection.Close();
_connection.Start();
return true;
}
}
if (ch == L'\x3') // Ctrl+C or Ctrl+Break
{
_handleControlC();
@@ -603,13 +601,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Update our runtime opacity value
_runtimeOpacity = newOpacity;
// GH#11285 - If the user is on Windows 10, and they changed the
// transparency of the control s.t. it should be partially opaque, then
// opt them in to acrylic. It's the only way to have transparency on
// Windows 10.
// We'll also turn the acrylic back off when they're fully opaque, which
// is what the Terminal did prior to 1.12.
_runtimeUseAcrylic = newOpacity < 1.0 && (!IsVintageOpacityAvailable() || _settings->UseAcrylic());
// Manually turn off acrylic if they turn off transparency.
_runtimeUseAcrylic = newOpacity < 1.0 && _settings->UseAcrylic();
// Update the renderer as well. It might need to fall back from
// cleartype -> grayscale if the BG is transparent / acrylic.
@@ -735,13 +728,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
auto lock = _terminal->LockForWriting();
// GH#11285 - If the user is on Windows 10, and they wanted opacity, but
// didn't explicitly request acrylic, then opt them in to acrylic.
// On Windows 11+, this isn't needed, because we can have vintage opacity.
// Instead, disable acrylic while the opacity is 100%
_runtimeUseAcrylic = _settings->Opacity() < 1.0 && (!IsVintageOpacityAvailable() || _settings->UseAcrylic());
_runtimeOpacity = std::nullopt;
// Manually turn off acrylic if they turn off transparency.
_runtimeUseAcrylic = _settings->Opacity() < 1.0 && _settings->UseAcrylic();
const auto sizeChanged = _setFontSizeUnderLock(_settings->FontSize());
// Update the terminal core with its new Core settings
@@ -870,7 +861,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
const auto actualNewSize = _actualFont.GetSize();
_FontSizeChangedHandlers(actualNewSize.X, actualNewSize.Y, initialUpdate);
_FontSizeChangedHandlers(actualNewSize.width, actualNewSize.height, initialUpdate);
}
// Method Description:
@@ -948,8 +939,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Don't actually resize so small that a single character wouldn't fit
// in either dimension. The buffer really doesn't like being size 0.
cx = std::max(cx, _actualFont.GetSize().X);
cy = std::max(cy, _actualFont.GetSize().Y);
cx = std::max(cx, _actualFont.GetSize().width);
cy = std::max(cy, _actualFont.GetSize().height);
// Convert our new dimensions to characters
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, { cx, cy });
@@ -1029,10 +1020,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Control::SelectionData info;
const auto start{ _terminal->SelectionStartForRendering() };
info.StartPos = { start.X, start.Y };
info.StartPos = { start.x, start.y };
const auto end{ _terminal->SelectionEndForRendering() };
info.EndPos = { end.X, end.Y };
info.EndPos = { end.x, end.y };
info.Endpoint = static_cast<SelectionEndpointTarget>(_terminal->SelectionEndpointTarget());
@@ -1109,7 +1100,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// content, which is unexpected.
const auto htmlData = formats == nullptr || WI_IsFlagSet(formats.Value(), CopyFormat::HTML) ?
TextBuffer::GenHTML(bufferData,
_actualFont.GetUnscaledSize().Y,
_actualFont.GetUnscaledSize().width,
_actualFont.GetFaceName(),
bgColor) :
"";
@@ -1117,7 +1108,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// convert to RTF format
const auto rtfData = formats == nullptr || WI_IsFlagSet(formats.Value(), CopyFormat::RTF) ?
TextBuffer::GenRTF(bufferData,
_actualFont.GetUnscaledSize().Y,
_actualFont.GetUnscaledSize().height,
_actualFont.GetFaceName(),
bgColor) :
"";
@@ -1214,8 +1205,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
const auto fontSize = _actualFont.GetSize();
return {
::base::saturated_cast<float>(fontSize.X),
::base::saturated_cast<float>(fontSize.Y)
::base::saturated_cast<float>(fontSize.width),
::base::saturated_cast<float>(fontSize.height)
};
}
winrt::hstring ControlCore::FontFaceName() const noexcept
@@ -1443,7 +1434,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return result;
}
::Microsoft::Console::Types::IUiaData* ControlCore::GetUiaData() const
::Microsoft::Console::Render::IRenderData* ControlCore::GetRenderData() const
{
return _terminal.get();
}
@@ -1474,7 +1465,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Search::Sensitivity::CaseSensitive :
Search::Sensitivity::CaseInsensitive;
::Search search(*GetUiaData(), text.c_str(), direction, sensitivity);
::Search search(*GetRenderData(), text.c_str(), direction, sensitivity);
auto lock = _terminal->LockForWriting();
const auto foundMatch{ search.FindNext() };
if (foundMatch)
@@ -1735,7 +1726,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const auto& textBuffer = _terminal->GetTextBuffer();
std::wstring str;
const auto lastRow = textBuffer.GetLastNonSpaceCharacter().Y;
const auto lastRow = textBuffer.GetLastNonSpaceCharacter().y;
for (auto rowIndex = 0; rowIndex <= lastRow; rowIndex++)
{
const auto& row = textBuffer.GetRowByOffset(rowIndex);
@@ -1755,22 +1746,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return hstring{ str };
}
// Helper to check if we're on Windows 11 or not. This is used to check if
// we need to use acrylic to achieve transparency, because vintage opacity
// doesn't work in islands on win10.
// Remove when we can remove the rest of GH#11285
bool ControlCore::IsVintageOpacityAvailable() noexcept
{
OSVERSIONINFOEXW osver{};
osver.dwOSVersionInfoSize = sizeof(osver);
osver.dwBuildNumber = 22000;
DWORDLONG dwlConditionMask = 0;
VER_SET_CONDITION(dwlConditionMask, VER_BUILDNUMBER, VER_GREATER_EQUAL);
return VerifyVersionInfoW(&osver, VER_BUILDNUMBER, dwlConditionMask) != FALSE;
}
Core::Scheme ControlCore::ColorScheme() const noexcept
{
Core::Scheme s;
@@ -1792,7 +1767,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// switched to an unfocused appearance.
//
// IF WE DON'T HAVE AN UNFOCUSED APPEARANCE: then just ask the Terminal
// for it's current color table. That way, we can restore those colors
// for its current color table. That way, we can restore those colors
// back.
if (HasUnfocusedAppearance())
{
@@ -2020,7 +1995,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// The version of this that only accepts a ScrollMark will automatically
// set the start & end to the cursor position.
_terminal->AddMark(m, m.start, m.end);
_terminal->AddMark(m, m.start, m.end, true);
}
void ControlCore::ClearMark() { _terminal->ClearMark(); }
void ControlCore::ClearAllMarks() { _terminal->ClearAllMarks(); }

View File

@@ -113,7 +113,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
winrt::hstring HoveredUriText() const;
Windows::Foundation::IReference<Core::Point> HoveredCell() const;
::Microsoft::Console::Types::IUiaData* GetUiaData() const;
::Microsoft::Console::Render::IRenderData* GetRenderData() const;
void ColorSelection(const Control::SelectionColor& fg, const Control::SelectionColor& bg, Core::MatchMode matchMode);
@@ -232,6 +232,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TYPED_EVENT(ShowWindowChanged, IInspectable, Control::ShowWindowArgs);
TYPED_EVENT(UpdateSelectionMarkers, IInspectable, Control::UpdateSelectionMarkersEventArgs);
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
TYPED_EVENT(CloseTerminalRequested, IInspectable, IInspectable);
// clang-format on
private:

View File

@@ -161,5 +161,6 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, ShowWindowArgs> ShowWindowChanged;
event Windows.Foundation.TypedEventHandler<Object, UpdateSelectionMarkersEventArgs> UpdateSelectionMarkers;
event Windows.Foundation.TypedEventHandler<Object, OpenHyperlinkEventArgs> OpenHyperlink;
event Windows.Foundation.TypedEventHandler<Object, Object> CloseTerminalRequested;
};
}

View File

@@ -660,9 +660,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return nullptr;
}
::Microsoft::Console::Types::IUiaData* ControlInteractivity::GetUiaData() const
::Microsoft::Console::Render::IRenderData* ControlInteractivity::GetRenderData() const
{
return _core->GetUiaData();
return _core->GetRenderData();
}
// Method Description:

View File

@@ -45,7 +45,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Control::ControlCore Core();
Control::InteractivityAutomationPeer OnCreateAutomationPeer();
::Microsoft::Console::Types::IUiaData* GetUiaData() const;
::Microsoft::Console::Render::IRenderData* GetRenderData() const;
#pragma region Input Methods
void PointerPressed(Control::MouseButtonState buttonState,

View File

@@ -32,7 +32,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
InteractivityAutomationPeer::InteractivityAutomationPeer(Control::implementation::ControlInteractivity* owner) :
_interactivity{ owner }
{
THROW_IF_FAILED(::Microsoft::WRL::MakeAndInitialize<::Microsoft::Terminal::TermControlUiaProvider>(&_uiaProvider, _interactivity->GetUiaData(), this));
THROW_IF_FAILED(::Microsoft::WRL::MakeAndInitialize<::Microsoft::Terminal::TermControlUiaProvider>(&_uiaProvider, _interactivity->GetRenderData(), this));
};
void InteractivityAutomationPeer::SetControlBounds(const Windows::Foundation::Rect bounds)
@@ -176,7 +176,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void InteractivityAutomationPeer::ChangeViewport(const til::inclusive_rect& NewWindow)
{
_interactivity->UpdateScrollbar(NewWindow.Top);
_interactivity->UpdateScrollbar(NewWindow.top);
}
#pragma endregion

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@@ -134,7 +134,7 @@
<comment>The tooltip text for the search forward button.</comment>
</data>
<data name="SearchBox_TextBox.PlaceholderText" xml:space="preserve">
<value>Find...</value>
<value>Find</value>
<comment>The placeholder text in the search box control.</comment>
</data>
<data name="DragFileCaption" xml:space="preserve">
@@ -158,8 +158,8 @@
<comment>The name of the search backward button for accessibility.</comment>
</data>
<data name="SearchBox_TextBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Search Text</value>
<comment>The name of the text box on the search box control for accessibility.</comment>
<value>Find</value>
<comment>The name of the text box on the search box control for accessibility. This should match "SearchBox_TextBox.PlaceholderText"</comment>
</data>
<data name="TerminalControl_ControlType" xml:space="preserve">
<value>terminal</value>
@@ -208,4 +208,4 @@ Please either install the missing font or choose another one.</value>
<value>No results found</value>
<comment>Announced to a screen reader when the user searches for some text and there are no matches for that text in the terminal.</comment>
</data>
</root>
</root>

View File

@@ -25,6 +25,7 @@ using namespace winrt::Windows::UI::ViewManagement;
using namespace winrt::Windows::UI::Input;
using namespace winrt::Windows::System;
using namespace winrt::Windows::ApplicationModel::DataTransfer;
using namespace winrt::Windows::Storage::Streams;
// The minimum delay between updates to the scroll bar's values.
// The updates are throttled to limit power usage.
@@ -630,7 +631,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
//
// Firing it manually makes sure it does.
_BackgroundBrush = RootGrid().Background();
_PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"BackgroundBrush" });
_PropertyChangedHandlers(*this, Data::PropertyChangedEventArgs{ L"BackgroundBrush" });
_isBackgroundLight = _isColorLight(bg);
}
@@ -652,7 +653,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
const auto opacity{ _core.Opacity() };
const auto useAcrylic{ _core.UseAcrylic() };
auto changed = false;
// GH#11743, #11619: If we're changing whether or not acrylic is used,
// then just entirely reinitialize the brush. The primary way that this
// happens is on Windows 10, where we need to enable acrylic when the
@@ -666,6 +667,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_InitializeBackgroundBrush();
return;
}
changed = acrylic.TintOpacity() != opacity;
acrylic.TintOpacity(opacity);
}
else if (auto solidColor = RootGrid().Background().try_as<Media::SolidColorBrush>())
@@ -675,8 +677,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_InitializeBackgroundBrush();
return;
}
changed = solidColor.Opacity() != opacity;
solidColor.Opacity(opacity);
}
// Send a BG brush changed event, so you can mouse wheel the
// transparency of the titlebar too.
if (changed)
{
_PropertyChangedHandlers(*this, Data::PropertyChangedEventArgs{ L"BackgroundBrush" });
}
}
TermControl::~TermControl()
@@ -1793,7 +1802,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// when the control is visible as the DPI changes.
// - The CompositionScale will be the new DPI. This happens when the
// control wasn't focused as the window's DPI changed, so it only got
// these messages after XAML updated it's scaling.
// these messages after XAML updated its scaling.
// - 3. Finally, a CompositionScaleChanged with the _new_ DPI.
// - 4. We'll usually get another SizeChanged some time after this last
// ScaleChanged. This usually seems to happen after something triggers
@@ -1981,6 +1990,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
if (!_IsClosing())
{
_closing = true;
if (_automationPeer)
{
auto autoPeerImpl{ winrt::get_self<implementation::TermControlAutomationPeer>(_automationPeer) };
autoPeerImpl->Close();
}
_RestorePointerCursorHandlers(*this, nullptr);
@@ -2124,7 +2138,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// ComCtl scrollbars, but it's certainly close enough.
const auto scrollbarSize = GetSystemMetricsForDpi(SM_CXVSCROLL, dpi);
double width = cols * actualFontSize.X;
double width = cols * actualFontSize.width;
// Reserve additional space if scrollbar is intended to be visible
if (scrollState != ScrollbarState::Hidden)
@@ -2132,7 +2146,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
width += scrollbarSize;
}
double height = rows * actualFontSize.Y;
double height = rows * actualFontSize.height;
const auto thickness = ParseThicknessFromPadding(padding);
// GH#2061 - make sure to account for the size the padding _will be_ scaled to
width += scale * (thickness.Left + thickness.Right);
@@ -2186,7 +2200,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
else
{
// If the terminal hasn't been initialized yet, then the font size will
// have dimensions {1, fontSize.Y}, which can mess with consumers of
// have dimensions {1, fontSize.height}, which can mess with consumers of
// this method. In that case, we'll need to pre-calculate the font
// width, before we actually have a renderer or swapchain.
const winrt::Windows::Foundation::Size minSize{ 1, 1 };
@@ -2518,21 +2532,62 @@ namespace winrt::Microsoft::Terminal::Control::implementation
if (items.Size() > 0)
{
std::wstring allPaths;
for (auto item : items)
std::vector<std::wstring> fullPaths;
// GH#14628: Workaround for GetStorageItemsAsync() only returning 16 items
// at most when dragging and dropping from archives (zip, 7z, rar, etc.)
if (items.Size() == 16 && e.DataView().Contains(winrt::hstring{ L"FileDrop" }))
{
auto fileDropData = co_await e.DataView().GetDataAsync(winrt::hstring{ L"FileDrop" });
if (fileDropData != nullptr)
{
auto stream = fileDropData.as<IRandomAccessStream>();
stream.Seek(0);
const uint32_t streamSize = gsl::narrow_cast<uint32_t>(stream.Size());
const Buffer buf(streamSize);
const auto buffer = co_await stream.ReadAsync(buf, streamSize, InputStreamOptions::None);
const HGLOBAL hGlobal = buffer.data();
const auto count = DragQueryFileW(static_cast<HDROP>(hGlobal), 0xFFFFFFFF, nullptr, 0);
fullPaths.reserve(count);
for (unsigned int i = 0; i < count; i++)
{
std::wstring path;
path.resize(wil::max_path_length);
const auto charsCopied = DragQueryFileW(static_cast<HDROP>(hGlobal), i, path.data(), wil::max_path_length);
if (charsCopied > 0)
{
path.resize(charsCopied);
fullPaths.emplace_back(std::move(path));
}
}
}
}
else
{
fullPaths.reserve(items.Size());
for (const auto& item : items)
{
fullPaths.emplace_back(item.Path());
}
}
std::wstring allPathsString;
for (auto& fullPath : fullPaths)
{
// Join the paths with spaces
if (!allPaths.empty())
if (!allPathsString.empty())
{
allPaths += L" ";
allPathsString += L" ";
}
std::wstring fullPath{ item.Path() };
// Fix path for WSL
// In the fullness of time, we should likely plumb this up
// to the TerminalApp layer, and have it make the decision
// if this control should have it's path mangled (and do the
// if this control should have its path mangled (and do the
// mangling), rather than exposing the source concept to the
// Control layer.
//
@@ -2583,10 +2638,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
fullPath += L"\"";
}
allPaths += fullPath;
allPathsString += fullPath;
}
_core.PasteText(winrt::hstring{ allPaths });
_core.PasteText(winrt::hstring{ allPathsString });
}
}
}

View File

@@ -148,6 +148,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
PROJECTED_FORWARDED_TYPED_EVENT(SetTaskbarProgress, IInspectable, IInspectable, _core, TaskbarProgressChanged);
PROJECTED_FORWARDED_TYPED_EVENT(ConnectionStateChanged, IInspectable, IInspectable, _core, ConnectionStateChanged);
PROJECTED_FORWARDED_TYPED_EVENT(ShowWindowChanged, IInspectable, Control::ShowWindowArgs, _core, ShowWindowChanged);
PROJECTED_FORWARDED_TYPED_EVENT(CloseTerminalRequested, IInspectable, IInspectable, _core, CloseTerminalRequested);
PROJECTED_FORWARDED_TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs, _interactivity, PasteFromClipboard);

View File

@@ -52,6 +52,8 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, ShowWindowArgs> ShowWindowChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> CloseTerminalRequested;
Boolean CopySelectionToClipboard(Boolean singleLine, Windows.Foundation.IReference<CopyFormat> formats);
void PasteTextFromClipboard();
void SelectAll();

View File

@@ -112,11 +112,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
if (const auto keyEventChar{ gsl::narrow_cast<wchar_t>(charCode) }; IsReadable({ &keyEventChar, 1 }))
{
_keyEvents.emplace_back(keyEventChar);
_keyEvents.lock()->emplace_back(keyEventChar);
}
}
}
void TermControlAutomationPeer::Close()
{
// GH#13978: If the TermControl has already been removed from the UI tree, XAML might run into weird bugs.
// This will prevent the `dispatcher.RunAsync` calls below from raising UIA events on the main thread.
_termControl = {};
}
// Method Description:
// - Signals the ui automation client that the terminal's selection has changed and should be updated
// Arguments:
@@ -202,27 +209,30 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TermControlAutomationPeer::NotifyNewOutput(std::wstring_view newOutput)
{
// Try to suppress any events (or event data)
// that is just the keypress the user made
auto sanitized{ Sanitize(newOutput) };
while (!_keyEvents.empty() && IsReadable(sanitized))
// Try to suppress any events (or event data)
// that are just the keypresses the user made
{
if (til::toupper_ascii(sanitized.front()) == _keyEvents.front())
auto keyEvents = _keyEvents.lock();
while (!keyEvents->empty() && IsReadable(sanitized))
{
// the key event's character (i.e. the "A" key) matches
// the output character (i.e. "a" or "A" text).
// We can assume that the output character resulted from
// the pressed key, so we can ignore it.
sanitized = sanitized.substr(1);
_keyEvents.pop_front();
}
else
{
// The output doesn't match,
// so clear the input stack and
// move on to fire the event.
_keyEvents.clear();
break;
if (til::toupper_ascii(sanitized.front()) == keyEvents->front())
{
// the key event's character (i.e. the "A" key) matches
// the output character (i.e. "a" or "A" text).
// We can assume that the output character resulted from
// the pressed key, so we can ignore it.
sanitized = sanitized.substr(1);
keyEvents->pop_front();
}
else
{
// The output doesn't match,
// so clear the input stack and
// move on to fire the event.
keyEvents->clear();
break;
}
}
}

View File

@@ -49,6 +49,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void UpdateControlBounds();
void SetControlPadding(const Core::Padding padding);
void RecordKeyEvent(const WORD vkey);
void Close();
#pragma region FrameworkElementAutomationPeer
hstring GetClassNameCore() const;
@@ -80,6 +81,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
private:
winrt::weak_ref<Microsoft::Terminal::Control::implementation::TermControl> _termControl;
Control::InteractivityAutomationPeer _contentAutomationPeer;
std::deque<wchar_t> _keyEvents;
til::shared_mutex<std::deque<wchar_t>> _keyEvents;
};
}

View File

@@ -57,7 +57,7 @@
</ItemGroup>
<!-- ========================= Project References ======================== -->
<ItemGroup>
<!-- Reference TerminalControlLib here, so we can use it's Microsoft.Terminal.Control.winmd as
<!-- Reference TerminalControlLib here, so we can use its Microsoft.Terminal.Control.winmd as
our Microsoft.Terminal.Control.winmd. This didn't work correctly in VS2017, you'd need to
manually reference the lib -->
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\TerminalControlLib.vcxproj">
@@ -88,7 +88,7 @@
<ItemDefinitionGroup>
<Link>
<AdditionalDependencies>dwrite.lib;dxgi.lib;d2d1.lib;d3d11.lib;shcore.lib;winmm.lib;pathcch.lib;propsys.lib;uiautomationcore.lib;Shlwapi.lib;ntdll.lib;user32.lib;kernel32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>dwrite.lib;dxgi.lib;d2d1.lib;d3d11.lib;shcore.lib;winmm.lib;pathcch.lib;propsys.lib;uiautomationcore.lib;Shlwapi.lib;ntdll.lib;user32.lib;shell32.lib;kernel32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<!--
ControlLib contains a DllMain that we need to force the use of.
If you don't have this, then you'll see an error like

View File

@@ -5,6 +5,9 @@
#include <LibraryResources.h>
#include <WilErrorReporting.h>
// For g_hCTerminalCoreProvider
#include "../../cascadia/TerminalCore/tracing.hpp"
// Note: Generate GUID using TlgGuid.exe tool
TRACELOGGING_DEFINE_PROVIDER(
g_hTerminalControlProvider,
@@ -20,6 +23,7 @@ BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/)
case DLL_PROCESS_ATTACH:
DisableThreadLibraryCalls(hInstDll);
TraceLoggingRegister(g_hTerminalControlProvider);
TraceLoggingRegister(g_hCTerminalCoreProvider);
Microsoft::Console::ErrorReporting::EnableFallbackFailureReporting(g_hTerminalControlProvider);
break;
case DLL_PROCESS_DETACH:

View File

@@ -48,6 +48,7 @@
#include <winrt/Windows.ui.xaml.shapes.h>
#include <winrt/Windows.ApplicationModel.DataTransfer.h>
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.Storage.Streams.h>
#include <winrt/Windows.UI.Xaml.Shapes.h>
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
@@ -59,10 +60,12 @@
TRACELOGGING_DECLARE_PROVIDER(g_hTerminalControlProvider);
#include <telemetry/ProjectTelemetry.h>
#include <shellapi.h>
#include <ShlObj_core.h>
#include <WinUser.h>
#include "til.h"
#include <til/mutex.h>
#include "ThrottledFunc.h"

View File

@@ -58,8 +58,8 @@ void Terminal::Create(til::size viewportSize, til::CoordType scrollbackLines, Re
{
_mutableViewport = Viewport::FromDimensions({ 0, 0 }, viewportSize);
_scrollbackLines = scrollbackLines;
const til::size bufferSize{ viewportSize.X,
Utils::ClampToShortMax(viewportSize.Y + scrollbackLines, 1) };
const til::size bufferSize{ viewportSize.width,
Utils::ClampToShortMax(viewportSize.height + scrollbackLines, 1) };
const TextAttribute attr{};
const UINT cursorSize = 12;
_mainBuffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, true, renderer);
@@ -286,10 +286,10 @@ std::wstring_view Terminal::GetWorkingDirectory() noexcept
return S_OK;
}
const auto dx = viewportSize.X - oldDimensions.X;
const auto newBufferHeight = std::clamp(viewportSize.Y + _scrollbackLines, 0, SHRT_MAX);
const auto dx = viewportSize.width - oldDimensions.width;
const auto newBufferHeight = std::clamp(viewportSize.height + _scrollbackLines, 0, SHRT_MAX);
til::size bufferSize{ viewportSize.X, newBufferHeight };
til::size bufferSize{ viewportSize.width, newBufferHeight };
// This will be used to determine where the viewport should be in the new buffer.
const auto oldViewportTop = _mutableViewport.Top();
@@ -390,9 +390,9 @@ std::wstring_view Terminal::GetWorkingDirectory() noexcept
CATCH_LOG();
#pragma warning(pop)
const auto maxRow = std::max(newLastChar.Y, newCursorPos.Y);
const auto maxRow = std::max(newLastChar.y, newCursorPos.y);
const auto proposedTopFromLastLine = maxRow - viewportSize.Y + 1;
const auto proposedTopFromLastLine = maxRow - viewportSize.height + 1;
const auto proposedTopFromScrollback = newViewportTop;
auto proposedTop = std::max(proposedTopFromLastLine,
@@ -448,9 +448,9 @@ std::wstring_view Terminal::GetWorkingDirectory() noexcept
// top up so that we'll still fit within the buffer.
const auto newView = Viewport::FromDimensions({ 0, proposedTop }, viewportSize);
const auto proposedBottom = newView.BottomExclusive();
if (proposedBottom > bufferSize.Y)
if (proposedBottom > bufferSize.height)
{
proposedTop = ::base::ClampSub(proposedTop, ::base::ClampSub(proposedBottom, bufferSize.Y));
proposedTop = ::base::ClampSub(proposedTop, ::base::ClampSub(proposedBottom, bufferSize.height));
}
_mutableViewport = Viewport::FromDimensions({ 0, proposedTop }, viewportSize);
@@ -646,7 +646,7 @@ uint16_t Terminal::GetHyperlinkIdAtViewportPosition(const til::point viewportPos
// - The interval representing the start and end coordinates
std::optional<PointTree::interval> Terminal::GetHyperlinkIntervalFromViewportPosition(const til::point viewportPos)
{
const auto results = _patternIntervalTree.findOverlapping({ viewportPos.X + 1, viewportPos.Y }, viewportPos);
const auto results = _patternIntervalTree.findOverlapping({ viewportPos.x + 1, viewportPos.y }, viewportPos);
if (results.size() > 0)
{
for (const auto& result : results)
@@ -804,11 +804,20 @@ bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const Contro
// Then treat this line like it's a prompt mark.
if (_autoMarkPrompts && vkey == VK_RETURN && !_inAltBuffer())
{
DispatchTypes::ScrollMark mark;
mark.category = DispatchTypes::MarkCategory::Prompt;
// Don't set the color - we'll automatically use the DEFAULT_FOREGROUND
// color for any MarkCategory::Prompt marks without one set.
AddMark(mark);
// * If we have a current prompt:
// - Then we did know that the prompt started, (we may have also
// already gotten a MarkCommandStart sequence). The user has pressed
// enter, and we're treating that like the prompt has now ended.
// - Perform a FTCS_COMMAND_EXECUTED, so that we start marking this
// as output.
// - This enables CMD to have full FTCS support, even though there's
// no point in CMD to insert a "pre exec" hook
// * Else: We don't have a prompt. We don't know anything else, but we
// can set the whole line as the prompt, no command, and start the
// command_executed now.
//
// Fortunately, MarkOutputStart will do all this logic for us!
MarkOutputStart();
}
// Unfortunately, the UI doesn't give us both a character down and a
@@ -856,9 +865,9 @@ void Terminal::_InvalidatePatternTree(const interval_tree::IntervalTree<til::poi
// - The start and end coords
void Terminal::_InvalidateFromCoords(const til::point start, const til::point end)
{
if (start.Y == end.Y)
if (start.y == end.y)
{
const til::inclusive_rect region{ start.X, start.Y, end.X, end.Y };
const til::inclusive_rect region{ start.x, start.y, end.x, end.y };
_activeBuffer().TriggerRedraw(Viewport::FromInclusive(region));
}
else
@@ -866,18 +875,18 @@ void Terminal::_InvalidateFromCoords(const til::point start, const til::point en
const auto rowSize = _activeBuffer().GetRowByOffset(0).size();
// invalidate the first line
til::inclusive_rect region{ start.X, start.Y, rowSize - 1, start.Y };
til::inclusive_rect region{ start.x, start.y, rowSize - 1, start.y };
_activeBuffer().TriggerRedraw(Viewport::FromInclusive(region));
if ((end.Y - start.Y) > 1)
if ((end.y - start.y) > 1)
{
// invalidate the lines in between the first and last line
region = til::inclusive_rect{ 0, start.Y + 1, rowSize - 1, end.Y - 1 };
region = til::inclusive_rect{ 0, start.y + 1, rowSize - 1, end.y - 1 };
_activeBuffer().TriggerRedraw(Viewport::FromInclusive(region));
}
// invalidate the last line
region = til::inclusive_rect{ 0, end.Y, end.X, end.Y };
region = til::inclusive_rect{ 0, end.y, end.x, end.y };
_activeBuffer().TriggerRedraw(Viewport::FromInclusive(region));
}
}
@@ -1069,83 +1078,6 @@ Viewport Terminal::_GetVisibleViewport() const noexcept
size);
}
// Writes a string of text to the buffer, then moves the cursor (and viewport)
// in accordance with the written text.
// This method is our proverbial `WriteCharsLegacy`, and great care should be made to
// keep it minimal and orderly, lest it become WriteCharsLegacy2ElectricBoogaloo
// TODO: MSFT 21006766
// This needs to become stream logic on the buffer itself sooner rather than later
// because it's otherwise impossible to avoid the Electric Boogaloo-ness here.
// I had to make a bunch of hacks to get Japanese and emoji to work-ish.
void Terminal::_WriteBuffer(const std::wstring_view& stringView)
{
auto& cursor = _activeBuffer().GetCursor();
// Defer the cursor drawing while we are iterating the string, for a better performance.
// We can not waste time displaying a cursor event when we know more text is coming right behind it.
cursor.StartDeferDrawing();
for (size_t i = 0; i < stringView.size(); i++)
{
const auto wch = stringView.at(i);
const auto cursorPosBefore = cursor.GetPosition();
auto proposedCursorPosition = cursorPosBefore;
// TODO: MSFT 21006766
// This is not great but I need it demoable. Fix by making a buffer stream writer.
//
// If wch is a surrogate character we need to read 2 code units
// from the stringView to form a single code point.
const auto isSurrogate = wch >= 0xD800 && wch <= 0xDFFF;
const auto view = stringView.substr(i, isSurrogate ? 2 : 1);
const OutputCellIterator it{ view, _activeBuffer().GetCurrentAttributes() };
const auto end = _activeBuffer().Write(it);
const auto cellDistance = end.GetCellDistance(it);
const auto inputDistance = end.GetInputDistance(it);
if (inputDistance > 0)
{
// If "wch" was a surrogate character, we just consumed 2 code units above.
// -> Increment "i" by 1 in that case and thus by 2 in total in this iteration.
proposedCursorPosition.X += cellDistance;
i += gsl::narrow_cast<size_t>(inputDistance - 1);
}
else
{
// If _WriteBuffer() is called with a consecutive string longer than the viewport/buffer width
// the call to _buffer->Write() will refuse to write anything on the current line.
// GetInputDistance() thus returns 0, which would in turn cause i to be
// decremented by 1 below and force the outer loop to loop forever.
// This if() basically behaves as if "\r\n" had been encountered above and retries the write.
// With well behaving shells during normal operation this safeguard should normally not be encountered.
proposedCursorPosition.X = 0;
proposedCursorPosition.Y++;
// Try the character again.
i--;
// If we write the last cell of the row here, TextBuffer::Write will
// mark this line as wrapped for us. If the next character we
// process is a newline, the Terminal::CursorLineFeed will unmark
// this line as wrapped.
// TODO: GH#780 - This should really be a _deferred_ newline. If
// the next character to come in is a newline or a cursor
// movement or anything, then we should _not_ wrap this line
// here.
}
_AdjustCursorPosition(proposedCursorPosition);
}
// Notify UIA of new text.
// It's important to do this here instead of in TextBuffer, because here you have access to the entire line of text,
// whereas TextBuffer writes it one character at a time via the OutputCellIterator.
_activeBuffer().TriggerNewTextNotification(stringView);
cursor.EndDeferDrawing();
}
void Terminal::_AdjustCursorPosition(const til::point proposedPosition)
{
#pragma warning(suppress : 26496) // cpp core checks wants this const but it's modified below.
@@ -1156,31 +1088,33 @@ void Terminal::_AdjustCursorPosition(const til::point proposedPosition)
// If we're about to scroll past the bottom of the buffer, instead cycle the
// buffer.
til::CoordType rowsPushedOffTopOfBuffer = 0;
const auto newRows = std::max(0, proposedCursorPosition.Y - bufferSize.Height() + 1);
if (proposedCursorPosition.Y >= bufferSize.Height())
const auto newRows = std::max(0, proposedCursorPosition.y - bufferSize.Height() + 1);
if (proposedCursorPosition.y >= bufferSize.Height())
{
for (auto dy = 0; dy < newRows; dy++)
{
_activeBuffer().IncrementCircularBuffer();
proposedCursorPosition.Y--;
proposedCursorPosition.y--;
rowsPushedOffTopOfBuffer++;
// Update our selection too, so it doesn't move as the buffer is cycled
if (_selection)
{
// Stash this, so we can make sure to update the pivot to match later
const auto pivotWasStart = _selection->start == _selection->pivot;
// If the start of the selection is above 0, we can reduce both the start and end by 1
if (_selection->start.Y > 0)
if (_selection->start.y > 0)
{
_selection->start.Y -= 1;
_selection->end.Y -= 1;
_selection->start.y -= 1;
_selection->end.y -= 1;
}
else
{
// The start of the selection is at 0, if the end is greater than 0, then only reduce the end
if (_selection->end.Y > 0)
if (_selection->end.y > 0)
{
_selection->start.X = 0;
_selection->end.Y -= 1;
_selection->start.x = 0;
_selection->end.y -= 1;
}
else
{
@@ -1188,6 +1122,15 @@ void Terminal::_AdjustCursorPosition(const til::point proposedPosition)
_selection.reset();
}
}
// If we still have a selection, make sure to sync the pivot
// with whichever value is the right one.
//
// Failure to do this might lead to GH #14462
if (_selection.has_value())
{
_selection->pivot = pivotWasStart ? _selection->start : _selection->end;
}
}
}
@@ -1203,10 +1146,10 @@ void Terminal::_AdjustCursorPosition(const til::point proposedPosition)
if (!_inAltBuffer())
{
auto updatedViewport = false;
const auto scrollAmount = std::max(0, proposedCursorPosition.Y - _mutableViewport.BottomInclusive());
const auto scrollAmount = std::max(0, proposedCursorPosition.y - _mutableViewport.BottomInclusive());
if (scrollAmount > 0)
{
const auto newViewTop = std::max(0, proposedCursorPosition.Y - (_mutableViewport.Height() - 1));
const auto newViewTop = std::max(0, proposedCursorPosition.y - (_mutableViewport.Height() - 1));
// In the alt buffer, we never need to adjust _mutableViewport, which is the viewport of the main buffer.
if (newViewTop != _mutableViewport.Top())
{
@@ -1251,8 +1194,20 @@ void Terminal::_AdjustCursorPosition(const til::point proposedPosition)
{
for (auto& mark : _scrollMarks)
{
// Move the mark up
mark.start.y -= rowsPushedOffTopOfBuffer;
// If the mark had sub-regions, then move those pointers too
if (mark.commandEnd.has_value())
{
(*mark.commandEnd).y -= rowsPushedOffTopOfBuffer;
}
if (mark.outputEnd.has_value())
{
(*mark.outputEnd).y -= rowsPushedOffTopOfBuffer;
}
}
_scrollMarks.erase(std::remove_if(_scrollMarks.begin(),
_scrollMarks.end(),
[](const VirtualTerminal::DispatchTypes::ScrollMark& m) { return m.start.y < 0; }),
@@ -1542,9 +1497,11 @@ void Terminal::_updateUrlDetection()
}
}
// NOTE: This is the version of AddMark that comes from the UI. The VT api call into this too.
void Terminal::AddMark(const Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark& mark,
const til::point& start,
const til::point& end)
const til::point& end,
const bool fromUi)
{
if (_inAltBuffer())
{
@@ -1555,11 +1512,21 @@ void Terminal::AddMark(const Microsoft::Console::VirtualTerminal::DispatchTypes:
m.start = start;
m.end = end;
_scrollMarks.push_back(m);
if (fromUi)
{
_scrollMarks.insert(_scrollMarks.begin(), m);
}
else
{
_scrollMarks.push_back(m);
}
// Tell the control that the scrollbar has somehow changed. Used as a
// workaround to force the control to redraw any scrollbar marks
_NotifyScrollEvent();
// DON'T set _currentPrompt. The VT impl will do that for you. We don't want
// UI-driven marks to set that.
}
void Terminal::ClearMark()
@@ -1576,13 +1543,14 @@ void Terminal::ClearMark()
start = til::point{ GetSelectionAnchor() };
end = til::point{ GetSelectionEnd() };
}
auto inSelection = [&start, &end](const DispatchTypes::ScrollMark& m) {
return (m.start >= start && m.start <= end) ||
(m.end >= start && m.end <= end);
};
_scrollMarks.erase(std::remove_if(_scrollMarks.begin(),
_scrollMarks.end(),
[&start, &end](const auto& m) {
return (m.start >= start && m.start <= end) ||
(m.end >= start && m.end <= end);
}),
inSelection),
_scrollMarks.end());
// Tell the control that the scrollbar has somehow changed. Used as a

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