Compare commits

..

35 Commits

Author SHA1 Message Date
Zoey Riordan
06001e4acc fix mouse events in the wpf control (#4720)
PR #4548 inadvertantly broke mouse button input in the WPF control. This happened due to the extra layer of HWND indirection. The fix is to move the mouse button handling down into the native control where the window messages are now being sent.

(cherry picked from commit 4393fefb71)
2020-02-28 14:49:42 -08:00
Dustin Howett
1d022aa816 Revert "Restrict DX run height adjustment to only relevant glyph AND Correct PTY rendering on trailing half of fullwidth glyphs (#4668)"
This reverts commit 4420950337.
2020-02-28 10:15:12 -08:00
Dustin Howett
f68bd09d7a Revert "Clip text to within the row we expect (#4671)"
This reverts commit 671110c88a.
2020-02-28 10:14:13 -08:00
Dustin Howett
8fb90dcea5 Revert "Don't split surrogate pairs when breaking runs for scaling. Affects emoji rendering. #4704 (#4731)"
This reverts commit d7ea526c3c.
2020-02-28 10:13:51 -08:00
Carlos Zamora
0e672fac08 Move rect expansion to textbuffer; refactor selection code (#4560)
- When performing chunk selection, the expansion now occurs at the time
  of the selection, not the rendering of the selection
- `GetSelectionRects()` was moved to the `TextBuffer` and is now shared
  between ConHost and Windows Terminal
- Some of the selection variables were renamed for clarity
- Selection COORDs are now in the Text Buffer coordinate space
- Fixes an issue with Shift+Click after performing a Multi-Click
  Selection

## References
This also contributes to...
- #4509: UIA Box Selection
- #2447: UIA Signaling for Selection
- #1354: UIA support for Wide Glyphs

Now that the expansion occurs at before render-time, the selection
anchors are an accurate representation of what is selected. We just need
to move `GetText` to the `TextBuffer`. Then we can have those three
issues just rely on code from the text buffer. This also means ConHost
gets some of this stuff for free 😀

### TextBuffer
- `GetTextRects` is the abstracted form of `GetSelectionRects`
- `_ExpandTextRow` is still needed to handle wide glyphs properly

### Terminal
- Rename...
    - `_boxSelection` --> `_blockSelection` for consistency with ConHost
    - `_selectionAnchor` --> `_selectionStart` for consistency with UIA
    - `_endSelectionPosition` --> `_selectionEnd` for consistency with
      UIA
- Selection anchors are in Text Buffer coordinates now
- Really rely on `SetSelectionEnd` to accomplish appropriate chunk
  selection and shift+click actions

## Validation Steps Performed
- Shift+Click
- Multi-Click --> Shift+Click
- Chunk Selection at...
    - top of buffer
    - bottom of buffer
    - random region in scrollback

Closes #4465
Closes #4547
2020-02-27 16:42:26 -08:00
Carlos Zamora
74cd9db383 Define Automation Properties for TermControl (#4732)
Defines the following automation properties for a Terminal Control:
- [**Orientation**](https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.automation.peers.automationpeer.getorientationcore):
  - The orientation of the control
  - None --> Vertical
- [**Name**](https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.automation.peers.automationpeer.getnamecore):
  - The name as used by assistive technology and other Microsoft UI
    Automation clients. Generally presented by automation clients as the
    primary way to identify an element (along with the control type)
  - "" --> <profile name>
- [**HelpText**](https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.automation.peers.automationpeer.gethelptextcore):
  - The help text. Generally presented by automation clients if
    requested by the user. This would be something that you would normally
    expect to appear from tooltips.
  - "" --> <tab title>
- [**LiveSetting**](https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.automation.peers.automationpeer.getlivesettingcore):
  - reports the live setting notification behavior. A representation of
    how assertive this control should be when content changes.
  - none --> Polite

## Detailed Description of the Pull Request / Additional comments
ProfileName had to be added to the TerminalSettings (IControlSettings)
to pass that information along to the automation peer. In the rare event
that somebody purposefully decided to make their ProfileName empty, we
fallback to the tab title.


## Validation Steps Performed
Verified using Accessibility Insights and inspect.exe

This is are some examples of the information a general user can expect
to receive about a Terminal Control.

- Type: Terminal Control
- Name: Command Prompt
- Help Text (if requested): Command Prompt - ping bing.com

- Type: Terminal Control
- Name: Ubuntu
- Help Text (if requested): cazamor@PC-cazamor:/mnt/c/Users/cazamor$

Note, it is generally read by an automation client as follows:
"<type>, <name>"

References #2099 - Automation Properties for TerminalControl, Search Box
References #2142 - Localization

Closes #2142
2020-02-27 16:37:56 -08:00
Carlos Zamora
a97048a798 Accept String value for Keybindings's Keys (#4714)
## Summary of the Pull Request

`keys` in `keybindings` now accepts a string value. This assumes that you wanted a keychord of size 1. The schema and user docs were properly updated too.

This means that the following keybinding is now accepted in your profiles.json:
```json
{ "command": "copy", "keys": "ctrl+c" }
```
as opposed to...
```json
{ "command": "copy", "keys": [ "ctrl+c" ] }
```

## PR Checklist
* [X] Closes #4713
* [X] CLA signed.
* [X] Tests added/passed
* [X] Requires documentation to be updated


## Validation Steps Performed
- [X] tested the new schema
- [X] added test
2020-02-27 20:53:31 +00:00
greg904
2f60cf0e91 Fix RenderThread's notify mechanism (#4698)
## Summary of the Pull Request

Fix a bug where the `Renderer::PaintFrame` method:
1. is not called until the next `RenderThread::NotifyThread` call but needs to be called because there the terminal was updated (theoretical bug)
2. is called twice but needs to be called only once (verified bug)

## References

The bug was introduced by #3511.

## PR Checklist
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA

## Detailed Description of the Pull Request / Additional comments

### Before

#### First bug

In the original code, `_fNextFrameRequested` is set to `true` in render thread because `std::atomic_flag::test_and_set` is called.

This is wrong because it means that the render thread will render the terminal again even if there is no change after the last render.

I think the the goal was to load the boolean value for `_fNextFrameRequested` to check whether the thread should sleep or not.

The problem is that there is no method on `std::atomic_flag` to load its boolean value. I guess what happened was that the "solution" that was found was to use `std::atomic_flag::test_and_set`, followed by `std::atomic_flag::clear` if the value was `false` originally (if `std::atomic_flag::test_and_set` returned `false`) to restore the original value. I guess that this was believed to be equivalent to just a simple load, without doing any change to the value because it restores it at the end.

But it's not: this is dangerous because if the value is changed to `true` between the call to `std::atomic_flag::test_and_set` and the call to `std::atomic_flag::clear`, then the value ends up being `false` at the end which is wrong because we don't want to change it! And if that value ends up being `false`, it means that we miss a render because we will wait on `_hEvent` during the next iteration on the render thread.

Well actually, here, this not even a problem because when that code is ran, `_fPainting` is `false` which means that the other thread that modifies the `_fNextFrameRequested` value through `RenderThread::NotifyPaint` will not actually modify `_fNextFrameRequested` but rather call `SetEvent` (see the method's body).

But wait! There is a problem there too! `std::atomic_flag::test_and_set` is called for `_fPainting` which sets its value to `true`. It was probably unintended. So actually, the next call to `RenderThread::NotifyPaint` _will_ end up modifying `_fNextFrameRequested` which means that the data race I was talking about _might_ happen!

#### Second bug

Let's go back a little bit in my explanation. I was talking about the fact that:

> I guess what happened was that the "solution" that was found was to use `std::atomic_flag::test_and_set`, followed by `std::atomic_flag::clear` if the value was `false` originally (if `std::atomic_flag::test_and_set` returned `false`) to restore the original value.

The problem is that the reverse was done in the implementation: `std::atomic_flag::clear` is called if the value was _`true`_ originally!

So at this point, if the value of `_fNextFrameRequested` was `false`, then `std::atomic_flag::test_and_set` sets its is set to `true` and returns `false`. So for the next iteration, `_fNextFrameRequested` is `true` and the render thread will re-render but that was not needed.

### After

I used `std::atomic<bool>` instead of `std::atomic_flag` for `_fNextFrameRequested` and the other atomic field because it has a `load` and a `store` method so we can actually load the value without changing it.

I also replaced `_fPainting` by `_fWaiting`, which is basically the opposite of `_fPainting` but stays `true` for a little shorter than `_fPainting` would stay `false`. Indeed, I think that it makes more sense to directly wrap/scope _just_ the call to `WaitForSingleObject` by setting my atomic variable to `true` _just_ before and to `false` _just_ after because:
* It makes more sense while you're reading the code: it's easier IMO to understand what the purpose of `_fWaiting` is (that is, to call `SetEvent` from `RenderThread::NotifyPaint` if it's `true`).
* It's probably a tiny bit better for performance because it will become `true` for a little shorter which means less calls to `SetEvent`.

#### Warning

I don't really understand [std::memory_order](https://en.cppreference.com/w/cpp/atomic/memory_order)s.

So I used the default one (`std::memory_order_seq_cst`) which is the safest.

I believe that if no read or write are reordered in the two threads (`RenderThread::NotifyPaint` and `RenderThread::_ThreadProc`), then the code I wrote will behave correctly.

I think that `std::memory_order_seq_cst` enforces that so it should be fine, but I'm not sure.

## Validation Steps Performed

**I tried to reproduce the second bug that I described in the first section of this PR.**

I put a breakpoint on `RenderThread::NotifyPaint` and on `Renderer::PaintFrame`. Initially they are disabled. Then I ran the terminal in Release mode, waited a bit for the prompt to display and the cursor to start blinking. Then I enabled the breakpoints.

### Before

Each `RenderThread::NotifyPaint` is followed by 2 `Renderer::PaintFrame` calls.  

### After

Each `RenderThread::NotifyPaint` is followed by 1 `Renderer::PaintFrame` call. ✔️
2020-02-27 18:29:21 +00:00
Mike Griese
e5182fb3e8 Make Conpty emit wrapped lines as actually wrapped lines (#4415)
## Summary of the Pull Request

Changes how conpty emits text to preserve line-wrap state, and additionally adds rudimentary support to the Windows Terminal for wrapped lines.

## References

* Does _not_ fix (!) #3088, but that might be lower down in conhost. This makes wt behave like conhost, so at least there's that
* Still needs a proper deferred EOL wrap implementation in #780, which is left as a todo
* #4200 is the mega bucket with all this work
* MSFT:16485846 was the first attempt at this task, which caused the regression MSFT:18123777 so we backed it out.
* #4403 - I made sure this worked with that PR before I even sent #4403

## PR Checklist
* [x] Closes #405
* [x] Closes #3367 
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated

## Detailed Description of the Pull Request / Additional comments

I started with the following implementation:
When conpty is about to write the last column, note that we wrapped this line here. If the next character the vt renderer is told to paint get is supposed to be at the start of the following line, then we know that the previous line had wrapped, so we _won't_ emit the usual `\r\n` here, and we'll just continue emitting text.

However, this isn't _exactly_ right - if someone fills the row _exactly_ with text, the information that's available to the vt renderer isn't enough to know for sure if this line broke or not. It is possible for the client to write a full line of text, with a `\n` at the end, to manually break the line. So, I had to also add the `lineWrapped` param to the `IRenderEngine` interface, which is about half the files in this changelist.

## Validation Steps Performed
* Ran tests
* Checked how the Windows Terminal behaves with these changes
* Made sure that conhost/inception and gnome-terminal both act as you'd expect with wrapped lines from conpty
2020-02-27 16:40:11 +00:00
Dustin L. Howett (MSFT)
1de07aa3ab Convert TermControl to .xaml (#4729)
This commit removes all of the custom UI initialization code in
TermControl and replaces it with a xaml file. Some dead or reundant code
was removed as part of this refactoring.

It also fixes two (quasi-related) issues:

* The search box, on first launch, was offset by the scrollbar even if
  the scrollbar was invisible.
* The scrollbar state wasn't hot-reloadable.
2020-02-26 16:35:16 -08:00
Zoey Riordan
9f53107f00 Fix PublicTerminalCore APIset usage on Windows 7 (#4733)
The new UIA code in PublicTerminalCore broke windows 7 support by referring
to API set dlls for UIA. This PR changes the link line to link to
UIAutomationcore.dll directly.
2020-02-26 16:30:05 -08:00
Michael Niksa
d7ea526c3c Don't split surrogate pairs when breaking runs for scaling. Affects emoji rendering. #4704 (#4731)
## Summary of the Pull Request
- Surrogate pairs are being split in half with the run splitting check.

## References
- Related to #4708 but not going to fix it.

## PR Checklist
* [x] Closes #4704
* [x] I work here.
* [x] I am a core contributor.

## Detailed Description of the Pull Request / Additional comments
- The adjustment of the run heights in the correction function reports back a text index and a scaling factor. However, I didn't remember at the time that the text is being stored as UTF-16. So the index given can be pointing to the high surrogate of a pair. Thus adding 1 to split "after" the text character, then backing up by 1 isn't valid in if the index given was for a high surrogate.

The quick fix is to advance by two if it's a high surrogate and one otherwise.

## Validation Steps Performed
- Used the sample code from #4704 to print the house emoji in various situations into the buffer.
2020-02-26 21:55:22 +00:00
Leon Liang
61e5917fe8 Allow IME input in Search Box (#4723)
## Summary of the Pull Request
Currently, when the user attempts to type using IME while the Search Box is focused, the input goes to the terminal instead. This is due to the fact that the `TSFInputControl` assumes it's in control whenever TermControl gets focus. So, it'll intercept IME input before the Search Box receives it. We simply need to modify `TermControl::GotFocus` to check if the SearchBox has focus. If it does, `TSFInputControl::NotifyFocusEnter` shouldn't be called.

As a small side fix, I've also disabled the terminal cursor blinking when the Search Box has focus.

Thinking a little further, if we have more features in the future that behave like search box (i.e. advanced tab switcher, or any other XAML controls that pop up) and require text input, we might need to create a sort of "AnyOtherTextControlInFocus" function to see if TSFInputControl should receive focus or not.

## PR Checklist
* [x] Closes #4434
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] Tests added/passed

## Validation Steps Performed
Search works as expected with IME. Composition picker appears underneath Search Box when typing IME in the Search Box. Clicking outside of the Search Box still returns control to TSFInputControl/TermControl.

Terminal Cursor blinks when it has focus, and doesn't when the Search Box has focus.
2020-02-26 19:28:01 +00:00
Leon Liang
31c9d19a72 Display Emojis, Kaomojis, and symbols while in IME mode (#4688)
## Summary of the Pull Request
Currently, while in IME mode, selections with the Emoji/Kaomoji/Symbol Picker (which is brought up with <kbd>win+.</kbd>) are not displayed until the user starts a new composition. This is due to the fact that we hide the TextBlock when we receive a CompositionCompleted event, and we only show the TextBlock when we receive a CompositionStarted event. Input from the picker does not count as a composition, so we were never showing the text box, even if the symbols were thrown into the inputBuffer. In addition, we weren't receiving CompositionStarted events when we expected to.

We should be showing the TextBlock when we receive _any_ text, so we should make the TextBlock visible inside of `TextUpdatingHandler`. Furthermore, some really helpful discussion in #3745 around wrapping the `NotifyTextChanged` call with a `NotifyFocusLeave` and a `NotifyFocusEnter` allowed the control to much more consistently determine when a CompositionStarted and a CompositionEnded.

I've also went around and replaced casts with saturating casts, and have removed the line that sets the `textBlock.Width()` so that it would automatically set its width. This resolves the issue where while composing a sentence, the textBlock would be too small to contain all the text, so it would be cut off, but the composition is still valid and still able to continue.

## PR Checklist
* [x] Closes #4148
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] Tests added/passed

## Validation Steps Performed
Tested picking emojis, kaomojis, and symbols with numerous different languages.
2020-02-26 18:36:02 +00:00
Dustin L. Howett (MSFT)
0efdc8f004 TermControl: don't use narrow when narrow_cast will do (#4721)
This will help one cause of #4623, which looks like it may be caused by
floats being truncated when stuffed into LONGs for UIA purposes.
2020-02-25 17:10:07 -08:00
Dustin L. Howett (MSFT)
64b446abb0 Fix the build on VS 2019 Update 5 (#4722)
This commit introduces two fixes for C5205 (delete of an abtract class
without a virtual dtor) and one fix for a very hopeful VS version gating
that didn't pan out.
2020-02-26 00:28:32 +00:00
Paul Ming
de5e72f3a4 Scale retro terminal scan lines (#4716)
<!-- 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
- Scale the retro terminal effects (#3468) scan lines with the screen's DPI.
- Remove artifacts from sampling wrap around.

Before & after, with my display scale set to 350%:
![Scaling scan lines](https://user-images.githubusercontent.com/38924837/75214566-df0f4780-5742-11ea-9bdc-3430eb24ccca.png)

Before & after showing artifact removal, with my display scale set to 100%, and image enlarged to 400%:
![Sampling artifacts annotated](https://user-images.githubusercontent.com/38924837/75214618-05cd7e00-5743-11ea-9060-f4eba257ea56.png)

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

Adds a constant buffer, which could be used for other settings for the retro terminal pixel shader.

I haven't touched C++ in over a decade before this change, and this is the first time I've played with DirectX, so please assume my code isn't exactly best practice. 🙂

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

- Changed display scale with experimental.retroTerminalEffect enabled, enjoyed scan lines on high resolution monitors.
- Enabled experimental.retroTerminalEffect, turned the setting off, changed display scale. Retro tabs still scale scan lines.
2020-02-26 00:08:45 +00:00
Mike Griese
8a5407c13a Add support for cleartype text antialiasing (#4711)
## Summary of the Pull Request

I needed to do something to keep sane so today I day of learned about antialiasing. This PR adds the ability to specify the `"antialiasingMode"` as a setting.
* "antialiasingMode": "grayscale": the current behavior, `D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE`
* "antialiasingMode": "cleartype": use `D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE` instead


## PR Checklist
* [x] Closes #1298
* [x] I work here
* [ ] I didn't add tests 
* [x] Requires documentation to be updated

## Detailed Description of the Pull Request / Additional comments
Grayscale:
 
![image](https://user-images.githubusercontent.com/18356694/75173847-2373f680-56f5-11ea-8896-c1cf04c61d41.png)



Cleartype:
![image](https://user-images.githubusercontent.com/18356694/75173854-25d65080-56f5-11ea-9de1-e2d1c343cae5.png)

 


Side-by-side (can you tell which is which?) <!-- grayscale, cleartype -->
 
![image](https://user-images.githubusercontent.com/18356694/75173864-28d14100-56f5-11ea-8bdd-d47a60fbbe4d.png)
2020-02-25 22:19:57 +00:00
Zoey Riordan
4def49c45e hook up UIA tree to WPF control (#4548)
This PR hooks up the existing UIA implementation to the WPF control. Some existing code that was specific to the UWP terminal control could be shared so that has been refactored to a common location as well.

## Validation Steps Performed
WPF control was brought up in UISpy and the UIA tree was verified. NVDA was then used to check that screen readers were operating properly.
2020-02-24 23:17:55 +00:00
Steffen
b8e33560f9 make sure caching of partials still works if the string consists of a single lead byte only (GH#4673) (#4685)
## Summary of the Pull Request
Fixes a flaw that happened if `til::u8u16` received a single lead byte.

## PR Checklist
* [x] Closes #4673 
* [x] Tests added/passed

## Detailed Description of the Pull Request / Additional comments
The loop for caching partials didn't run and thus, the lead byte was
converted to U+FFFD. That's because the loop starts with `sequenceLen`
initialized with 1. And if the string has a length of 1 the initial
condition is `1<1` which is evaluated to `false` and the body of the
loop was never executed.

## Validation Steps Performed
1) updated the code of the state class and tested manually that `printf
   "\xE2"; printf "\x98\xBA\n"` prints a U+263A character
2) updated the unit tests to make sure that still up to 3 partials are
   cached
3) updated the unit tests to make sure caching also works if the string
   consists of a lead byte only
4) tested manually that #4086 is still resolved
2020-02-21 20:45:53 +00:00
Michael Niksa
671110c88a Clip text to within the row we expect (#4671)
## Summary of the Pull Request
Adjusts `DrawGlyphRun` method inside DirectX renderer to restrict text
to be clipped within the boundaries of the row.

## PR Checklist
* [x] Closes #1703
* [x] I work here.
* [x] No tests.
* [x] No docs.
* [x] I am core contributor.

## Detailed Description of the Pull Request / Additional comments
For whatever reason, some of these shade glyphs near U+2591 tend to
extend way above the height of where we expect they should. This didn't
look like a problem in conhost because it clipped every draw inside the
bounds. This therefore applies the same clip logic as people don't
really expect text to pour out of the box.

It could, theoretically, get us into trouble later should someone
attempt zalgo text. But doing zalgo text is more of a silliness that
varies in behavior across rendering platforms anyway.

## Validation Steps Performed
- Ran the old conhost GDI renderer and observed
- Ran the new Terminal DX renderer and observed
- Made the code change
- Observed that the height and approximate display characteristics of
  the U+2591 shade and neighboring characters now matches with the conhost
  GDI style to stay within its lane.
2020-02-21 00:57:14 +00:00
Michael Niksa
4420950337 Restrict DX run height adjustment to only relevant glyph AND Correct PTY rendering on trailing half of fullwidth glyphs (#4668)
## Summary of the Pull Request
- Height adjustment of a glyph is now restricted to itself in the DX
  renderer instead of applying to the entire run
- ConPTY compensates for drawing the right half of a fullwidth
  character. The entire render base has this behavior restored now as
  well.

## PR Checklist
* [x] Closes #2191
* [x] I work here
* [x] Tests added/passed
* [x] No doc
* [x] Am core contributor.

## Detailed Description of the Pull Request / Additional comments
Two issues:
1. On the DirectX renderer side, when confronted with shrinking a glyph,
   the correction code would apply the shrunken size to the entire run, not
   just the potentially individual glyph that needed to be reduced in size.
   Unfortunately while adjusting the horizontal X width can be done for
   each glyph in a run, the vertical Y height has to be adjusted for an
   entire run. So the solution here was to split the individual glyph
   needing shrinking out of the run into its own run so it can be shrunk.
2. On the ConPTY side, there was a long standing TODO that was never
   completed to deal with a request to draw only the right half of a
   two-column character. This meant that when encountering a request for
   the right half only, we would transmit the entire full character to be
   drawn, left and right halves, struck over the right half position. Now
   we correct the cursor back a position (if space) and draw it out so the
   right half is struck over where we believe the right half should be (and
   the left half is updated as well as a consequence, which should be OK.)

The reason this happens right now is because despite VIM only updating
two cells in the buffer, the differential drawing calculation in the
ConPTY is very simplistic and intersects only rectangles. This means
from the top left most character drawn down to the row/col cursor count
indicator in vim's modeline are redrawn with each character typed. This
catches the line below the edited line in the typing and refreshes it.
But incorrectly.

We need to address making ConPTY smarter about what it draws
incrementally as it's clearly way too chatty. But I plan to do that with
some of the structures I will be creating to solve #778.

## Validation Steps Performed
- Ran the scenario listed in #2191 in vim in the Terminal
- Added unit tests similar to examples given around glyph/text mapping
  in runs from Microsoft community page
2020-02-21 00:24:12 +00:00
Dustin L. Howett (MSFT)
7d6738cde7 Shim the AzureConn through a conhost to stop VT bleeding (#4652)
This commit introduces a small console-subsystem application whose sole
job is to consume TerminalConnection.dll and hook it up to something
other than Terminal. It is 99% of the way to a generic solution.

I've introduced a stopgap in TerminalPage that makes sure we launch
TerminalAzBridge using ConptyConnection instead of AzureConnection.

As a bonus, this commit includes a class whose sole job it is to make
reading VT input off a console handle not terrible. It returns you a
string and dispatches window size change callbacks.

Fixes #2267.
Fixes #4589.
Related to #2266 (since pwsh needs better VT).
2020-02-20 16:21:05 -08:00
Dustin L. Howett (MSFT)
693cdc1c95 Unify resource filenames across the repository (#4642)
This unifies the rest of the projects around the resource structure laid out in WindowsTerminalUniversal. Now we'll have a single flat structure for resource files and keep the qualifiers in their filenames. It's easier to manage this way.
2020-02-20 23:51:41 +00:00
Carlos Zamora
360c655acc UIA: Fix GetVisibleRanges() and add Tracing (#4495)
## Summary of the Pull Request
Debugging our custom UIA providers has been a painful experience because outputting content to VS may result in UIA Clients getting impatient and giving up on extracting data.

Adding tracing allows us to debug these providers without getting in the way of reproducing a bug. This will help immensely with developing accessibility features on Windows Terminal and Console.

This pull request additionally contains payload from #4526:
* Make GetVisibleRanges() return one range (and add tracing for it).
`ScreenInfoUiaProvider::GetVisibleRanges()` used to return one range per line of visible text. The documentation for this function says that we should return one per contiguous span of text. Since all of the text in the TermControl will always be contiguous (at least by our standards), we should only ever be returning one range.

## PR Checklist
* [x] Closes #1914. Closes #4507.
* [x] CLA signed

## Detailed Description of the Pull Request / Additional comments
`UiaTracing` is a singleton class that is in charge of registration for trace logging. `TextRange` is used to trace `UiaTextRange`, whereas `TextProvider` is used to trace `ScreenInfoUiaProviderBase`.

`_getValue()` is overloaded to transform complex objects and enums into a string for logging.

`_getTextValue()` had to be added to be able to trace the text a UiaTextRange included. This makes following UiaTextRanges much simpler.

## Validation Steps Performed
Performed a few operations when under NVDA/Narrator and manually checked the results.
2020-02-20 23:50:43 +00:00
Michael Niksa
215df3212f Add DxEngine drawing ETW tracing for debugging and diagnostics purposes (#4664)
## Summary of the Pull Request
Adds an ETW provider for tracing out operations inside the DirectX Renderer.

## References
This supports #2191 and #778 and other rendering issues.

## PR Checklist
* [x] I work here

## Detailed Description of the Pull Request / Additional comments
This declares and defines the provider with the a GUID appropriate for the namespace and adds an initial invalidation rectangle method for figuring out what is being drawn mostly to understand what could be differential.

## Validation Steps Performed
Implemented provider.
Opened real-time ETW tracing tool
Ran the Terminal and watched invalidation events appear live
2020-02-20 23:13:43 +00:00
Carlos Zamora
d0c8221c6e Make ScreenInfoUiaProvider::GetSelection() Return One Selection (#4466)
## Summary of the Pull Request
We used to return multiple text ranges to represent one selection. We only support one selection at a time, so we should only return one range.

Additionally, I moved all TriggerSelection() calls to the renderer from Terminal to TermControl for consistency. This ensures we only call it _once_ when we make a change to our selection state.

## References
#2447 - helps polish Signaling for Selection
#4465 - This is more apparent as the problem holding back Signaling for Selection

## PR Checklist
* [x] Closes #4452 

Tested using Accessibility Insights.
2020-02-20 23:03:50 +00:00
Yitzhak Steinmetz
ce39b63f46 Update building instructions (#4650) 2020-02-20 13:05:20 -08:00
Leon Liang
2dec894515 Fix duplicate copyOnSelect when clicking on an unfocused terminal (#4596)
## Summary of the Pull Request
Currently, clicking on an unfocused terminal with a selection active will trigger `copyOnSelect`. This is because the check for `copyOnSelect` and copying to the clipboard is bound to when the Pointer is released. This works fine for when a user performs a click-drag selection, but it inadvertently also triggers when the user performs a single click on an unfocused terminal. We expect `copyOnSelect` to trigger only on the first time a selection is completed. 

This PR will allow the user to single click on an unfocused terminal that has a selection active without triggering a copyOnSelect. It also ensures that any click-drag selection, whether it's on an unfocused or focused terminal, will trigger copyOnSelect.

## PR Checklist
* [x] Closes #4255

## Validation Steps Performed
Performed manual testing involving permutations of multiple panes, tabs, in focus, and out of focus.
2020-02-20 18:30:10 +00:00
Dustin L. Howett (MSFT)
39d3c65420 Migrate the ConPTY functional tests out of Windows (#4648)
## Summary of the Pull Request
This will allow us to run the ConPTY tests in CI.

## PR Checklist
* [x] Closes MSFT:24265197
* [X] I've discussed this with core contributors already.

## Validation Steps Performed
I've run the tests.

Please note: this code is unchanged (apart from `wil::ScopeExit` -> `wil::scope_exit`) from Windows. Now is not the time to comment on their perfectness.
2020-02-19 13:27:17 -08:00
Dustin L. Howett (MSFT)
deccf7e12b Batch up to 64 files per clang-format to speed it up (#4639)
This will make `Invoke-CodeFormat` less bad.
2020-02-19 13:47:56 +00:00
James
8392d6b647 Fix touchpad and touchscreen scrolling (#4554)
Fixed inconsistent scrolling when using both touchscreen and precision
touchpad.

Scrolling jumpiness is caused by rounding errors. Instead of retrieving
the current scrolling value from `GetScrollOffset`, which is already
rounded in `int`, let the scrolling operation to operate on
`_scrollBar`'s `Value` directly (which uses `double`) in
`TermControl.cpp`.

TermControl now also respects WHEEL_DELTA, which it was previously
ignoring, to determine how many scroll wheel detents were used.

Testing scrolling on the following scenario manually:

- nonscrollable terminal (e.g. the window is large enough to contain the
  current buffer).
- scrolling to the topmost and bottom-most.
- scrolling TUI apps such as `nano` and `more` in WSL.
- after clearing the terminal, both in cmd and WSL.

... has the same behavior between using touchscreen or precision trackpad
and regular mouse wheel.

Closes #4554 (original pull request)
Closes #1066
Closes #4542
2020-02-18 19:01:58 -08:00
Fernando de Oliveira
c54f59b3c5 doc: fix double quotes on cmder and Anaconda profiles (#4617) 2020-02-18 10:43:50 -08:00
Ryan Punt
9df9bd00d7 Add linting fixes for markdown (#4615)
VSCode's markdown linter suggested a pile of fixes
2020-02-18 09:20:03 -08:00
Clint Rutkas
941a44a464 readme: reintroduce the Store install link (#4592) 2020-02-14 14:58:11 -08:00
124 changed files with 4561 additions and 2879 deletions

View File

@@ -294,6 +294,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{D3EF
build\scripts\Test-WindowsTerminalPackage.ps1 = build\scripts\Test-WindowsTerminalPackage.ps1
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winconpty.Tests.Feature", "src\winconpty\ft_pty\winconpty.FeatureTests.vcxproj", "{024052DE-83FB-4653-AEA4-90790D29D5BD}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalAzBridge", "src\cascadia\TerminalAzBridge\TerminalAzBridge.vcxproj", "{067F0A06-FCB7-472C-96E9-B03B54E8E18D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
AuditMode|Any CPU = AuditMode|Any CPU
@@ -1419,6 +1423,34 @@ Global
{A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x64.Build.0 = Release|x64
{A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x86.ActiveCfg = Release|Win32
{A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x86.Build.0 = Release|Win32
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|Any CPU.ActiveCfg = Debug|Win32
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|ARM64.ActiveCfg = Debug|ARM64
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|ARM64.Build.0 = Debug|ARM64
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|x64.ActiveCfg = Debug|x64
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|x64.Build.0 = Debug|x64
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|x86.ActiveCfg = Debug|Win32
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|x86.Build.0 = Debug|Win32
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|Any CPU.ActiveCfg = Release|Win32
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|ARM64.ActiveCfg = Release|ARM64
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|ARM64.Build.0 = Release|ARM64
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|x64.ActiveCfg = Release|x64
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|x64.Build.0 = Release|x64
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|x86.ActiveCfg = Release|Win32
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|x86.Build.0 = Release|Win32
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|Any CPU.ActiveCfg = Debug|Win32
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|ARM64.ActiveCfg = Debug|ARM64
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|ARM64.Build.0 = Debug|ARM64
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|x64.ActiveCfg = Debug|x64
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|x64.Build.0 = Debug|x64
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|x86.ActiveCfg = Debug|Win32
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|x86.Build.0 = Debug|Win32
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|Any CPU.ActiveCfg = Release|Win32
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|ARM64.ActiveCfg = Release|ARM64
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|ARM64.Build.0 = Release|ARM64
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|x64.ActiveCfg = Release|x64
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|x64.Build.0 = Release|x64
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|x86.ActiveCfg = Release|Win32
{067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1493,6 +1525,8 @@ Global
{53DD5520-E64C-4C06-B472-7CE62CA539C9} = {04170EEF-983A-4195-BFEF-2321E5E38A1E}
{6B5A44ED-918D-4747-BFB1-2472A1FCA173} = {04170EEF-983A-4195-BFEF-2321E5E38A1E}
{D3EF7B96-CD5E-47C9-B9A9-136259563033} = {04170EEF-983A-4195-BFEF-2321E5E38A1E}
{024052DE-83FB-4653-AEA4-90790D29D5BD} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
{067F0A06-FCB7-472C-96E9-B03B54E8E18D} = {59840756-302F-44DF-AA47-441A9D673202}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271}

View File

@@ -17,7 +17,15 @@ Related repositories include:
> 👉 Note: Windows Terminal requires Windows 10 1903 (build 18362) or later
### Manually installing builds from this repository
### Microsoft Store [Recommended]
Install the [Windows Terminal from the Microsoft Store][store-install-link]. This allows you to always be on the latest version when we release new builds with automatic upgrades.
This is our preferred method.
### Other install methods
#### Via GitHub
For users who are unable to install Terminal from the Microsoft Store, Terminal builds can be manually downloaded from this repository's [Releases page](https://github.com/microsoft/terminal/releases).
@@ -26,7 +34,7 @@ For users who are unable to install Terminal from the Microsoft Store, Terminal
> * Be sure to install the [Desktop Bridge VC++ v14 Redistributable Package](https://www.microsoft.com/en-us/download/details.aspx?id=53175) otherwise Terminal may not install and/or run and may crash at startup
> * Terminal will not auto-update when new builds are released so you will need to regularly install the latest Terminal release to receive all the latest fixes and improvements!
### Install via Chocolatey (unofficial)
#### Via Chocolatey (unofficial)
[Chocolatey](https://chocolatey.org) users can download and install the latest Terminal release by installing the `microsoft-windows-terminal` package:
@@ -219,3 +227,4 @@ For more information see the [Code of Conduct FAQ][conduct-FAQ] or contact [open
[conduct-code]: https://opensource.microsoft.com/codeofconduct/
[conduct-FAQ]: https://opensource.microsoft.com/codeofconduct/faq/
[conduct-email]: mailto:opencode@microsoft.com
[store-install-link]: https://aka.ms/windowsterminal

View File

@@ -6,6 +6,7 @@ jobs:
steps:
- checkout: self
fetchDepth: 1
submodules: false
clean: true

View File

@@ -1,13 +1,38 @@
# How to build Openconsole
# How to build OpenConsole
Openconsole can be built with Visual Studio or from the command line. There are build scripts for both cmd and PowerShell in /tools.
This repository uses [git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules) for some of its dependencies. To make sure submodules are restored or updated, be sure to run the following prior to building:
```shell
git submodule update --init --recursive
```
OpenConsole.sln may be built from within Visual Studio or from the command-line using a set of convenience scripts & tools in the **/tools** directory:
When using Visual Studio, be sure to set up the path for code formatting. This can be done in Visual Studio by going to Tools > Options > Text Editor > C++ > Formatting and checking "Use custom clang-format.exe file" and choosing the clang-format.exe in the repository at /dep/llvm/clang-format.exe by clicking "browse" right under the check box.
## Building with cmd
### Building in PowerShell
The cmd scripts are set up to emulate a portion of the OS razzle build environment. razzle.cmd is the first script that should be run. bcz.cmd will build clean and bz.cmd should build incrementally.
```powershell
Import-Module .\tools\OpenConsole.psm1
Set-MsBuildDevEnvironment
Invoke-OpenConsoleBuild
```
There are a few additional exported functions (look at their documentation for further details):
- `Invoke-OpenConsoleBuild` - builds the solution. Can be passed msbuild arguments.
- `Invoke-OpenConsoleTests` - runs the various tests. Will run the unit tests by default.
- `Start-OpenConsole` - starts Openconsole.exe from the output directory. x64 is run by default.
- `Debug-OpenConsole` - starts Openconsole.exe and attaches it to the default debugger. x64 is run by default.
- `Invoke-CodeFormat` - uses clang-format to format all c++ files to match our coding style.
### Building in Cmd
```shell
.\tools\razzle.cmd
bcz
```
There are also scripts for running the tests:
- `runut.cmd` - run the unit tests
@@ -15,15 +40,13 @@ There are also scripts for running the tests:
- `runuia.cmd` - run the UIA tests
- `runformat` - uses clang-format to format all c++ files to match our coding style.
## Build with Powershell
## Running & Debugging
Openconsole.psm1 should be loaded with `Import-Module`. From there `Set-MsbuildDevEnvironment` will set up environment variables required to build. There are a few exported functions (look at their documentation for further details):
To debug the Windows Terminal in VS, right click on `CascadiaPackage` (in the Solution Explorer) and go to properties. In the Debug menu, change "Application process" and "Background task process" to "Native Only".
- `Invoke-OpenConsolebuild` - builds the solution. Can be passed msbuild arguments.
- `Invoke-OpenConsoleTests` - runs the various tests. Will run the unit tests by default.
- `Start-OpenConsole` - starts Openconsole.exe from the output directory. x64 is run by default.
- `Debug-OpenConsole` - starts Openconsole.exe and attaches it to the default debugger. x64 is run by default.
- `Invoke-CodeFormat` - uses clang-format to format all c++ files to match our coding style.
You should then be able to build & debug the Terminal project by hitting <kbd>F5</kbd>.
> 👉 You will _not_ be able to launch the Terminal directly by running the WindowsTerminal.exe. For more details on why, see [#926](https://github.com/microsoft/terminal/issues/926), [#4043](https://github.com/microsoft/terminal/issues/4043)
## Configuration Types

View File

@@ -29,6 +29,7 @@ Properties listed below are specific to each unique profile.
| `guid` | _Required_ | String | | Unique identifier of the profile. Written in registry format: `"{00000000-0000-0000-0000-000000000000}"`. |
| `name` | _Required_ | String | | Name of the profile. Displays in the dropdown menu. <br>Additionally, this value will be used as the "title" to pass to the shell on startup. Some shells (like `bash`) may choose to ignore this initial value, while others (`cmd`, `powershell`) may use this value over the lifetime of the application. This "title" behavior can be overridden by using `tabTitle`. |
| `acrylicOpacity` | Optional | Number | `0.5` | When `useAcrylic` is set to `true`, it sets the transparency of the window for the profile. Accepts floating point values from 0-1. |
| `antialiasingMode` | Optional | String | `"grayscale"` | Controls how text is antialiased in the renderer. Possible values are "grayscale", "cleartype" and "aliased". Note that changing this setting will require starting a new terminal instance. |
| `background` | Optional | String | | Sets the background color of the profile. Overrides `background` set in color scheme if `colorscheme` is set. Uses hex color format: `"#rrggbb"`. |
| `backgroundImage` | Optional | String | | Sets the file location of the Image to draw over the window background. |
| `backgroundImageAlignment` | Optional | String | `center` | Sets how the background image aligns to the boundaries of the window. Possible values: `"center"`, `"left"`, `"top"`, `"right"`, `"bottom"`, `"topLeft"`, `"topRight"`, `"bottomLeft"`, `"bottomRight"` |

View File

@@ -3,6 +3,10 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Microsoft's Windows Terminal Settings Profile Schema'",
"definitions": {
"KeyChordSegment": {
"pattern": "^(?<modifier>(ctrl|alt|shift)\\+?((ctrl|alt|shift)(?<!\\2)\\+?)?((ctrl|alt|shift)(?<!\\2|\\4))?\\+?)?(?<key>[^+\\s]+?)?(?<=[^+\\s])$",
"type": "string"
},
"Color": {
"default": "#",
"pattern": "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$",
@@ -244,12 +248,18 @@
},
"keys": {
"description": "Defines the key combinations used to call the command.",
"items": {
"pattern": "^(?<modifier>(ctrl|alt|shift)\\+?((ctrl|alt|shift)(?<!\\2)\\+?)?((ctrl|alt|shift)(?<!\\2|\\4))?\\+?)?(?<key>[^+\\s]+?)?(?<=[^+\\s])$",
"type": "string"
},
"minItems": 1,
"type": "array"
"oneOf": [
{
"$ref": "#/definitions/KeyChordSegment"
},
{
"items": {
"$ref": "#/definitions/KeyChordSegment"
},
"minItems": 1,
"type": "array"
}
]
}
},
"required": [
@@ -378,6 +388,16 @@
"minimum": 0,
"type": "number"
},
"antialiasingMode": {
"default": "grayscale",
"description": "Controls how text is antialiased in the renderer. Possible values are \"grayscale\", \"cleartype\" and \"aliased\". Note that changing this setting will require starting a new terminal instance.",
"enum": [
"grayscale",
"cleartype",
"aliased"
],
"type": "string"
},
"background": {
"$ref": "#/definitions/Color",
"default": "#0c0c0c",

View File

@@ -15,7 +15,7 @@ Assuming that you've installed Anaconda into `%USERPROFILE%\Anaconda3`:
```json
{
"commandline" : "cmd.exe /K %USERPROFILE%\\Anaconda3\\Scripts\\activate.bat %USERPROFILE%\\Anaconda3",
"commandline" : "cmd.exe /k \"%USERPROFILE%\\Anaconda3\\Scripts\\activate.bat %USERPROFILE%\\Anaconda3\"",
"icon" : "%USERPROFILE%/Anaconda3/Menu/anaconda-navigator.ico",
"name" : "Anaconda3",
"startingDirectory" : "%USERPROFILE%"
@@ -28,7 +28,7 @@ Assuming that you've installed cmder into `%CMDER_ROOT%`:
```json
{
"commandline" : "cmd.exe /k %CMDER_ROOT%\\vendor\\init.bat",
"commandline" : "cmd.exe /k \"%CMDER_ROOT%\\vendor\\init.bat\"",
"name" : "cmder",
"startingDirectory" : "%USERPROFILE%"
}

View File

@@ -22,6 +22,7 @@ pass, and gives some examples of how to use the `wt` commandline.
### Options
#### `--help,-h,-?,/?,`
Display the help message.
## Subcommands
@@ -35,8 +36,8 @@ opens a new window. Subsequent `new-tab` commands will all open new tabs in the
same window.
**Parameters**:
* `[terminal_parameters]`: See [[terminal_parameters]](#terminal_parameters).
* `[terminal_parameters]`: See [[terminal_parameters]](#terminal_parameters).
#### `split-pane`
@@ -46,6 +47,7 @@ Creates a new pane in the currently focused tab by splitting the given pane
vertically or horizontally.
**Parameters**:
* `--target,-t target-pane`: Creates a new split in the given `target-pane`.
Each pane has a unique index (per-tab) which can be used to identify them.
These indicies are assigned in the order the panes were created. If omitted,
@@ -72,7 +74,6 @@ Moves focus to a given tab.
* `-p,--previous`: Move focus to the previous tab. Will display an error if
combined with either of `--next` or `--target`.
#### `[terminal_parameters]`
Some of the preceding commands are used to create a new terminal instance.
@@ -92,12 +93,11 @@ following:
selected profile. If the user wants to use a `;` in this commandline, it
should be escaped as `\;`.
## Examples
### Open Windows Terminal in the current directory
```
```powershell
wt -d .
```
@@ -114,11 +114,12 @@ the `split-pane` command to create new panes.
Consider the following commandline:
```
```powershell
wt ; split-pane -p "Windows PowerShell" ; split-pane -H wsl.exe
```
This creates a new Windows Terminal window with one tab, and 3 panes:
* `wt`: Creates the new tab with the default profile
* `split-pane -p "Windows PowerShell"`: This will create a new pane, split from
the parent with the default profile. This pane will open with the "Windows

View File

@@ -54,8 +54,8 @@ object under a root property `"globals"`.
This is an array of key chords and shortcuts to invoke various commands.
Each command can have more than one key binding.
NOTE: Key bindings is a subfield of the global settings and
key bindings apply to all profiles in the same manner.
> 👉 **Note**: Key bindings is a subfield of the global settings and
> key bindings apply to all profiles in the same manner.
For example, here's a sample of the default keybindings:
@@ -69,9 +69,26 @@ For example, here's a sample of the default keybindings:
// etc.
]
}
```
You can also use a single key chord string as the value of `"keys"`.
It will be treated as a chord of length one.
This will allow you to simplify the above snippet as follows:
```json
{
"keybindings":
[
{ "command": "closePane", "keys": "ctrl+shift+w" },
{ "command": "copy", "keys": "ctrl+shift+c" },
{ "command": "newTab", "keys": "ctrl+shift+t" },
// etc.
]
}
```
### Unbinding keys
If you ever come across a key binding that you're unhappy with, it's possible to
@@ -136,7 +153,7 @@ the property `"hidden": true` to the profile's json. This can also be used to
remove the default `cmd` and PowerShell profiles, if the user does not wish to
see them.
## Color Schemes
## Color Schemes
Each scheme defines the color values to be used for various terminal escape sequences.
Each schema is identified by the name field. Examples include
@@ -159,6 +176,7 @@ The schema name can then be referenced in one or more profiles.
## Settings layering
The runtime settings are actually constructed from _three_ sources:
* The default settings, which are hardcoded into the application, and available
in `defaults.json`. This includes the default keybindings, color schemes, and
profiles for both Windows PowerShell and Command Prompt (`cmd.exe`).
@@ -287,6 +305,7 @@ properties for all your profiles, like so:
Note that the `profiles` property has changed in this example from a _list_ of
profiles, to an _object_ with two properties:
* a `list` that contains the list of all the profiles
* the new `defaults` object, which contains all the settings that should apply to
every profile.
@@ -329,12 +348,11 @@ could achieve that with the following:
In the above settings, the `"fontFace"` in the `cmd.exe` profile overrides the
`"fontFace"` from the `defaults`.
## Configuration Examples:
## Configuration Examples
### Add a custom background to the WSL Debian terminal profile
1. Download the Debian JPG logo https://www.debian.org/logos/openlogo-100.jpg
1. Download the [Debian JPG logo](https://www.debian.org/logos/openlogo-100.jpg)
2. Put the image in the
`$env:LocalAppData\Packages\Microsoft.WindowsTerminal_<randomString>\LocalState\`
directory (same directory as your `profiles.json` file).
@@ -342,17 +360,20 @@ In the above settings, the `"fontFace"` in the `cmd.exe` profile overrides the
__NOTE__: You can put the image anywhere you like, the above suggestion happens to be convenient.
3. Open your WT json properties file.
4. Under the Debian Linux profile, add the following fields:
```json
"backgroundImage": "ms-appdata:///Local/openlogo-100.jpg",
"backgroundImageOpacity": 1,
"backgroundImageStretchMode" : "none",
"backgroundImageAlignment" : "topRight",
```
5. Make sure that `useAcrylic` is `false`.
6. Save the file.
7. Jump over to WT and verify your changes.
Notes:
1. You will need to experiment with different color settings
and schemes to make your terminal text visible on top of your image
2. If you store the image in the UWP directory (the same directory as your profiles.json file),
@@ -414,7 +435,6 @@ no text selection. Additionally, if you set `paste` to `"ctrl+v"`, commandline
applications won't be able to read a ctrl+v from the input. For these reasons,
we suggest `"ctrl+shift+c"` and `"ctrl+shift+v"`
### Setting the `startingDirectory` of WSL Profiles to `~`
By default, the `startingDirectory` of a profile is `%USERPROFILE%`

View File

@@ -75,14 +75,14 @@ To open the settings file from Windows Terminal:
For an introduction to the various settings, see [Using Json Settings](UsingJsonSettings.md). The list of valid settings can be found in the [profiles.json documentation](../cascadia/SettingsSchema.md) section.
## Tips and Tricks:
## Tips and Tricks
1. In PowerShell you can discover if the Windows Terminal is being used by checking for the existence of the environment variable `WT_SESSION`.
Under pwsh you can also use
`(Get-Process -Id $pid).Parent.ProcessName -eq 'WindowsTerminal'`
(ref https://twitter.com/r_keith_hill/status/1142871145852440576)
(ref [https://twitter.com/r_keith_hill/status/1142871145852440576](https://twitter.com/r_keith_hill/status/1142871145852440576))
2. Terminal zoom can be changed by holding <kbd>Ctrl</kbd> and scrolling with mouse.
3. If `useAcrylic` is enabled in profiles.json, background opacity can be changed by holding <kbd>Ctrl</kbd>+<kbd>Shift</kbd> and scrolling with mouse. Note that acrylic transparency is limited by the OS only to focused windows.

View File

@@ -1314,6 +1314,101 @@ TextBuffer::DelimiterClass TextBuffer::_GetDelimiterClass(const std::wstring_vie
}
}
// Method Description:
// - Determines the line-by-line rectangles based on two COORDs
// - expands the rectangles to support wide glyphs
// - used for selection rects and UIA bounding rects
// Arguments:
// - start: a corner of the text region of interest (inclusive)
// - end: the other corner of the text region of interest (inclusive)
// - blockSelection: when enabled, only get the rectangular text region,
// as opposed to the text extending to the left/right
// buffer margins
// Return Value:
// - the delimiter class for the given char
const std::vector<SMALL_RECT> TextBuffer::GetTextRects(COORD start, COORD end, bool blockSelection) const
{
std::vector<SMALL_RECT> textRects;
const auto bufferSize = GetSize();
// (0,0) is the top-left of the screen
// the physically "higher" coordinate is closer to the top-left
// the physically "lower" coordinate is closer to the bottom-right
const auto [higherCoord, lowerCoord] = bufferSize.CompareInBounds(start, end) <= 0 ?
std::make_tuple(start, end) :
std::make_tuple(end, start);
const auto textRectSize = base::ClampedNumeric<short>(1) + lowerCoord.Y - higherCoord.Y;
textRects.reserve(textRectSize);
for (auto row = higherCoord.Y; row <= lowerCoord.Y; row++)
{
SMALL_RECT textRow;
textRow.Top = row;
textRow.Bottom = row;
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);
}
else
{
textRow.Left = (row == higherCoord.Y) ? higherCoord.X : bufferSize.Left();
textRow.Right = (row == lowerCoord.Y) ? lowerCoord.X : bufferSize.RightInclusive();
}
_ExpandTextRow(textRow);
textRects.emplace_back(textRow);
}
return textRects;
}
// Method Description:
// - Expand the selection row according to include wide glyphs fully
// - this is particularly useful for box selections (ALT + selection)
// Arguments:
// - selectionRow: the selection row to be expanded
// Return Value:
// - modifies selectionRow's Left and Right values to expand properly
void TextBuffer::_ExpandTextRow(SMALL_RECT& textRow) const
{
const auto bufferSize = GetSize();
// expand left side of rect
COORD targetPoint{ textRow.Left, textRow.Top };
if (GetCellDataAt(targetPoint)->DbcsAttr().IsTrailing())
{
if (targetPoint.X == bufferSize.Left())
{
bufferSize.IncrementInBounds(targetPoint);
}
else
{
bufferSize.DecrementInBounds(targetPoint);
}
textRow.Left = targetPoint.X;
}
// expand right side of rect
targetPoint = { textRow.Right, textRow.Bottom };
if (GetCellDataAt(targetPoint)->DbcsAttr().IsLeading())
{
if (targetPoint.X == bufferSize.RightInclusive())
{
bufferSize.DecrementInBounds(targetPoint);
}
else
{
bufferSize.IncrementInBounds(targetPoint);
}
textRow.Right = targetPoint.X;
}
}
// Routine Description:
// - Retrieves the text data from the selected region and presents it in a clipboard-ready format (given little post-processing).
// Arguments:

View File

@@ -135,6 +135,8 @@ public:
bool MoveToNextWord(COORD& pos, const std::wstring_view wordDelimiters, COORD lastCharPos) const;
bool MoveToPreviousWord(COORD& pos, const std::wstring_view wordDelimiters) const;
const std::vector<SMALL_RECT> GetTextRects(COORD start, COORD end, bool blockSelection = false) const;
class TextAndColor
{
public:
@@ -193,6 +195,8 @@ private:
ROW& _GetFirstRow();
ROW& _GetPrevRowNoWrap(const ROW& row);
void _ExpandTextRow(SMALL_RECT& selectionRow) const;
enum class DelimiterClass
{
ControlChar,

View File

@@ -43,7 +43,8 @@
<!-- Resources -->
<!-- This resw only defines things that are used in this package's AppxManifest,
so it's not in the common resource items. -->
<PRIResource Include="Resources\en-US\Resources.resw" />
<PRIResource Include="Resources\Resources.language-en.resw" />
<PRIResource Include="Resources\Resources.resw" />
</ItemGroup>
<PropertyGroup Condition="'$(WindowsTerminalReleaseBuild)'!='true'">
<!-- This is picked up by CascadiaResources.build.items. -->
@@ -54,6 +55,7 @@
<ItemGroup>
<ProjectReference Include="..\WindowsTerminal\WindowsTerminal.vcxproj" />
<ProjectReference Include="..\..\host\exe\Host.EXE.vcxproj" />
<ProjectReference Include="..\TerminalAzBridge\TerminalAzBridge.vcxproj" />
</ItemGroup>
<Target Name="OpenConsoleStompSourceProjectForWapProject" BeforeTargets="_ConvertItems">
<ItemGroup>
@@ -91,9 +93,7 @@
roll up our subproject resources. We have to suppress that rule but keep part of its logic, because that rule is
where the AppxPackagePayload items are created. -->
<PropertyGroup>
<!-- Only for MSBuild versions <= 16.4.0 -->
<!-- TODO: Change this to hard less than once the 16.4.0 previews fix the bug. -->
<WapProjBeforeGenerateAppxManifestDependsOn Condition="$(MSBuildVersion) &lt;= '16.4.0'">
<WapProjBeforeGenerateAppxManifestDependsOn>
$([MSBuild]::Unescape('$(WapProjBeforeGenerateAppxManifestDependsOn.Replace('_RemoveAllNonWapUWPItems', '_OpenConsoleRemoveAllNonWapUWPItems'))'))
</WapProjBeforeGenerateAppxManifestDependsOn>
</PropertyGroup>

View File

@@ -120,19 +120,7 @@
<data name="AppDescription" xml:space="preserve">
<value>The New Windows Terminal</value>
</data>
<data name="AppName" xml:space="preserve">
<value>Windows Terminal (Preview)</value>
</data>
<data name="AppDescriptionDev" xml:space="preserve">
<value>The Windows Terminal, but Unofficial</value>
</data>
<data name="AppNameDev" xml:space="preserve">
<value>Windows Terminal (Dev Build)</value>
</data>
<data name="AppShortName" xml:space="preserve">
<value>Terminal</value>
</data>
<data name="AppShortNameDev" xml:space="preserve">
<value>Terminal (Dev)</value>
</data>
</root>
</root>

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AppName" xml:space="preserve">
<value>Windows Terminal (Preview)</value>
</data>
<data name="AppNameDev" xml:space="preserve">
<value>Windows Terminal (Dev Build)</value>
</data>
<data name="AppShortName" xml:space="preserve">
<value>Terminal</value>
</data>
<data name="AppShortNameDev" xml:space="preserve">
<value>Terminal (Dev)</value>
</data>
</root>

View File

@@ -42,6 +42,8 @@ namespace TerminalAppLocalTests
TEST_METHOD(TestArbitraryArgs);
TEST_METHOD(TestSplitPaneArgs);
TEST_METHOD(TestStringOverload);
TEST_CLASS_SETUP(ClassSetup)
{
InitializeJsonReader();
@@ -458,4 +460,27 @@ namespace TerminalAppLocalTests
}
}
void KeyBindingsTests::TestStringOverload()
{
const std::string bindings0String{ R"([
{ "command": "copy", "keys": "ctrl+c" }
])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto appKeyBindings = winrt::make_self<implementation::AppKeyBindings>();
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(1u, appKeyBindings->_keyShortcuts.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_TRUE(realArgs.TrimWhitespace());
}
}
}

View File

@@ -3,6 +3,8 @@
#include "pch.h"
#include "HwndTerminal.hpp"
#include <windowsx.h>
#include "../../types/TermControlUiaProvider.hpp"
#include <DefaultSettings.h>
#include "../../renderer/base/Renderer.hpp"
#include "../../renderer/dx/DxRenderer.hpp"
@@ -14,13 +16,54 @@ using namespace ::Microsoft::Terminal::Core;
static LPCWSTR term_window_class = L"HwndTerminalClass";
static LRESULT CALLBACK HwndTerminalWndProc(
LRESULT CALLBACK HwndTerminal::HwndTerminalWndProc(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam) noexcept
{
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
#pragma warning(suppress : 26490) // Win32 APIs can only store void*, have to use reinterpret_cast
HwndTerminal* terminal = reinterpret_cast<HwndTerminal*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
if (terminal)
{
switch (uMsg)
{
case WM_GETOBJECT:
if (lParam == UiaRootObjectId)
{
return UiaReturnRawElementProvider(hwnd, wParam, lParam, terminal->_GetUiaProvider());
}
break;
case WM_LBUTTONDOWN:
LOG_IF_FAILED(terminal->_StartSelection(lParam));
return 0;
case WM_MOUSEMOVE:
if (WI_IsFlagSet(wParam, MK_LBUTTON))
{
LOG_IF_FAILED(terminal->_MoveSelection(lParam));
return 0;
}
break;
case WM_RBUTTONDOWN:
if (terminal->_terminal->IsSelectionActive())
{
try
{
const auto bufferData = terminal->_terminal->RetrieveSelectedTextFromBuffer(false);
LOG_IF_FAILED(terminal->_CopyTextToSystemClipboard(bufferData, true));
terminal->_terminal->ClearSelection();
}
CATCH_LOG();
}
else
{
terminal->_PasteTextFromClipboard();
}
return 0;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
static bool RegisterTermClass(HINSTANCE hInstance) noexcept
@@ -32,7 +75,7 @@ static bool RegisterTermClass(HINSTANCE hInstance) noexcept
}
wc.style = 0;
wc.lpfnWndProc = HwndTerminalWndProc;
wc.lpfnWndProc = HwndTerminal::HwndTerminalWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
@@ -47,7 +90,11 @@ static bool RegisterTermClass(HINSTANCE hInstance) noexcept
HwndTerminal::HwndTerminal(HWND parentHwnd) :
_desiredFont{ DEFAULT_FONT_FACE, 0, 10, { 0, 14 }, CP_UTF8 },
_actualFont{ DEFAULT_FONT_FACE, 0, 10, { 0, 14 }, CP_UTF8, false }
_actualFont{ DEFAULT_FONT_FACE, 0, 10, { 0, 14 }, CP_UTF8, false },
_uiaProvider{ nullptr },
_uiaProviderInitialized{ false },
_currentDpi{ USER_DEFAULT_SCREEN_DPI },
_pfnWriteCallback{ nullptr }
{
HINSTANCE hInstance = wil::GetModuleInstanceHandle();
@@ -69,6 +116,9 @@ HwndTerminal::HwndTerminal(HWND parentHwnd) :
nullptr,
hInstance,
nullptr));
#pragma warning(suppress : 26490) // Win32 APIs can only store void*, have to use reinterpret_cast
SetWindowLongPtr(_hwnd.get(), GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
}
}
@@ -107,7 +157,7 @@ HRESULT HwndTerminal::Initialize()
_terminal->Create(COORD{ 80, 25 }, 1000, *_renderer);
_terminal->SetDefaultBackground(RGB(5, 27, 80));
_terminal->SetDefaultForeground(RGB(255, 255, 255));
_terminal->SetWriteInputCallback([=](std::wstring & input) noexcept { _WriteTextToConnection(input); });
localPointerToThread->EnablePainting();
return S_OK;
@@ -118,31 +168,39 @@ void HwndTerminal::RegisterScrollCallback(std::function<void(int, int, int)> cal
_terminal->SetScrollPositionChangedCallback(callback);
}
void HwndTerminal::_WriteTextToConnection(const std::wstring& input) noexcept
{
if (!_pfnWriteCallback)
{
return;
}
try
{
auto callingText{ wil::make_cotaskmem_string(input.data(), input.size()) };
_pfnWriteCallback(callingText.release());
}
CATCH_LOG();
}
void HwndTerminal::RegisterWriteCallback(const void _stdcall callback(wchar_t*))
{
_terminal->SetWriteInputCallback([=](std::wstring & input) noexcept {
const wchar_t* text = input.c_str();
const size_t textChars = wcslen(text) + 1;
const size_t textBytes = textChars * sizeof(wchar_t);
wchar_t* callingText = nullptr;
_pfnWriteCallback = callback;
}
callingText = static_cast<wchar_t*>(::CoTaskMemAlloc(textBytes));
::Microsoft::Console::Types::IUiaData* HwndTerminal::GetUiaData() const noexcept
{
return _terminal.get();
}
if (callingText == nullptr)
{
callback(nullptr);
}
else
{
wcscpy_s(callingText, textChars, text);
callback(callingText);
}
});
HWND HwndTerminal::GetHwnd() const noexcept
{
return _hwnd.get();
}
void HwndTerminal::_UpdateFont(int newDpi)
{
_currentDpi = newDpi;
auto lock = _terminal->LockForWriting();
// TODO: MSFT:20895307 If the font doesn't exist, this doesn't
@@ -150,6 +208,33 @@ void HwndTerminal::_UpdateFont(int newDpi)
_renderer->TriggerFontChange(newDpi, _desiredFont, _actualFont);
}
IRawElementProviderSimple* HwndTerminal::_GetUiaProvider() noexcept
{
if (nullptr == _uiaProvider && !_uiaProviderInitialized)
{
std::unique_lock<std::shared_mutex> lock;
try
{
#pragma warning(suppress : 26441) // The lock is named, this appears to be a false positive
lock = _terminal->LockForWriting();
if (_uiaProviderInitialized)
{
return _uiaProvider.Get();
}
LOG_IF_FAILED(::Microsoft::WRL::MakeAndInitialize<::Microsoft::Terminal::TermControlUiaProvider>(&_uiaProvider, this->GetUiaData(), this));
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
_uiaProvider = nullptr;
}
_uiaProviderInitialized = true;
}
return _uiaProvider.Get();
}
HRESULT HwndTerminal::Refresh(const SIZE windowSize, _Out_ COORD* dimensions)
{
RETURN_HR_IF_NULL(E_INVALIDARG, dimensions);
@@ -186,10 +271,29 @@ void HwndTerminal::SendOutput(std::wstring_view data)
HRESULT _stdcall CreateTerminal(HWND parentHwnd, _Out_ void** hwnd, _Out_ void** terminal)
{
auto _terminal = std::make_unique<HwndTerminal>(parentHwnd);
// In order for UIA to hook up properly there needs to be a "static" window hosting the
// inner win32 control. If the static window is not present then WM_GETOBJECT messages
// will not reach the child control, and the uia element will not be present in the tree.
auto _hostWindow = CreateWindowEx(
0,
L"static",
nullptr,
WS_CHILD |
WS_CLIPCHILDREN |
WS_CLIPSIBLINGS |
WS_VISIBLE,
0,
0,
0,
0,
parentHwnd,
nullptr,
nullptr,
0);
auto _terminal = std::make_unique<HwndTerminal>(_hostWindow);
RETURN_IF_FAILED(_terminal->Initialize());
*hwnd = _terminal->_hwnd.get();
*hwnd = _hostWindow;
*terminal = _terminal.release();
return S_OK;
@@ -217,6 +321,15 @@ HRESULT _stdcall TerminalTriggerResize(void* terminal, double width, double heig
{
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
LOG_IF_WIN32_BOOL_FALSE(SetWindowPos(
publicTerminal->GetHwnd(),
nullptr,
0,
0,
static_cast<int>(width),
static_cast<int>(height),
0));
const SIZE windowSize{ static_cast<short>(width), static_cast<short>(height) };
return publicTerminal->Refresh(windowSize, dimensions);
}
@@ -233,45 +346,54 @@ void _stdcall TerminalUserScroll(void* terminal, int viewTop)
publicTerminal->_terminal->UserScrollViewport(viewTop);
}
HRESULT _stdcall TerminalStartSelection(void* terminal, COORD cursorPosition, bool altPressed)
HRESULT HwndTerminal::_StartSelection(LPARAM lParam) noexcept
try
{
COORD terminalPosition = { cursorPosition };
const bool altPressed = GetKeyState(VK_MENU) < 0;
COORD cursorPosition{
GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam),
};
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
const auto fontSize = publicTerminal->_actualFont.GetSize();
const auto fontSize = this->_actualFont.GetSize();
RETURN_HR_IF(E_NOT_VALID_STATE, fontSize.X == 0);
RETURN_HR_IF(E_NOT_VALID_STATE, fontSize.Y == 0);
terminalPosition.X /= fontSize.X;
terminalPosition.Y /= fontSize.Y;
cursorPosition.X /= fontSize.X;
cursorPosition.Y /= fontSize.Y;
publicTerminal->_terminal->SetSelectionAnchor(terminalPosition);
publicTerminal->_terminal->SetBoxSelection(altPressed);
this->_terminal->SetSelectionAnchor(cursorPosition);
this->_terminal->SetBlockSelection(altPressed);
publicTerminal->_renderer->TriggerSelection();
this->_renderer->TriggerSelection();
return S_OK;
}
CATCH_RETURN();
HRESULT _stdcall TerminalMoveSelection(void* terminal, COORD cursorPosition)
HRESULT HwndTerminal::_MoveSelection(LPARAM lParam) noexcept
try
{
COORD terminalPosition = { cursorPosition };
COORD cursorPosition{
GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam),
};
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
const auto fontSize = publicTerminal->_actualFont.GetSize();
const auto fontSize = this->_actualFont.GetSize();
RETURN_HR_IF(E_NOT_VALID_STATE, fontSize.X == 0);
RETURN_HR_IF(E_NOT_VALID_STATE, fontSize.Y == 0);
terminalPosition.X /= fontSize.X;
terminalPosition.Y /= fontSize.Y;
cursorPosition.X /= fontSize.X;
cursorPosition.Y /= fontSize.Y;
publicTerminal->_terminal->SetEndSelectionPosition(terminalPosition);
publicTerminal->_renderer->TriggerSelection();
this->_terminal->SetSelectionEnd(cursorPosition);
this->_renderer->TriggerSelection();
return S_OK;
}
CATCH_RETURN();
void _stdcall TerminalClearSelection(void* terminal)
{
@@ -413,3 +535,173 @@ void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible)
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
publicTerminal->_terminal->SetCursorVisible(visible);
}
// Routine Description:
// - Copies the text given onto the global system clipboard.
// Arguments:
// - rows - Rows of text data to copy
// - fAlsoCopyFormatting - true if the color and formatting should also be copied, false otherwise
HRESULT HwndTerminal::_CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, bool const fAlsoCopyFormatting)
{
std::wstring finalString;
// Concatenate strings into one giant string to put onto the clipboard.
for (const auto& str : rows.text)
{
finalString += str;
}
// allocate the final clipboard data
const size_t cchNeeded = finalString.size() + 1;
const size_t cbNeeded = sizeof(wchar_t) * cchNeeded;
wil::unique_hglobal globalHandle(GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, cbNeeded));
RETURN_LAST_ERROR_IF_NULL(globalHandle.get());
PWSTR pwszClipboard = static_cast<PWSTR>(GlobalLock(globalHandle.get()));
RETURN_LAST_ERROR_IF_NULL(pwszClipboard);
// The pattern gets a bit strange here because there's no good wil built-in for global lock of this type.
// Try to copy then immediately unlock. Don't throw until after (so the hglobal won't be freed until we unlock).
const HRESULT hr = StringCchCopyW(pwszClipboard, cchNeeded, finalString.data());
GlobalUnlock(globalHandle.get());
RETURN_IF_FAILED(hr);
// Set global data to clipboard
RETURN_LAST_ERROR_IF(!OpenClipboard(_hwnd.get()));
{ // Clipboard Scope
auto clipboardCloser = wil::scope_exit([]() noexcept {
LOG_LAST_ERROR_IF(!CloseClipboard());
});
RETURN_LAST_ERROR_IF(!EmptyClipboard());
RETURN_LAST_ERROR_IF_NULL(SetClipboardData(CF_UNICODETEXT, globalHandle.get()));
if (fAlsoCopyFormatting)
{
const auto& fontData = _actualFont;
int const iFontHeightPoints = fontData.GetUnscaledSize().Y * 72 / this->_currentDpi;
const COLORREF bgColor = _terminal->GetBackgroundColor(_terminal->GetDefaultBrushColors());
std::string HTMLToPlaceOnClip = TextBuffer::GenHTML(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor, "Hwnd Console Host");
_CopyToSystemClipboard(HTMLToPlaceOnClip, L"HTML Format");
std::string RTFToPlaceOnClip = TextBuffer::GenRTF(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor);
_CopyToSystemClipboard(RTFToPlaceOnClip, L"Rich Text Format");
}
}
// only free if we failed.
// the memory has to remain allocated if we successfully placed it on the clipboard.
// Releasing the smart pointer will leave it allocated as we exit scope.
globalHandle.release();
return S_OK;
}
// Routine Description:
// - Copies the given string onto the global system clipboard in the specified format
// Arguments:
// - stringToCopy - The string to copy
// - lpszFormat - the name of the format
HRESULT HwndTerminal::_CopyToSystemClipboard(std::string stringToCopy, LPCWSTR lpszFormat)
{
const size_t cbData = stringToCopy.size() + 1; // +1 for '\0'
if (cbData)
{
wil::unique_hglobal globalHandleData(GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, cbData));
RETURN_LAST_ERROR_IF_NULL(globalHandleData.get());
PSTR pszClipboardHTML = static_cast<PSTR>(GlobalLock(globalHandleData.get()));
RETURN_LAST_ERROR_IF_NULL(pszClipboardHTML);
// The pattern gets a bit strange here because there's no good wil built-in for global lock of this type.
// Try to copy then immediately unlock. Don't throw until after (so the hglobal won't be freed until we unlock).
const HRESULT hr2 = StringCchCopyA(pszClipboardHTML, cbData, stringToCopy.data());
GlobalUnlock(globalHandleData.get());
RETURN_IF_FAILED(hr2);
UINT const CF_FORMAT = RegisterClipboardFormatW(lpszFormat);
RETURN_LAST_ERROR_IF(0 == CF_FORMAT);
RETURN_LAST_ERROR_IF_NULL(SetClipboardData(CF_FORMAT, globalHandleData.get()));
// only free if we failed.
// the memory has to remain allocated if we successfully placed it on the clipboard.
// Releasing the smart pointer will leave it allocated as we exit scope.
globalHandleData.release();
}
return S_OK;
}
void HwndTerminal::_PasteTextFromClipboard() noexcept
{
// Get paste data from clipboard
if (!OpenClipboard(_hwnd.get()))
{
return;
}
HANDLE ClipboardDataHandle = GetClipboardData(CF_UNICODETEXT);
if (ClipboardDataHandle == nullptr)
{
CloseClipboard();
return;
}
PCWCH pwstr = static_cast<PCWCH>(GlobalLock(ClipboardDataHandle));
_StringPaste(pwstr);
GlobalUnlock(ClipboardDataHandle);
CloseClipboard();
}
void HwndTerminal::_StringPaste(const wchar_t* const pData) noexcept
{
if (pData == nullptr)
{
return;
}
try
{
std::wstring text(pData);
_WriteTextToConnection(text);
}
CATCH_LOG();
}
COORD HwndTerminal::GetFontSize() const
{
return _actualFont.GetSize();
}
RECT HwndTerminal::GetBounds() const noexcept
{
RECT windowRect;
GetWindowRect(_hwnd.get(), &windowRect);
return windowRect;
}
RECT HwndTerminal::GetPadding() const noexcept
{
return { 0 };
}
double HwndTerminal::GetScaleFactor() const noexcept
{
return static_cast<double>(_currentDpi) / static_cast<double>(USER_DEFAULT_SCREEN_DPI);
}
void HwndTerminal::ChangeViewport(const SMALL_RECT NewWindow)
{
_terminal->UserScrollViewport(NewWindow.Top);
}
HRESULT HwndTerminal::GetHostUiaProvider(IRawElementProviderSimple** provider) noexcept
{
return UiaHostProviderFromHwnd(_hwnd.get(), provider);
}

View File

@@ -6,6 +6,9 @@
#include "../../renderer/base/Renderer.hpp"
#include "../../renderer/dx/DxRenderer.hpp"
#include "../../cascadia/TerminalCore/Terminal.hpp"
#include <UIAutomationCore.h>
#include "../../types/IControlAccessibilityInfo.h"
#include "../../types/TermControlUiaProvider.hpp"
using namespace Microsoft::Console::VirtualTerminal;
@@ -25,8 +28,6 @@ __declspec(dllexport) HRESULT _stdcall TerminalTriggerResize(void* terminal, dou
__declspec(dllexport) HRESULT _stdcall TerminalResize(void* terminal, COORD dimensions);
__declspec(dllexport) void _stdcall TerminalDpiChanged(void* terminal, int newDpi);
__declspec(dllexport) void _stdcall TerminalUserScroll(void* terminal, int viewTop);
__declspec(dllexport) HRESULT _stdcall TerminalStartSelection(void* terminal, COORD cursorPosition, bool altPressed);
__declspec(dllexport) HRESULT _stdcall TerminalMoveSelection(void* terminal, COORD cursorPosition);
__declspec(dllexport) void _stdcall TerminalClearSelection(void* terminal);
__declspec(dllexport) const wchar_t* _stdcall TerminalGetSelection(void* terminal);
__declspec(dllexport) bool _stdcall TerminalIsSelectionActive(void* terminal);
@@ -39,20 +40,35 @@ __declspec(dllexport) void _stdcall TerminalBlinkCursor(void* terminal);
__declspec(dllexport) void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);
};
struct HwndTerminal
struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo
{
public:
HwndTerminal(HWND hwnd);
HwndTerminal(const HwndTerminal&) = default;
HwndTerminal(HwndTerminal&&) = default;
HwndTerminal& operator=(const HwndTerminal&) = default;
HwndTerminal& operator=(HwndTerminal&&) = default;
~HwndTerminal() = default;
HRESULT Initialize();
void SendOutput(std::wstring_view data);
HRESULT Refresh(const SIZE windowSize, _Out_ COORD* 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;
HWND GetHwnd() const noexcept;
static LRESULT CALLBACK HwndTerminalWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept;
private:
wil::unique_hwnd _hwnd;
FontInfoDesired _desiredFont;
FontInfo _actualFont;
int _currentDpi;
bool _uiaProviderInitialized;
std::function<void(wchar_t*)> _pfnWriteCallback;
::Microsoft::WRL::ComPtr<::Microsoft::Terminal::TermControlUiaProvider> _uiaProvider;
std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal;
@@ -63,8 +79,6 @@ private:
friend HRESULT _stdcall TerminalResize(void* terminal, COORD dimensions);
friend void _stdcall TerminalDpiChanged(void* terminal, int newDpi);
friend void _stdcall TerminalUserScroll(void* terminal, int viewTop);
friend HRESULT _stdcall TerminalStartSelection(void* terminal, COORD cursorPosition, bool altPressed);
friend HRESULT _stdcall TerminalMoveSelection(void* terminal, COORD cursorPosition);
friend void _stdcall TerminalClearSelection(void* terminal);
friend const wchar_t* _stdcall TerminalGetSelection(void* terminal);
friend bool _stdcall TerminalIsSelectionActive(void* terminal);
@@ -73,5 +87,23 @@ private:
friend void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, short fontSize, int newDpi);
friend void _stdcall TerminalBlinkCursor(void* terminal);
friend void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);
void _UpdateFont(int newDpi);
void _WriteTextToConnection(const std::wstring& text) noexcept;
HRESULT _CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, bool const fAlsoCopyFormatting);
HRESULT _CopyToSystemClipboard(std::string stringToCopy, LPCWSTR lpszFormat);
void _PasteTextFromClipboard() noexcept;
void _StringPaste(const wchar_t* const pData) noexcept;
HRESULT _StartSelection(LPARAM lParam) noexcept;
HRESULT _MoveSelection(LPARAM lParam) noexcept;
IRawElementProviderSimple* _GetUiaProvider() noexcept;
// Inherited via IControlAccessibilityInfo
COORD GetFontSize() const override;
RECT GetBounds() const noexcept override;
double GetScaleFactor() const noexcept override;
void ChangeViewport(const SMALL_RECT NewWindow) override;
HRESULT GetHostUiaProvider(IRawElementProviderSimple** provider) noexcept override;
RECT GetPadding() const noexcept override;
};

View File

@@ -54,7 +54,7 @@
instead of APISet forwarders for easier Windows 7 compatibility. -->
<ItemDefinitionGroup>
<Link>
<AdditionalDependencies>onecoreuap.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>Uiautomationcore.lib;onecoreuap.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
</Project>

View File

@@ -1,4 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN // If this is not defined, windows.h includes commdlg.h which defines FindText globally and conflicts with UIAutomation ITextRangeProvider.
#endif
#include <LibraryIncludes.h>

View File

@@ -441,11 +441,13 @@ void winrt::TerminalApp::implementation::AppKeyBindings::LayerJson(const Json::V
if (keys)
{
if (!keys.isArray() || keys.size() != 1)
const auto validString = keys.isString();
const auto validArray = keys.isArray() && keys.size() == 1;
if (!validString && !validArray)
{
continue;
}
const auto keyChordString = winrt::to_hstring(keys[0].asString());
const auto keyChordString = keys.isString() ? winrt::to_hstring(keys.asString()) : winrt::to_hstring(keys[0].asString());
// Invalid is our placeholder that the action was not parsed.
ShortcutAction action = ShortcutAction::Invalid;

View File

@@ -49,6 +49,8 @@ static constexpr std::string_view BackgroundImageKey{ "backgroundImage" };
static constexpr std::string_view BackgroundImageOpacityKey{ "backgroundImageOpacity" };
static constexpr std::string_view BackgroundImageStretchModeKey{ "backgroundImageStretchMode" };
static constexpr std::string_view BackgroundImageAlignmentKey{ "backgroundImageAlignment" };
static constexpr std::string_view RetroTerminalEffectKey{ "experimental.retroTerminalEffect" };
static constexpr std::string_view AntialiasingModeKey{ "antialiasingMode" };
// Possible values for closeOnExit
static constexpr std::string_view CloseOnExitAlways{ "always" };
@@ -83,8 +85,10 @@ static constexpr std::string_view ImageAlignmentTopRight{ "topRight" };
static constexpr std::string_view ImageAlignmentBottomLeft{ "bottomLeft" };
static constexpr std::string_view ImageAlignmentBottomRight{ "bottomRight" };
// Terminal effects
static constexpr std::string_view RetroTerminalEffectKey{ "experimental.retroTerminalEffect" };
// Possible values for TextAntialiasingMode
static constexpr std::wstring_view AntialiasingModeGrayscale{ L"grayscale" };
static constexpr std::wstring_view AntialiasingModeCleartype{ L"cleartype" };
static constexpr std::wstring_view AntialiasingModeAliased{ L"aliased" };
Profile::Profile() :
Profile(std::nullopt)
@@ -124,7 +128,8 @@ Profile::Profile(const std::optional<GUID>& guid) :
_backgroundImageOpacity{},
_backgroundImageStretchMode{},
_backgroundImageAlignment{},
_retroTerminalEffect{}
_retroTerminalEffect{},
_antialiasingMode{ TextAntialiasingMode::Grayscale }
{
}
@@ -178,6 +183,7 @@ TerminalSettings Profile::CreateTerminalSettings(const std::unordered_map<std::w
terminalSettings.CursorShape(_cursorShape);
// Fill in the remaining properties from the profile
terminalSettings.ProfileName(_name);
terminalSettings.UseAcrylic(_useAcrylic);
terminalSettings.TintOpacity(_acrylicTransparency);
@@ -257,6 +263,8 @@ TerminalSettings Profile::CreateTerminalSettings(const std::unordered_map<std::w
terminalSettings.RetroTerminalEffect(_retroTerminalEffect.value());
}
terminalSettings.AntialiasingMode(_antialiasingMode);
return terminalSettings;
}
@@ -377,6 +385,8 @@ Json::Value Profile::ToJson() const
root[JsonKey(RetroTerminalEffectKey)] = _retroTerminalEffect.value();
}
root[JsonKey(AntialiasingModeKey)] = SerializeTextAntialiasingMode(_antialiasingMode).data();
return root;
}
@@ -742,6 +752,12 @@ void Profile::LayerJson(const Json::Value& json)
JsonUtils::GetOptionalValue(json, BackgroundImageAlignmentKey, _backgroundImageAlignment, &Profile::_ConvertJsonToAlignment);
JsonUtils::GetOptionalValue(json, RetroTerminalEffectKey, _retroTerminalEffect, Profile::_ConvertJsonToBool);
if (json.isMember(JsonKey(AntialiasingModeKey)))
{
auto antialiasingMode{ json[JsonKey(AntialiasingModeKey)] };
_antialiasingMode = ParseTextAntialiasingMode(GetWstringFromJson(antialiasingMode));
}
}
void Profile::SetFontFace(std::wstring fontFace) noexcept
@@ -1349,3 +1365,49 @@ void Profile::SetRetroTerminalEffect(bool value) noexcept
{
_retroTerminalEffect = value;
}
// Method Description:
// - Helper function for converting a user-specified antialiasing mode
// corresponding TextAntialiasingMode enum value
// Arguments:
// - antialiasingMode: The string value from the settings file to parse
// Return Value:
// - The corresponding enum value which maps to the string provided by the user
TextAntialiasingMode Profile::ParseTextAntialiasingMode(const std::wstring& antialiasingMode)
{
if (antialiasingMode == AntialiasingModeCleartype)
{
return TextAntialiasingMode::Cleartype;
}
else if (antialiasingMode == AntialiasingModeAliased)
{
return TextAntialiasingMode::Aliased;
}
else if (antialiasingMode == AntialiasingModeGrayscale)
{
return TextAntialiasingMode::Grayscale;
}
// default behavior for invalid data
return TextAntialiasingMode::Grayscale;
}
// Method Description:
// - Helper function for converting a TextAntialiasingMode to its corresponding
// string value.
// Arguments:
// - antialiasingMode: The enum value to convert to a string.
// Return Value:
// - The string value for the given TextAntialiasingMode
std::wstring_view Profile::SerializeTextAntialiasingMode(const TextAntialiasingMode antialiasingMode)
{
switch (antialiasingMode)
{
case TextAntialiasingMode::Cleartype:
return AntialiasingModeCleartype;
case TextAntialiasingMode::Aliased:
return AntialiasingModeAliased;
default:
case TextAntialiasingMode::Grayscale:
return AntialiasingModeGrayscale;
}
}

View File

@@ -123,6 +123,9 @@ private:
static winrt::Microsoft::Terminal::Settings::CursorStyle _ParseCursorShape(const std::wstring& cursorShapeString);
static std::wstring_view _SerializeCursorStyle(const winrt::Microsoft::Terminal::Settings::CursorStyle cursorShape);
static winrt::Microsoft::Terminal::Settings::TextAntialiasingMode ParseTextAntialiasingMode(const std::wstring& antialiasingMode);
static std::wstring_view SerializeTextAntialiasingMode(const winrt::Microsoft::Terminal::Settings::TextAntialiasingMode antialiasingMode);
static GUID _GenerateGuidForProfile(const std::wstring& name, const std::optional<std::wstring>& source) noexcept;
static bool _ConvertJsonToBool(const Json::Value& json);
@@ -166,6 +169,8 @@ private:
std::optional<std::wstring> _icon;
winrt::Microsoft::Terminal::Settings::TextAntialiasingMode _antialiasingMode;
friend class TerminalAppLocalTests::SettingsTests;
friend class TerminalAppLocalTests::ProfileTests;
friend class TerminalAppUnitTests::JsonTests;

View File

@@ -602,8 +602,10 @@ namespace winrt::TerminalApp::implementation
profile->GetConnectionType() == AzureConnectionType &&
TerminalConnection::AzureConnection::IsAzureConnectionAvailable())
{
connection = TerminalConnection::AzureConnection(settings.InitialRows(),
settings.InitialCols());
// TODO GH#4661: Replace this with directly using the AzCon when our VT is better
std::filesystem::path azBridgePath{ wil::GetModuleFileNameW<std::wstring>(nullptr) };
azBridgePath.replace_filename(L"TerminalAzBridge.exe");
connection = TerminalConnection::ConptyConnection(azBridgePath.wstring(), L".", L"Azure", settings.InitialRows(), settings.InitialCols(), winrt::guid());
}
else if (profile->HasConnectionType() &&

View File

@@ -30,7 +30,8 @@
"icon": "ms-appx:///ProfileIcons/{61c54bbd-c2c6-5271-96e7-009a87ff44bf}.png",
"padding": "8, 8, 8, 8",
"snapOnInput": true,
"useAcrylic": false
"useAcrylic": false,
"antialiasingMode": "grayscale"
},
{
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
@@ -48,7 +49,8 @@
"icon": "ms-appx:///ProfileIcons/{0caa0dad-35be-5f56-a8ff-afceeeaa6101}.png",
"padding": "8, 8, 8, 8",
"snapOnInput": true,
"useAcrylic": false
"useAcrylic": false,
"antialiasingMode": "grayscale"
}
],
"schemes":
@@ -203,55 +205,55 @@
],
"keybindings":
[
{ "command": "closePane", "keys": [ "ctrl+shift+w" ] },
{ "command": "closeWindow", "keys": [ "alt+f4" ] },
{ "command": "copy", "keys": [ "ctrl+shift+c" ] },
{ "command": "copy", "keys": [ "ctrl+insert" ] },
{ "command": "decreaseFontSize", "keys": [ "ctrl+-" ] },
{ "command": "duplicateTab", "keys": [ "ctrl+shift+d" ] },
{ "command": "increaseFontSize", "keys": [ "ctrl+=" ] },
{ "command": { "action": "moveFocus", "direction": "down" }, "keys": [ "alt+down" ] },
{ "command": { "action": "moveFocus", "direction": "left" }, "keys": [ "alt+left" ] },
{ "command": { "action": "moveFocus", "direction": "right" }, "keys": [ "alt+right" ] },
{ "command": { "action": "moveFocus", "direction": "up" }, "keys": [ "alt+up" ] },
{ "command": "newTab", "keys": [ "ctrl+shift+t" ] },
{ "command": { "action": "newTab", "index": 0 }, "keys": ["ctrl+shift+1"] },
{ "command": { "action": "newTab", "index": 1 }, "keys": ["ctrl+shift+2"] },
{ "command": { "action": "newTab", "index": 2 }, "keys": ["ctrl+shift+3"] },
{ "command": { "action": "newTab", "index": 3 }, "keys": ["ctrl+shift+4"] },
{ "command": { "action": "newTab", "index": 4 }, "keys": ["ctrl+shift+5"] },
{ "command": { "action": "newTab", "index": 5 }, "keys": ["ctrl+shift+6"] },
{ "command": { "action": "newTab", "index": 6 }, "keys": ["ctrl+shift+7"] },
{ "command": { "action": "newTab", "index": 7 }, "keys": ["ctrl+shift+8"] },
{ "command": { "action": "newTab", "index": 8 }, "keys": ["ctrl+shift+9"] },
{ "command": "nextTab", "keys": [ "ctrl+tab" ] },
{ "command": "openNewTabDropdown", "keys": [ "ctrl+shift+space" ] },
{ "command": "openSettings", "keys": [ "ctrl+," ] },
{ "command": "paste", "keys": [ "ctrl+shift+v" ] },
{ "command": "paste", "keys": [ "shift+insert" ] },
{ "command": "prevTab", "keys": [ "ctrl+shift+tab" ] },
{ "command": "resetFontSize", "keys": ["ctrl+0"]},
{ "command": { "action": "resizePane", "direction": "down" }, "keys": [ "alt+shift+down" ] },
{ "command": { "action": "resizePane", "direction": "left" }, "keys": [ "alt+shift+left" ] },
{ "command": { "action": "resizePane", "direction": "right" }, "keys": [ "alt+shift+right" ] },
{ "command": { "action": "resizePane", "direction": "up" }, "keys": [ "alt+shift+up" ] },
{ "command": "scrollDown", "keys": [ "ctrl+shift+down" ] },
{ "command": "scrollDownPage", "keys": [ "ctrl+shift+pgdn" ] },
{ "command": "scrollUp", "keys": [ "ctrl+shift+up" ] },
{ "command": "scrollUpPage", "keys": [ "ctrl+shift+pgup" ] },
{ "command": { "action": "splitPane", "split": "horizontal"}, "keys": [ "alt+shift+-" ] },
{ "command": { "action": "splitPane", "split": "vertical"}, "keys": [ "alt+shift+plus" ] },
{ "command": { "action": "switchToTab", "index": 0 }, "keys": ["ctrl+alt+1"] },
{ "command": { "action": "switchToTab", "index": 1 }, "keys": ["ctrl+alt+2"] },
{ "command": { "action": "switchToTab", "index": 2 }, "keys": ["ctrl+alt+3"] },
{ "command": { "action": "switchToTab", "index": 3 }, "keys": ["ctrl+alt+4"] },
{ "command": { "action": "switchToTab", "index": 4 }, "keys": ["ctrl+alt+5"] },
{ "command": { "action": "switchToTab", "index": 5 }, "keys": ["ctrl+alt+6"] },
{ "command": { "action": "switchToTab", "index": 6 }, "keys": ["ctrl+alt+7"] },
{ "command": { "action": "switchToTab", "index": 7 }, "keys": ["ctrl+alt+8"] },
{ "command": { "action": "switchToTab", "index": 8 }, "keys": ["ctrl+alt+9"] },
{ "command": "find", "keys": [ "ctrl+shift+f" ] },
{ "command": "toggleFullscreen", "keys": [ "alt+enter" ] },
{ "command": "toggleFullscreen", "keys": [ "f11" ] }
{ "command": "closePane", "keys": "ctrl+shift+w" },
{ "command": "closeWindow", "keys": "alt+f4" },
{ "command": "copy", "keys": "ctrl+shift+c" },
{ "command": "copy", "keys": "ctrl+insert" },
{ "command": "decreaseFontSize", "keys": "ctrl+-" },
{ "command": "duplicateTab", "keys": "ctrl+shift+d" },
{ "command": "increaseFontSize", "keys": "ctrl+=" },
{ "command": { "action": "moveFocus", "direction": "down" }, "keys": "alt+down" },
{ "command": { "action": "moveFocus", "direction": "left" }, "keys": "alt+left" },
{ "command": { "action": "moveFocus", "direction": "right" }, "keys": "alt+right" },
{ "command": { "action": "moveFocus", "direction": "up" }, "keys": "alt+up" },
{ "command": "newTab", "keys": "ctrl+shift+t" },
{ "command": { "action": "newTab", "index": 0 }, "keys": "ctrl+shift+1" },
{ "command": { "action": "newTab", "index": 1 }, "keys": "ctrl+shift+2" },
{ "command": { "action": "newTab", "index": 2 }, "keys": "ctrl+shift+3" },
{ "command": { "action": "newTab", "index": 3 }, "keys": "ctrl+shift+4" },
{ "command": { "action": "newTab", "index": 4 }, "keys": "ctrl+shift+5" },
{ "command": { "action": "newTab", "index": 5 }, "keys": "ctrl+shift+6" },
{ "command": { "action": "newTab", "index": 6 }, "keys": "ctrl+shift+7" },
{ "command": { "action": "newTab", "index": 7 }, "keys": "ctrl+shift+8" },
{ "command": { "action": "newTab", "index": 8 }, "keys": "ctrl+shift+9" },
{ "command": "nextTab", "keys": "ctrl+tab" },
{ "command": "openNewTabDropdown", "keys": "ctrl+shift+space" },
{ "command": "openSettings", "keys": "ctrl+," },
{ "command": "paste", "keys": "ctrl+shift+v" },
{ "command": "paste", "keys": "shift+insert" },
{ "command": "prevTab", "keys": "ctrl+shift+tab" },
{ "command": "resetFontSize", "keys": "ctrl+0" },
{ "command": { "action": "resizePane", "direction": "down" }, "keys": "alt+shift+down" },
{ "command": { "action": "resizePane", "direction": "left" }, "keys": "alt+shift+left" },
{ "command": { "action": "resizePane", "direction": "right" }, "keys": "alt+shift+right" },
{ "command": { "action": "resizePane", "direction": "up" }, "keys": "alt+shift+up" },
{ "command": "scrollDown", "keys": "ctrl+shift+down" },
{ "command": "scrollDownPage", "keys": "ctrl+shift+pgdn" },
{ "command": "scrollUp", "keys": "ctrl+shift+up" },
{ "command": "scrollUpPage", "keys": "ctrl+shift+pgup" },
{ "command": { "action": "splitPane", "split": "horizontal"}, "keys": "alt+shift+-" },
{ "command": { "action": "splitPane", "split": "vertical"}, "keys": "alt+shift+plus" },
{ "command": { "action": "switchToTab", "index": 0 }, "keys": "ctrl+alt+1" },
{ "command": { "action": "switchToTab", "index": 1 }, "keys": "ctrl+alt+2" },
{ "command": { "action": "switchToTab", "index": 2 }, "keys": "ctrl+alt+3" },
{ "command": { "action": "switchToTab", "index": 3 }, "keys": "ctrl+alt+4" },
{ "command": { "action": "switchToTab", "index": 4 }, "keys": "ctrl+alt+5" },
{ "command": { "action": "switchToTab", "index": 5 }, "keys": "ctrl+alt+6" },
{ "command": { "action": "switchToTab", "index": 6 }, "keys": "ctrl+alt+7" },
{ "command": { "action": "switchToTab", "index": 7 }, "keys": "ctrl+alt+8" },
{ "command": { "action": "switchToTab", "index": 8 }, "keys": "ctrl+alt+9" },
{ "command": "find", "keys": "ctrl+shift+f" },
{ "command": "toggleFullscreen", "keys": "alt+enter" },
{ "command": "toggleFullscreen", "keys": "f11" }
]
}

View File

@@ -213,7 +213,7 @@
</ItemGroup>
<!-- ========================= Misc Files ======================== -->
<ItemGroup>
<PRIResource Include="../Resources/en-US/Resources.resw" />
<PRIResource Include="../Resources/Resources.language-en.resw" />
<None Include="../packages.config" />
</ItemGroup>
<!-- ========================= Project References ======================== -->
@@ -354,4 +354,4 @@
<Target Name="_TerminalAppGenerateUserSettingsH" Inputs="..\userDefaults.json" Outputs="Generated Files\userDefaults.h" BeforeTargets="BeforeClCompile">
<Exec Command="powershell.exe -noprofile ExecutionPolicy Unrestricted $(OpenConsoleDir)\tools\GenerateHeaderForJson.ps1 -JsonFile ..\userDefaults.json -OutPath '&quot;Generated Files\userDefaults.h&quot;' -VariableName UserSettingsJson" />
</Target>
</Project>
</Project>

View File

@@ -0,0 +1,87 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "ConsoleInputReader.h"
#include "unicode.hpp"
ConsoleInputReader::ConsoleInputReader(HANDLE handle) :
_handle(handle)
{
_buffer.resize(BufferSize);
_convertedString.reserve(BufferSize);
}
void ConsoleInputReader::SetWindowSizeChangedCallback(std::function<void()> callback)
{
_windowSizeChangedCallback = std::move(callback);
}
std::optional<std::wstring_view> ConsoleInputReader::Read()
{
DWORD readCount{ 0 };
_convertedString.clear();
while (_convertedString.empty())
{
_buffer.resize(BufferSize);
BOOL succeeded =
ReadConsoleInputW(_handle, _buffer.data(), gsl::narrow_cast<DWORD>(_buffer.size()), &readCount);
if (!succeeded)
{
return std::nullopt;
}
_buffer.resize(readCount);
for (auto it = _buffer.begin(); it != _buffer.end(); ++it)
{
if (it->EventType == WINDOW_BUFFER_SIZE_EVENT && _windowSizeChangedCallback)
{
_windowSizeChangedCallback();
}
else if (it->EventType == KEY_EVENT)
{
const auto& keyEvent = it->Event.KeyEvent;
if (keyEvent.bKeyDown || (!keyEvent.bKeyDown && keyEvent.wVirtualKeyCode == VK_MENU))
{
// Got a high surrogate at the end of the buffer
if (IS_HIGH_SURROGATE(keyEvent.uChar.UnicodeChar))
{
_highSurrogate.emplace(keyEvent.uChar.UnicodeChar);
continue; // we've consumed it -- only dispatch it if we get a low
}
if (IS_LOW_SURROGATE(keyEvent.uChar.UnicodeChar))
{
// No matter what we do, we want to destructively consume the high surrogate
if (const auto oldHighSurrogate{ std::exchange(_highSurrogate, std::nullopt) })
{
_convertedString.push_back(*_highSurrogate);
}
else
{
// If we get a low without a high surrogate, we've done everything we can.
// This is an illegal state.
_convertedString.push_back(UNICODE_REPLACEMENT);
continue; // onto the next event
}
}
// (\0 with a scancode is probably a modifier key, not a VT input key)
if (keyEvent.uChar.UnicodeChar != L'\0' || keyEvent.wVirtualScanCode == 0)
{
if (_highSurrogate) // non-destructive: we don't want to set it to nullopt needlessly for every character
{
// If we get a high surrogate *here*, we didn't find a low surrogate.
// This state is also illegal.
_convertedString.push_back(UNICODE_REPLACEMENT);
_highSurrogate.reset();
}
_convertedString.push_back(keyEvent.uChar.UnicodeChar);
}
}
}
}
}
return _convertedString;
}

View File

@@ -0,0 +1,33 @@
/*++
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
Module Name:
ConsoleInputReader.h
Abstract:
This file contains a class whose sole purpose is to
abstract away a bunch of details you usually need to
know to read VT from a console input handle.
--*/
class ConsoleInputReader
{
public:
ConsoleInputReader(HANDLE handle);
void SetWindowSizeChangedCallback(std::function<void()> callback);
std::optional<std::wstring_view> Read();
private:
static constexpr size_t BufferSize{ 128 };
HANDLE _handle;
std::wstring _convertedString;
std::vector<INPUT_RECORD> _buffer;
std::optional<wchar_t> _highSurrogate;
std::function<void()> _windowSizeChangedCallback;
};

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>{067F0A06-FCB7-472C-96E9-B03B54E8E18D}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>TerminalAzBridge</RootNamespace>
<ProjectName>TerminalAzBridge</ProjectName>
<TargetName>TerminalAzBridge</TargetName>
<ConfigurationType>Application</ConfigurationType>
<OpenConsoleUniversalApp>false</OpenConsoleUniversalApp>
<ApplicationType>Windows Store</ApplicationType>
<NoOutputRedirection>true</NoOutputRedirection>
</PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<ItemDefinitionGroup>
<ClCompile>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<PropertyGroup>
<GenerateManifest>true</GenerateManifest>
<EmbedManifest>true</EmbedManifest>
</PropertyGroup>
<!-- Source Files -->
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="ConsoleInputReader.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="main.cpp" />
<ClCompile Include="ConsoleInputReader.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<!-- Dependencies -->
<ItemGroup>
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalConnection\TerminalConnection.vcxproj">
<Project>{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}</Project>
</ProjectReference>
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj" />
</ItemGroup>
<!--
This ItemGroup and the Globals PropertyGroup below it are required in order
to enable F5 debugging for the unpackaged application
-->
<ItemGroup>
<PropertyPageSchema Include="$(VCTargetsPath)$(LangID)\debugger_general.xml" />
<PropertyPageSchema Include="$(VCTargetsPath)$(LangID)\debugger_local_windows.xml" />
</ItemGroup>
<PropertyGroup Label="Globals">
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
</PropertyGroup>
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<Import Project="$(OpenConsoleDir)\build\rules\GenerateSxsManifestsFromWinmds.targets" />
</Project>

View File

@@ -0,0 +1,104 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "winrt/Microsoft.Terminal.TerminalConnection.h"
#include "ConsoleInputReader.h"
using namespace winrt;
using namespace winrt::Windows::Foundation;
using namespace winrt::Microsoft::Terminal::TerminalConnection;
static COORD GetConsoleScreenSize(HANDLE outputHandle)
{
CONSOLE_SCREEN_BUFFER_INFOEX csbiex{};
csbiex.cbSize = sizeof(csbiex);
GetConsoleScreenBufferInfoEx(outputHandle, &csbiex);
return {
(csbiex.srWindow.Right - csbiex.srWindow.Left) + 1,
(csbiex.srWindow.Bottom - csbiex.srWindow.Top) + 1
};
}
static ConnectionState RunConnectionToCompletion(const ITerminalConnection& connection, HANDLE outputHandle, HANDLE inputHandle)
{
connection.TerminalOutput([outputHandle](const winrt::hstring& output) {
WriteConsoleW(outputHandle, output.data(), output.size(), nullptr, nullptr);
});
// Detach a thread to spin the console read indefinitely.
// This application exits when the connection is closed, so
// the connection's lifetime will outlast this thread.
std::thread([connection, outputHandle, inputHandle] {
ConsoleInputReader reader{ inputHandle };
reader.SetWindowSizeChangedCallback([&]() {
const auto size = GetConsoleScreenSize(outputHandle);
connection.Resize(size.Y, size.X);
});
while (true)
{
auto input = reader.Read();
if (input)
{
connection.WriteInput(*input);
}
}
}).detach();
std::condition_variable stateChangeVar;
std::optional<ConnectionState> state;
std::mutex stateMutex;
connection.StateChanged([&](auto&& /*s*/, auto&& /*e*/) {
std::unique_lock<std::mutex> lg{ stateMutex };
state = connection.State();
stateChangeVar.notify_all();
});
connection.Start();
std::unique_lock<std::mutex> lg{ stateMutex };
stateChangeVar.wait(lg, [&]() {
if (!state.has_value())
{
return false;
}
return state.value() == ConnectionState::Closed || state.value() == ConnectionState::Failed;
});
return state.value();
}
int wmain(int /*argc*/, wchar_t** /*argv*/)
{
winrt::init_apartment(winrt::apartment_type::single_threaded);
DWORD inputMode{}, outputMode{};
HANDLE conIn{ GetStdHandle(STD_INPUT_HANDLE) }, conOut{ GetStdHandle(STD_OUTPUT_HANDLE) };
UINT codepage{ GetConsoleCP() }, outputCodepage{ GetConsoleOutputCP() };
RETURN_IF_WIN32_BOOL_FALSE(GetConsoleMode(conIn, &inputMode));
RETURN_IF_WIN32_BOOL_FALSE(GetConsoleMode(conOut, &outputMode));
RETURN_IF_WIN32_BOOL_FALSE(SetConsoleMode(conIn, ENABLE_WINDOW_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT));
RETURN_IF_WIN32_BOOL_FALSE(SetConsoleMode(conOut, ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_WRAP_AT_EOL_OUTPUT | DISABLE_NEWLINE_AUTO_RETURN));
RETURN_IF_WIN32_BOOL_FALSE(SetConsoleCP(CP_UTF8));
RETURN_IF_WIN32_BOOL_FALSE(SetConsoleOutputCP(CP_UTF8));
auto restoreConsoleModes = wil::scope_exit([&]() {
SetConsoleMode(conIn, inputMode);
SetConsoleMode(conOut, outputMode);
SetConsoleCP(codepage);
SetConsoleOutputCP(outputCodepage);
});
const auto size = GetConsoleScreenSize(conOut);
AzureConnection azureConn{ gsl::narrow_cast<uint32_t>(size.Y), gsl::narrow_cast<uint32_t>(size.X) };
const auto state = RunConnectionToCompletion(azureConn, conOut, conIn);
return state == ConnectionState::Closed ? 0 : 1;
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.190730.2" targetFramework="native" />
</packages>

View File

@@ -0,0 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"

View File

@@ -0,0 +1,36 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- pch.h
Abstract:
- Contains external headers to include in the precompile phase of console build process.
- Avoid including internal project headers. Instead include them only in the classes that need them (helps with test project building).
--*/
#pragma once
// Ignore checked iterators warning from VC compiler.
#define _SCL_SECURE_NO_WARNINGS
// Block minwindef.h min/max macros to prevent <algorithm> conflict
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <unknwn.h>
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#include <windows.h>
#include "../inc/LibraryIncludes.h"
#include <wil/cppwinrt.h>
#include <winrt/Windows.system.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <wil/resource.h>
#include <wil/win32_helpers.h>

View File

@@ -59,7 +59,7 @@
<Midl Include="TelnetConnection.idl" />
</ItemGroup>
<ItemGroup>
<PRIResource Include="Resources/en-US/Resources.resw" />
<PRIResource Include="Resources/Resources.language-en.resw" />
<None Include="packages.config" />
</ItemGroup>
<!-- ========================= Project References ======================== -->
@@ -95,4 +95,4 @@
</Link>
</ItemDefinitionGroup>
<Import Project="..\..\..\packages\vcpkg-telnetpp.1.0.1\build\native\vcpkg-telnetpp.targets" Condition="Exists('..\..\..\packages\vcpkg-telnetpp.1.0.1\build\native\vcpkg-telnetpp.targets')" />
</Project>
</Project>

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.
-->
@@ -117,27 +117,48 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="CaseSensitivityButtonLocalizedText.ToolTipService.ToolTip" xml:space="preserve">
<data name="SearchBox_CaseSensitivity.ToolTipService.ToolTip" xml:space="preserve">
<value>Match Case</value>
<comment>The tooltip text for CaseSensitivityButton</comment>
<comment>The tooltip text for the case sensitivity button on the search box control.</comment>
</data>
<data name="CloseButtonLocalizedText.ToolTipService.ToolTip" xml:space="preserve">
<data name="SearchBox_Close.ToolTipService.ToolTip" xml:space="preserve">
<value>Close</value>
<comment>The tooltip text for CloseButton</comment>
<comment>The tooltip text for the close button on the search box control.</comment>
</data>
<data name="GoBackwardButtonLocalizedText.ToolTipService.ToolTip" xml:space="preserve">
<data name="SearchBox_SearchBackwards.ToolTipService.ToolTip" xml:space="preserve">
<value>Find Up</value>
<comment>The tooltip text for GoBackward Button</comment>
<comment>The tooltip text for the search backward button.</comment>
</data>
<data name="GoForwardButtonLocalizedText.ToolTipService.ToolTip" xml:space="preserve">
<data name="SearchBox_SearchForwards.ToolTipService.ToolTip" xml:space="preserve">
<value>Find Down</value>
<comment>The tooltip text for GoForward Button</comment>
<comment>The tooltip text for the search forward button.</comment>
</data>
<data name="TextBoxLocalizedText.PlaceholderText" xml:space="preserve">
<data name="SearchBox_TextBox.PlaceholderText" xml:space="preserve">
<value>Find...</value>
<comment>The placeholder text in the search dialog TextBox</comment>
<comment>The placeholder text in the search box control.</comment>
</data>
<data name="DragFileCaption" xml:space="preserve">
<value>Copy path to file</value>
<comment>The displayed caption for dragging a file onto a terminal.</comment>
</data>
</root>
<data name="SearchBox_CaseSensitivity.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Case Sensitivity</value>
<comment>The name of the case sensitivity button on the search box control for accessibility.</comment>
</data>
<data name="SearchBox_SearchForwards.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Search Forward</value>
<comment>The name of the search forward button for accessibility.</comment>
</data>
<data name="SearchBox_SearchBackwards.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Search Backward</value>
<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>
</data>
<data name="TerminalControl_ControlType" xml:space="preserve">
<value>terminal</value>
<comment>The type of control that the terminal ahderes to. Used to identify how a user can interact with this kind of control.</comment>
</data>
</root>

View File

@@ -153,29 +153,44 @@
</UserControl.Resources>
<StackPanel Orientation="Horizontal" Style="{ThemeResource SearchBoxBackground}" Padding="5" CornerRadius="0,0,2,2">
<TextBox x:Name="TextBox" x:Uid="TextBoxLocalizedText" AutomationProperties.Name="Search Text" CornerRadius="2" Width="160"
PlaceholderForeground="{ThemeResource TextBoxPlaceholderTextThemeBrush}" FontSize="15" KeyDown = "TextBoxKeyDown"
Margin="5" HorizontalAlignment="Left" VerticalAlignment="Center">
<TextBox x:Name="TextBox"
x:Uid="SearchBox_TextBox"
CornerRadius="2"
Width="160"
PlaceholderForeground="{ThemeResource TextBoxPlaceholderTextThemeBrush}"
FontSize="15"
KeyDown="TextBoxKeyDown"
Margin="5"
HorizontalAlignment="Left"
VerticalAlignment="Center">
</TextBox>
<ToggleButton x:Name="GoBackwardButton" x:Uid="GoBackwardButtonLocalizedText" AutomationProperties.Name="Set Search Backward"
HorizontalAlignment="Right" Style="{StaticResource ToggleButtonStyle}"
Click="GoBackwardClicked" IsChecked="True">
<ToggleButton x:Name="GoBackwardButton"
x:Uid="SearchBox_SearchBackwards"
HorizontalAlignment="Right"
Style="{StaticResource ToggleButtonStyle}"
Click="GoBackwardClicked"
IsChecked="True">
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE74A;" Style="{ThemeResource FontIconStyle}"/>
</ToggleButton>
<ToggleButton x:Name="GoForwardButton" x:Uid="GoForwardButtonLocalizedText"
AutomationProperties.Name="Set Search Forward" Style="{StaticResource ToggleButtonStyle}"
<ToggleButton x:Name="GoForwardButton"
x:Uid="SearchBox_SearchForwards"
Style="{StaticResource ToggleButtonStyle}"
Click="GoForwardClicked">
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE74B;" Style="{ThemeResource FontIconStyle}"/>
</ToggleButton>
<ToggleButton x:Name="CaseSensitivityButton" x:Uid="CaseSensitivityButtonLocalizedText"
AutomationProperties.Name="CaseSensitivity" Style="{StaticResource ToggleButtonStyle}">
<ToggleButton x:Name="CaseSensitivityButton"
x:Uid="SearchBox_CaseSensitivity"
Style="{StaticResource ToggleButtonStyle}">
<PathIcon Data="M8.87305 10H7.60156L6.5625 7.25195H2.40625L1.42871 10H0.150391L3.91016 0.197266H5.09961L8.87305 10ZM6.18652 6.21973L4.64844 2.04297C4.59831 1.90625 4.54818 1.6875 4.49805 1.38672H4.4707C4.42513 1.66471 4.37272 1.88346 4.31348 2.04297L2.78906 6.21973H6.18652ZM15.1826 10H14.0615V8.90625H14.0342C13.5465 9.74479 12.8288 10.1641 11.8809 10.1641C11.1836 10.1641 10.6367 9.97949 10.2402 9.61035C9.84831 9.24121 9.65234 8.7513 9.65234 8.14062C9.65234 6.83268 10.4225 6.07161 11.9629 5.85742L14.0615 5.56348C14.0615 4.37402 13.5807 3.7793 12.6191 3.7793C11.776 3.7793 11.015 4.06641 10.3359 4.64062V3.49219C11.0241 3.05469 11.8171 2.83594 12.7148 2.83594C14.36 2.83594 15.1826 3.70638 15.1826 5.44727V10ZM14.0615 6.45898L12.373 6.69141C11.8535 6.76432 11.4616 6.89421 11.1973 7.08105C10.9329 7.26335 10.8008 7.58919 10.8008 8.05859C10.8008 8.40039 10.9215 8.68066 11.1631 8.89941C11.4092 9.11361 11.735 9.2207 12.1406 9.2207C12.6966 9.2207 13.1546 9.02702 13.5146 8.63965C13.8792 8.24772 14.0615 7.75326 14.0615 7.15625V6.45898Z"/>
</ToggleButton>
<Button x:Name="CloseButton" x:Uid="CloseButtonLocalizedText" AutomationProperties.Name="Close"
Padding="0" Click="CloseClick" Style="{ ThemeResource ButtonStyle}">
<Button x:Name="CloseButton"
x:Uid="SearchBox_Close"
Padding="0"
Click="CloseClick"
Style="{ThemeResource ButtonStyle}">
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE711;" FontSize="12"/>
</Button>
</StackPanel>

View File

@@ -17,7 +17,8 @@ using namespace winrt::Windows::UI::Xaml;
namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
TSFInputControl::TSFInputControl() :
_editContext{ nullptr }
_editContext{ nullptr },
_inComposition{ false }
{
_Create();
}
@@ -138,32 +139,31 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Get the cursor position in text buffer position
auto cursorArgs = winrt::make_self<CursorPositionEventArgs>();
_CurrentCursorPositionHandlers(*this, *cursorArgs);
const COORD cursorPos = { gsl::narrow_cast<SHORT>(cursorArgs->CurrentPosition().X), gsl::narrow_cast<SHORT>(cursorArgs->CurrentPosition().Y) };
const COORD cursorPos = { ::base::ClampedNumeric<short>(cursorArgs->CurrentPosition().X), ::base::ClampedNumeric<short>(cursorArgs->CurrentPosition().Y) };
// Get Font Info as we use this is the pixel size for characters in the display
auto fontArgs = winrt::make_self<FontInfoEventArgs>();
_CurrentFontInfoHandlers(*this, *fontArgs);
const float fontWidth = fontArgs->FontSize().Width;
const float fontHeight = fontArgs->FontSize().Height;
const auto fontWidth = fontArgs->FontSize().Width;
const auto fontHeight = fontArgs->FontSize().Height;
// Convert text buffer cursor position to client coordinate position within the window
COORD clientCursorPos;
COORD screenCursorPos;
THROW_IF_FAILED(ShortMult(cursorPos.X, gsl::narrow<SHORT>(fontWidth), &clientCursorPos.X));
THROW_IF_FAILED(ShortMult(cursorPos.Y, gsl::narrow<SHORT>(fontHeight), &clientCursorPos.Y));
clientCursorPos.X = ::base::ClampMul(cursorPos.X, ::base::ClampedNumeric<short>(fontWidth));
clientCursorPos.Y = ::base::ClampMul(cursorPos.Y, ::base::ClampedNumeric<short>(fontHeight));
// Convert from client coordinate to screen coordinate by adding window position
THROW_IF_FAILED(ShortAdd(clientCursorPos.X, gsl::narrow_cast<SHORT>(windowBounds.X), &screenCursorPos.X));
THROW_IF_FAILED(ShortAdd(clientCursorPos.Y, gsl::narrow_cast<SHORT>(windowBounds.Y), &screenCursorPos.Y));
COORD screenCursorPos;
screenCursorPos.X = ::base::ClampAdd(clientCursorPos.X, ::base::ClampedNumeric<short>(windowBounds.X));
screenCursorPos.Y = ::base::ClampAdd(clientCursorPos.Y, ::base::ClampedNumeric<short>(windowBounds.Y));
// get any offset (margin + tabs, etc..) of the control within the window
const auto offsetPoint = this->TransformToVisual(nullptr).TransformPoint(winrt::Windows::Foundation::Point(0, 0));
// add the margin offsets if any
const auto currentMargin = this->Margin();
THROW_IF_FAILED(ShortAdd(screenCursorPos.X, gsl::narrow_cast<SHORT>(offsetPoint.X), &screenCursorPos.X));
THROW_IF_FAILED(ShortAdd(screenCursorPos.Y, gsl::narrow_cast<SHORT>(offsetPoint.Y), &screenCursorPos.Y));
screenCursorPos.X = ::base::ClampAdd(screenCursorPos.X, ::base::ClampedNumeric<short>(offsetPoint.X));
screenCursorPos.Y = ::base::ClampAdd(screenCursorPos.Y, ::base::ClampedNumeric<short>(offsetPoint.Y));
// Get scale factor for view
const double scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
@@ -178,12 +178,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// position textblock to cursor position
_canvas.SetLeft(_textBlock, clientCursorPos.X);
_canvas.SetTop(_textBlock, static_cast<double>(clientCursorPos.Y));
_canvas.SetTop(_textBlock, ::base::ClampedNumeric<double>(clientCursorPos.Y));
// width is cursor to end of canvas
_textBlock.Width(200); // TODO GitHub #3640: Determine proper Width
_textBlock.Height(fontHeight);
// calculate FontSize in pixels from DIPs
const double fontSizePx = (fontHeight * 72) / USER_DEFAULT_SCREEN_DPI;
_textBlock.FontSize(fontSizePx);
@@ -201,8 +198,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - <none>
void TSFInputControl::_compositionStartedHandler(CoreTextEditContext sender, CoreTextCompositionStartedEventArgs const& /*args*/)
{
_canvas.Visibility(Visibility::Visible);
_textBlock.Visibility(Visibility::Visible);
_inComposition = true;
}
// Method Description:
@@ -215,30 +211,19 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - <none>
void TSFInputControl::_compositionCompletedHandler(CoreTextEditContext sender, CoreTextCompositionCompletedEventArgs const& /*args*/)
{
_inComposition = false;
// only need to do work if the current buffer has text
if (!_inputBuffer.empty())
{
// call event handler with data handled by parent
_compositionCompletedHandlers(_inputBuffer);
// clear the buffer for next round
const auto bufferLength = gsl::narrow_cast<int32_t>(_inputBuffer.length());
_inputBuffer.clear();
_textBlock.Text(L"");
// indicate text is now 0
_editContext.NotifyTextChanged({ 0, bufferLength }, 0, { 0, 0 });
// hide the controls until composition starts again
_canvas.Visibility(Visibility::Collapsed);
_textBlock.Visibility(Visibility::Collapsed);
_SendAndClearText();
}
}
// Method Description:
// - Handler for FocusRemoved event by CoreEditContext responsible
// for removing focus for the TSFInputControl control accordingly
// when focus was forcibly removed from text input control. (TODO GitHub #3644)
// when focus was forcibly removed from text input control.
// NOTE: Documentation says application should handle this event
// Arguments:
// - sender: CoreTextEditContext sending the request. Not used in method.
@@ -265,7 +250,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
try
{
const auto textRequested = _inputBuffer.substr(range.StartCaretPosition, static_cast<size_t>(range.EndCaretPosition) - static_cast<size_t>(range.StartCaretPosition));
const auto textEnd = ::base::ClampMin<size_t>(range.EndCaretPosition, _inputBuffer.length());
const auto length = ::base::ClampSub<size_t>(textEnd, range.StartCaretPosition);
const auto textRequested = _inputBuffer.substr(range.StartCaretPosition, length);
args.Request().Text(textRequested);
}
@@ -315,13 +302,24 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
try
{
_canvas.Visibility(Visibility::Visible);
_textBlock.Visibility(Visibility::Visible);
const auto length = ::base::ClampSub<size_t>(range.EndCaretPosition, range.StartCaretPosition);
_inputBuffer = _inputBuffer.replace(
range.StartCaretPosition,
static_cast<size_t>(range.EndCaretPosition) - static_cast<size_t>(range.StartCaretPosition),
length,
text);
_textBlock.Text(_inputBuffer);
// If we receive tabbed IME input like emoji, kaomojis, and symbols, send it to the terminal immediately.
// They aren't composition, so we don't want to wait for the user to start and finish a composition to send the text.
if (!_inComposition)
{
_SendAndClearText();
}
// Notify the TSF that the update succeeded
args.Result(CoreTextTextUpdatingResult::Succeeded);
}
@@ -334,6 +332,35 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
}
// Method Description:
// - Sends the currently held text in the input buffer to the parent and
// clears the input buffer and text block for the next round of input.
// Then hides the text block control until the next time text received.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TSFInputControl::_SendAndClearText()
{
// call event handler with data handled by parent
_compositionCompletedHandlers(_inputBuffer);
// clear the buffer for next round
const auto bufferLength = ::base::ClampedNumeric<int32_t>(_inputBuffer.length());
_inputBuffer.clear();
_textBlock.Text(L"");
// Leaving focus before NotifyTextChanged seems to guarantee that the next
// composition will send us a CompositionStarted event.
_editContext.NotifyFocusLeave();
_editContext.NotifyTextChanged({ 0, bufferLength }, 0, { 0, 0 });
_editContext.NotifyFocusEnter();
// hide the controls until text input starts again
_canvas.Visibility(Visibility::Collapsed);
_textBlock.Visibility(Visibility::Collapsed);
}
// Method Description:
// - Handler for FormatUpdating event by CoreEditContext responsible
// for handling different format updates for a particular range of text.

View File

@@ -75,6 +75,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
std::wstring _inputBuffer;
void _Create();
bool _inComposition;
void _SendAndClearText();
};
}
namespace winrt::Microsoft::Terminal::TerminalControl::factory_implementation

View File

@@ -18,6 +18,7 @@
using namespace ::Microsoft::Console::Types;
using namespace ::Microsoft::Terminal::Core;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Input;
using namespace winrt::Windows::UI::Xaml::Automation::Peers;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::System;
@@ -53,8 +54,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
TermControl::TermControl(Settings::IControlSettings settings, TerminalConnection::ITerminalConnection connection) :
_connection{ connection },
_initializedTerminal{ false },
_root{ nullptr },
_swapChainPanel{ nullptr },
_settings{ settings },
_closing{ false },
_isTerminalInitiatedScroll{ false },
@@ -69,54 +68,15 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_lastMouseClick{},
_lastMouseClickPos{},
_searchBox{ nullptr },
_tsfInputControl{ nullptr }
_unfocusedClickPos{ std::nullopt },
_isClickDragSelection{ false }
{
_EnsureStaticInitialization();
_Create();
}
void TermControl::_Create()
{
Controls::Grid container;
Controls::ColumnDefinition contentColumn{};
Controls::ColumnDefinition scrollbarColumn{};
contentColumn.Width(GridLength{ 1.0, GridUnitType::Star });
scrollbarColumn.Width(GridLength{ 1.0, GridUnitType::Auto });
container.ColumnDefinitions().Append(contentColumn);
container.ColumnDefinitions().Append(scrollbarColumn);
_scrollBar = Controls::Primitives::ScrollBar{};
_scrollBar.Orientation(Controls::Orientation::Vertical);
_scrollBar.IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::MouseIndicator);
_scrollBar.HorizontalAlignment(HorizontalAlignment::Right);
_scrollBar.VerticalAlignment(VerticalAlignment::Stretch);
// Initialize the scrollbar with some placeholder values.
// The scrollbar will be updated with real values on _Initialize
_scrollBar.Maximum(1);
_scrollBar.ViewportSize(10);
_scrollBar.IsTabStop(false);
_scrollBar.SmallChange(1);
_scrollBar.LargeChange(4);
_scrollBar.Visibility(Visibility::Visible);
_tsfInputControl = TSFInputControl();
_tsfInputControl.CompositionCompleted({ this, &TermControl::_CompositionCompleted });
_tsfInputControl.CurrentCursorPosition({ this, &TermControl::_CurrentCursorPositionHandler });
_tsfInputControl.CurrentFontInfo({ this, &TermControl::_FontInfoHandler });
container.Children().Append(_tsfInputControl);
// Create the SwapChainPanel that will display our content
Controls::SwapChainPanel swapChainPanel;
_sizeChangedRevoker = swapChainPanel.SizeChanged(winrt::auto_revoke, { this, &TermControl::_SwapChainSizeChanged });
_compositionScaleChangedRevoker = swapChainPanel.CompositionScaleChanged(winrt::auto_revoke, { this, &TermControl::_SwapChainScaleChanged });
InitializeComponent();
// Initialize the terminal only once the swapchainpanel is loaded - that
// way, we'll be able to query the real pixel size it got on layout
_layoutUpdatedRevoker = swapChainPanel.LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) {
_layoutUpdatedRevoker = SwapChainPanel().LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) {
// This event fires every time the layout changes, but it is always the last one to fire
// in any layout change chain. That gives us great flexibility in finding the right point
// at which to initialize our renderer (and our terminal).
@@ -129,74 +89,29 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
});
container.Children().Append(swapChainPanel);
container.Children().Append(_scrollBar);
Controls::Grid::SetColumn(swapChainPanel, 0);
Controls::Grid::SetColumn(_scrollBar, 1);
Controls::Grid root{};
Controls::Image bgImageLayer{};
root.Children().Append(bgImageLayer);
root.Children().Append(container);
_root = root;
_bgImageLayer = bgImageLayer;
_swapChainPanel = swapChainPanel;
this->Content(_root);
_ApplyUISettings();
// These are important:
// 1. When we get tapped, focus us
_tappedRevoker = this->Tapped(winrt::auto_revoke, [this](auto&, auto& e) {
Focus(FocusState::Pointer);
e.Handled(true);
});
// 2. Make sure we can be focused (why this isn't `Focusable` I'll never know)
this->IsTabStop(true);
// 3. Actually not sure about this one. Maybe it isn't necessary either.
this->AllowFocusOnInteraction(true);
// DON'T CALL _InitializeTerminal here - wait until the swap chain is loaded to do that.
// Subscribe to the connection's disconnected event and call our connection closed handlers.
_connectionStateChangedRevoker = _connection.StateChanged(winrt::auto_revoke, [this](auto&& /*s*/, auto&& /*v*/) {
_ConnectionStateChangedHandlers(*this, nullptr);
});
_root.AllowDrop(true);
_root.Drop({ get_weak(), &TermControl::_DragDropHandler });
_root.DragOver({ get_weak(), &TermControl::_DragOverHandler });
_ApplyUISettings();
}
// Method Description:
// - Create the SearchBoxControl object, and attach it
// to the Terminal Control root
// Arguments:
// - <none>
// Return Value:
// - <none>
// - Loads the search box from the xaml UI and focuses it.
void TermControl::CreateSearchBoxControl()
{
if (!_searchBox)
// Lazy load the search box control.
if (auto loadedSearchBox{ FindName(L"SearchBox") })
{
_searchBox = winrt::make_self<SearchBoxControl>();
_searchBox->HorizontalAlignment(HorizontalAlignment::Right);
_searchBox->VerticalAlignment(VerticalAlignment::Top);
// We need to make sure the searchbox does not overlap
// with the scroll bar
Thickness searchBoxPadding = { 0, 0, _scrollBar.ActualWidth(), 0 };
_searchBox->Margin(searchBoxPadding);
_root.Children().Append(*_searchBox);
// Event handlers
_searchBox->Search({ get_weak(), &TermControl::_Search });
_searchBox->Closed({ get_weak(), &TermControl::_CloseSearchBoxControl });
if (auto searchBox{ loadedSearchBox.try_as<::winrt::Microsoft::Terminal::TerminalControl::SearchBoxControl>() })
{
// get at its private implementation
_searchBox.copy_from(winrt::get_self<implementation::SearchBoxControl>(searchBox));
_searchBox->Visibility(Visibility::Visible);
_searchBox->SetFocusOnTextbox();
}
}
_searchBox->SetFocusOnTextbox();
}
// Method Description:
@@ -227,7 +142,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
auto lock = _terminal->LockForWriting();
if (search.FindNext())
{
_terminal->SetBoxSelection(false);
_terminal->SetBlockSelection(false);
search.Select();
_renderer->TriggerSelection();
}
@@ -236,8 +151,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Method Description:
// - The handler for the close button or pressing "Esc" when focusing on the
// search dialog.
// This removes the SearchBoxControl object from the XAML tree,
// reset smart pointer and set focus back to Terminal
// Arguments:
// - IInspectable: not used
// - RoutedEventArgs: not used
@@ -245,11 +158,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - <none>
void TermControl::_CloseSearchBoxControl(const winrt::Windows::Foundation::IInspectable& /*sender*/, RoutedEventArgs const& /*args*/)
{
unsigned int idx;
_root.Children().IndexOf(*_searchBox, idx);
_root.Children().RemoveAt(idx);
_searchBox = nullptr;
_searchBox->Visibility(Visibility::Collapsed);
// Set focus back to terminal control
this->Focus(FocusState::Programmatic);
@@ -268,7 +177,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Dispatch a call to the UI thread to apply the new settings to the
// terminal.
co_await winrt::resume_foreground(_root.Dispatcher());
co_await winrt::resume_foreground(Dispatcher());
// If 'weakThis' is locked, then we can safely work with 'this'
if (auto control{ weakThis.get() })
@@ -285,8 +194,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Refresh our font with the renderer
_UpdateFont();
const auto width = _swapChainPanel.ActualWidth();
const auto height = _swapChainPanel.ActualHeight();
const auto width = SwapChainPanel().ActualWidth();
const auto height = SwapChainPanel().ActualHeight();
if (width != 0 && height != 0)
{
// If the font size changed, or the _swapchainPanel's size changed
@@ -295,11 +204,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
auto lock = _terminal->LockForWriting();
_DoResize(width, height);
}
// set TSF Foreground
Media::SolidColorBrush foregroundBrush{};
foregroundBrush.Color(ColorRefToColor(_settings.DefaultForeground()));
_tsfInputControl.Foreground(foregroundBrush);
}
}
@@ -324,12 +228,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Apply padding as swapChainPanel's margin
auto newMargin = _ParseThicknessFromPadding(_settings.Padding());
auto existingMargin = _swapChainPanel.Margin();
_swapChainPanel.Margin(newMargin);
SwapChainPanel().Margin(newMargin);
// Initialize our font information.
const auto fontFace = _settings.FontFace();
const short fontHeight = gsl::narrow<short>(_settings.FontSize());
const short fontHeight = gsl::narrow_cast<short>(_settings.FontSize());
// The font width doesn't terribly matter, we'll only be using the
// height to look it up
// The other params here also largely don't matter.
@@ -342,8 +245,24 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// set TSF Foreground
Media::SolidColorBrush foregroundBrush{};
foregroundBrush.Color(ColorRefToColor(_settings.DefaultForeground()));
_tsfInputControl.Foreground(foregroundBrush);
_tsfInputControl.Margin(newMargin);
TSFInputControl().Foreground(foregroundBrush);
TSFInputControl().Margin(newMargin);
// Apply settings for scrollbar
if (_settings.ScrollState() == ScrollbarState::Hidden)
{
// In the scenario where the user has turned off the OS setting to automatically hide scollbars, the
// Terminal scrollbar would still be visible; so, we need to set the control's visibility accordingly to
// achieve the intended effect.
ScrollBar().IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::None);
ScrollBar().Visibility(Visibility::Collapsed);
}
else // (default or Visible)
{
// Default behavior
ScrollBar().IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::MouseIndicator);
ScrollBar().Visibility(Visibility::Visible);
}
// set number of rows to scroll at a time
_rowsToScroll = _settings.RowsToScroll();
@@ -369,7 +288,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
// See if we've already got an acrylic background brush
// to avoid the flicker when setting up a new one
auto acrylic = _root.Background().try_as<Media::AcrylicBrush>();
auto acrylic = RootGrid().Background().try_as<Media::AcrylicBrush>();
// Instantiate a brush if there's not already one there
if (acrylic == nullptr)
@@ -394,15 +313,15 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
acrylic.TintOpacity(_settings.TintOpacity());
// Apply brush to control if it's not already there
if (_root.Background() != acrylic)
if (RootGrid().Background() != acrylic)
{
_root.Background(acrylic);
RootGrid().Background(acrylic);
}
}
else
{
Media::SolidColorBrush solidColor{};
_root.Background(solidColor);
RootGrid().Background(solidColor);
}
if (!_settings.BackgroundImage().empty())
@@ -412,7 +331,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Check if the image brush is already pointing to the image
// in the modified settings; if it isn't (or isn't there),
// set a new image source for the brush
auto imageSource = _bgImageLayer.Source().try_as<Media::Imaging::BitmapImage>();
auto imageSource = BackgroundImage().Source().try_as<Media::Imaging::BitmapImage>();
if (imageSource == nullptr ||
imageSource.UriSource() == nullptr ||
@@ -423,18 +342,18 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// may well be both large and somewhere out on the
// internet.
Media::Imaging::BitmapImage image(imageUri);
_bgImageLayer.Source(image);
BackgroundImage().Source(image);
}
// Apply stretch, opacity and alignment settings
_bgImageLayer.Stretch(_settings.BackgroundImageStretchMode());
_bgImageLayer.Opacity(_settings.BackgroundImageOpacity());
_bgImageLayer.HorizontalAlignment(_settings.BackgroundImageHorizontalAlignment());
_bgImageLayer.VerticalAlignment(_settings.BackgroundImageVerticalAlignment());
BackgroundImage().Stretch(_settings.BackgroundImageStretchMode());
BackgroundImage().Opacity(_settings.BackgroundImageOpacity());
BackgroundImage().HorizontalAlignment(_settings.BackgroundImageHorizontalAlignment());
BackgroundImage().VerticalAlignment(_settings.BackgroundImageVerticalAlignment());
}
else
{
_bgImageLayer.Source(nullptr);
BackgroundImage().Source(nullptr);
}
}
@@ -448,7 +367,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(_root.Dispatcher());
co_await winrt::resume_foreground(Dispatcher());
if (auto control{ weakThis.get() })
{
@@ -462,12 +381,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
bgColor.B = B;
bgColor.A = 255;
if (auto acrylic = _root.Background().try_as<Media::AcrylicBrush>())
if (auto acrylic = RootGrid().Background().try_as<Media::AcrylicBrush>())
{
acrylic.FallbackColor(bgColor);
acrylic.TintColor(bgColor);
}
else if (auto solidColor = _root.Background().try_as<Media::SolidColorBrush>())
else if (auto solidColor = RootGrid().Background().try_as<Media::SolidColorBrush>())
{
solidColor.Color(bgColor);
}
@@ -520,9 +439,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
return _actualFont;
}
const Windows::UI::Xaml::Thickness TermControl::GetPadding() const
const Windows::UI::Xaml::Thickness TermControl::GetPadding()
{
return _swapChainPanel.Margin();
return SwapChainPanel().Margin();
}
TerminalConnection::ConnectionState TermControl::ConnectionState() const
@@ -540,13 +459,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
auto chain = _renderEngine->GetSwapChain();
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(_swapChainPanel.Dispatcher());
co_await winrt::resume_foreground(Dispatcher());
// If 'weakThis' is locked, then we can safely work with 'this'
if (auto control{ weakThis.get() })
{
auto lock = _terminal->LockForWriting();
auto nativePanel = _swapChainPanel.as<ISwapChainPanelNative>();
auto nativePanel = SwapChainPanel().as<ISwapChainPanelNative>();
nativePanel->SetSwapChain(chain.Get());
}
}
@@ -556,12 +475,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
auto chain = _renderEngine->GetSwapChain();
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(_swapChainPanel.Dispatcher());
co_await winrt::resume_foreground(Dispatcher());
if (auto control{ weakThis.get() })
{
_terminal->LockConsole();
auto nativePanel = _swapChainPanel.as<ISwapChainPanelNative>();
auto nativePanel = SwapChainPanel().as<ISwapChainPanelNative>();
nativePanel->SetSwapChain(chain.Get());
_terminal->UnlockConsole();
}
@@ -574,8 +493,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
return false;
}
const auto windowWidth = _swapChainPanel.ActualWidth(); // Width() and Height() are NaN?
const auto windowHeight = _swapChainPanel.ActualHeight();
const auto windowWidth = SwapChainPanel().ActualWidth(); // Width() and Height() are NaN?
const auto windowHeight = SwapChainPanel().ActualHeight();
if (windowWidth == 0 || windowHeight == 0)
{
@@ -636,6 +555,22 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// here, the setting will only be used when the Terminal is initialized.
dxEngine->SetRetroTerminalEffects(_settings.RetroTerminalEffect());
// TODO:GH#3927 - hot-reload this one too
// Update DxEngine's AntialiasingMode
switch (_settings.AntialiasingMode())
{
case TextAntialiasingMode::Cleartype:
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
break;
case TextAntialiasingMode::Aliased:
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
break;
case TextAntialiasingMode::Grayscale:
default:
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
break;
}
THROW_IF_FAILED(dxEngine->Enable());
_renderEngine = std::move(dxEngine);
@@ -654,62 +589,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
auto bottom = _terminal->GetViewport().BottomExclusive();
auto bufferHeight = bottom;
const auto originalMaximum = _scrollBar.Maximum();
const auto originalMinimum = _scrollBar.Minimum();
const auto originalValue = _scrollBar.Value();
const auto originalViewportSize = _scrollBar.ViewportSize();
_scrollBar.Maximum(bufferHeight - bufferHeight);
_scrollBar.Minimum(0);
_scrollBar.Value(0);
_scrollBar.ViewportSize(bufferHeight);
_scrollBar.ValueChanged({ this, &TermControl::_ScrollbarChangeHandler });
_scrollBar.PointerPressed({ this, &TermControl::_CapturePointer });
_scrollBar.PointerReleased({ this, &TermControl::_ReleasePointerCapture });
// Apply settings for scrollbar
if (_settings.ScrollState() == ScrollbarState::Visible)
{
_scrollBar.IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::MouseIndicator);
}
else if (_settings.ScrollState() == ScrollbarState::Hidden)
{
_scrollBar.IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::None);
// In the scenario where the user has turned off the OS setting to automatically hide scollbars, the
// Terminal scrollbar would still be visible; so, we need to set the control's visibility accordingly to
// achieve the intended effect.
_scrollBar.Visibility(Visibility::Collapsed);
}
else
{
// Default behavior
_scrollBar.IndicatorMode(Controls::Primitives::ScrollingIndicatorMode::MouseIndicator);
}
_root.PointerWheelChanged({ this, &TermControl::_MouseWheelHandler });
// These need to be hooked up to the SwapChainPanel because we don't want the scrollbar to respond to pointer events (GitHub #950)
_swapChainPanel.PointerPressed({ this, &TermControl::_PointerPressedHandler });
_swapChainPanel.PointerMoved({ this, &TermControl::_PointerMovedHandler });
_swapChainPanel.PointerReleased({ this, &TermControl::_PointerReleasedHandler });
ScrollBar().Maximum(bufferHeight - bufferHeight);
ScrollBar().Minimum(0);
ScrollBar().Value(0);
ScrollBar().ViewportSize(bufferHeight);
localPointerToThread->EnablePainting();
// No matter what order these guys are in, The KeyDown's will fire
// before the CharacterReceived, so we can't easily get characters
// first, then fallback to getting keys from vkeys.
// TODO: This apparently handles keys and characters correctly, though
// I'd keep an eye on it, and test more.
// I presume that the characters that aren't translated by terminalInput
// just end up getting ignored, and the rest of the input comes
// through CharacterReceived.
// I don't believe there's a difference between KeyDown and
// PreviewKeyDown for our purposes
// These two handlers _must_ be on this, not _root.
this->PreviewKeyDown({ this, &TermControl::_KeyDownHandler });
this->CharacterReceived({ this, &TermControl::_CharacterHandler });
auto pfnTitleChanged = std::bind(&TermControl::_TerminalTitleChanged, this, std::placeholders::_1);
_terminal->SetTitleChangedCallback(pfnTitleChanged);
@@ -742,10 +628,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// import value from WinUser (convert from milli-seconds to micro-seconds)
_multiClickTimer = GetDoubleClickTime() * 1000;
_gotFocusRevoker = this->GotFocus(winrt::auto_revoke, { this, &TermControl::_GotFocusHandler });
_lostFocusRevoker = this->LostFocus(winrt::auto_revoke, { this, &TermControl::_LostFocusHandler });
// Focus the control here. If we do it up above (in _Create_), then the
// Focus the control here. If we do it during control initialization, then
// focus won't actually get passed to us. I believe this is because
// we're not technically a part of the UI tree yet, so focusing us
// becomes a no-op.
@@ -850,6 +733,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (_terminal->IsSelectionActive())
{
_terminal->ClearSelection();
_renderer->TriggerSelection();
if (vkey == VK_ESCAPE)
{
@@ -873,6 +757,17 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
return handled;
}
// Method Description:
// - handle a tap event by taking focus
// Arguments:
// - sender: the XAML element responding to the tap event
// - args: event data
void TermControl::_TappedHandler(const IInspectable& /*sender*/, const TappedRoutedEventArgs& e)
{
Focus(FocusState::Pointer);
e.Handled(true);
}
// Method Description:
// - handle a mouse click event. Begin selection process.
// Arguments:
@@ -884,7 +779,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_CapturePointer(sender, args);
const auto ptr = args.Pointer();
const auto point = args.GetCurrentPoint(_root);
const auto point = args.GetCurrentPoint(*this);
if (!_focused)
{
@@ -895,7 +790,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// only want to start the selection when the user moves the pointer with
// the left mouse button held down, the PointerMovedHandler will use
// this saved position to set the SelectionAnchor.
_clickDragStartPos = point.Position();
_unfocusedClickPos = point.Position();
}
if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse || ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Pen)
@@ -908,11 +803,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (point.Properties().IsLeftButtonPressed())
{
// _clickDragStartPos having a value signifies to us that
// _unfocusedClickPos having a value signifies to us that
// the user clicked on an unfocused terminal. We don't want
// a single left click from out of focus to start a selection,
// so we return fast here.
if (_clickDragStartPos)
if (_unfocusedClickPos)
{
args.Handled(true);
return;
@@ -922,7 +817,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
const auto terminalPosition = _GetTerminalPosition(cursorPosition);
// handle ALT key
_terminal->SetBoxSelection(altEnabled);
_terminal->SetBlockSelection(altEnabled);
auto clickCount = _NumberOfClicks(cursorPosition, point.Timestamp());
@@ -933,17 +828,17 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (multiClickMapper == 3)
{
_terminal->TripleClickSelection(terminalPosition);
_terminal->MultiClickSelection(terminalPosition, ::Terminal::SelectionExpansionMode::Line);
}
else if (multiClickMapper == 2)
{
_terminal->DoubleClickSelection(terminalPosition);
_terminal->MultiClickSelection(terminalPosition, ::Terminal::SelectionExpansionMode::Word);
}
else
{
if (shiftEnabled && _terminal->IsSelectionActive())
{
_terminal->SetEndSelectionPosition(terminalPosition);
_terminal->SetSelectionEnd(terminalPosition, ::Terminal::SelectionExpansionMode::Cell);
}
else
{
@@ -988,26 +883,28 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
Input::PointerRoutedEventArgs const& args)
{
const auto ptr = args.Pointer();
const auto point = args.GetCurrentPoint(_root);
const auto point = args.GetCurrentPoint(*this);
if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse || ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Pen)
{
if (point.Properties().IsLeftButtonPressed())
{
_isClickDragSelection = true;
// If this does not have a value, it means that PointerPressedHandler already
// set the SelectionAnchor. If it does have a value, that means the user is
// performing a click-drag selection on an unfocused terminal, so
// a SelectionAnchor isn't set yet. We'll have to set it here.
if (_clickDragStartPos)
if (_unfocusedClickPos)
{
_terminal->SetSelectionAnchor(_GetTerminalPosition(*_clickDragStartPos));
_terminal->SetSelectionAnchor(_GetTerminalPosition(*_unfocusedClickPos));
}
const auto cursorPosition = point.Position();
_SetEndSelectionPointAtCursor(cursorPosition);
const double cursorBelowBottomDist = cursorPosition.Y - _swapChainPanel.Margin().Top - _swapChainPanel.ActualHeight();
const double cursorAboveTopDist = -1 * cursorPosition.Y + _swapChainPanel.Margin().Top;
const double cursorBelowBottomDist = cursorPosition.Y - SwapChainPanel().Margin().Top - SwapChainPanel().ActualHeight();
const double cursorAboveTopDist = -1 * cursorPosition.Y + SwapChainPanel().Margin().Top;
constexpr double MinAutoScrollDist = 2.0; // Arbitrary value
double newAutoScrollVelocity = 0.0;
@@ -1041,8 +938,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
const float dy = newTouchPoint.Y - anchor.Y;
// If we've moved more than one row of text, we'll want to scroll the viewport
if (std::abs(dy) > fontHeight)
// Start viewport scroll after we've moved more than a half row of text
if (std::abs(dy) > (fontHeight / 2.0f))
{
// Multiply by -1, because moving the touch point down will
// create a positive delta, but we want the viewport to move up,
@@ -1050,10 +947,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// panning down)
const float numRows = -1.0f * (dy / fontHeight);
const auto currentOffset = this->GetScrollOffset();
const double newValue = (numRows) + (currentOffset);
const auto currentOffset = ::base::ClampedNumeric<double>(ScrollBar().Value());
const auto newValue = numRows + currentOffset;
_scrollBar.Value(static_cast<int>(newValue));
ScrollBar().Value(newValue);
// Use this point as our new scroll anchor.
_touchAnchor = newTouchPoint;
@@ -1075,8 +972,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
const auto ptr = args.Pointer();
_clickDragStartPos = std::nullopt;
if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse || ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Pen)
{
const auto modifiers = static_cast<uint32_t>(args.KeyModifiers());
@@ -1086,7 +981,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (_terminal->IsCopyOnSelectActive())
{
CopySelectionToClipboard(!shiftEnabled);
// If the terminal was in focus, copy to clipboard.
// If the terminal was unfocused AND a click-drag selection happened, copy to clipboard.
if (!_unfocusedClickPos || (_unfocusedClickPos && _isClickDragSelection))
{
CopySelectionToClipboard(!shiftEnabled);
}
}
}
else if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Touch)
@@ -1094,6 +994,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_touchAnchor = std::nullopt;
}
_unfocusedClickPos = std::nullopt;
_isClickDragSelection = false;
_TryStopAutoScroll(ptr.PointerId());
args.Handled(true);
@@ -1108,7 +1011,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void TermControl::_MouseWheelHandler(Windows::Foundation::IInspectable const& /*sender*/,
Input::PointerRoutedEventArgs const& args)
{
const auto point = args.GetCurrentPoint(_root);
const auto point = args.GetCurrentPoint(*this);
const auto delta = point.Properties().MouseWheelDelta();
// Get the state of the Ctrl & Shift keys
@@ -1146,7 +1049,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
try
{
auto acrylicBrush = _root.Background().as<Media::AcrylicBrush>();
auto acrylicBrush = RootGrid().Background().as<Media::AcrylicBrush>();
acrylicBrush.TintOpacity(acrylicBrush.TintOpacity() + effectiveDelta);
}
CATCH_LOG();
@@ -1189,11 +1092,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - mouseDelta: the mouse wheel delta that triggered this event.
void TermControl::_MouseScrollHandler(const double mouseDelta, Windows::UI::Input::PointerPoint const& pointerPoint)
{
const auto currentOffset = this->GetScrollOffset();
const auto currentOffset = ScrollBar().Value();
// negative = down, positive = up
// However, for us, the signs are flipped.
const auto rowDelta = mouseDelta < 0 ? 1.0 : -1.0;
const auto rowDelta = mouseDelta / (-1.0 * WHEEL_DELTA);
// With one of the precision mouses, one click is always a multiple of 120,
// but the "smooth scrolling" mode results in non-int values
@@ -1202,7 +1105,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// The scroll bar's ValueChanged handler will actually move the viewport
// for us.
_scrollBar.Value(static_cast<int>(newValue));
ScrollBar().Value(newValue);
if (_terminal->IsSelectionActive() && pointerPoint.Properties().IsLeftButtonPressed())
{
@@ -1333,7 +1236,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
static constexpr double microSecPerSec = 1000000.0;
const double deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(timeNow - _lastAutoScrollUpdateTime.value()).count() / microSecPerSec;
_scrollBar.Value(_scrollBar.Value() + _autoScrollVelocity * deltaTime);
ScrollBar().Value(ScrollBar().Value() + _autoScrollVelocity * deltaTime);
if (_autoScrollingPointerPoint.has_value())
{
@@ -1357,16 +1260,25 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
return;
}
_focused = true;
// If the searchbox is focused, we don't want TSFInputControl to think
// it has focus so it doesn't intercept IME input. We also don't want the
// terminal's cursor to start blinking. So, we'll just return quickly here.
if (_searchBox && _searchBox->ContainsFocus())
{
return;
}
if (_uiaEngine.get())
{
THROW_IF_FAILED(_uiaEngine->Enable());
}
if (_tsfInputControl != nullptr)
if (TSFInputControl() != nullptr)
{
_tsfInputControl.NotifyFocusEnter();
TSFInputControl().NotifyFocusEnter();
}
if (_cursorTimer.has_value())
@@ -1396,9 +1308,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
THROW_IF_FAILED(_uiaEngine->Disable());
}
if (_tsfInputControl != nullptr)
if (TSFInputControl() != nullptr)
{
_tsfInputControl.NotifyFocusLeave();
TSFInputControl().NotifyFocusLeave();
}
if (_cursorTimer.has_value())
@@ -1462,7 +1374,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
auto lock = _terminal->LockForWriting();
const int newDpi = static_cast<int>(static_cast<double>(USER_DEFAULT_SCREEN_DPI) * _swapChainPanel.CompositionScaleX());
const int newDpi = static_cast<int>(static_cast<double>(USER_DEFAULT_SCREEN_DPI) * SwapChainPanel().CompositionScaleX());
// TODO: MSFT:20895307 If the font doesn't exist, this doesn't
// actually fail. We need a way to gracefully fallback.
@@ -1481,7 +1393,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
try
{
// Make sure we have a non-zero font size
const auto newSize = std::max(gsl::narrow<short>(fontSize), static_cast<short>(1));
const auto newSize = std::max<short>(gsl::narrow_cast<short>(fontSize), 1);
const auto fontFace = _settings.FontFace();
_actualFont = { fontFace, 0, 10, { 0, newSize }, CP_UTF8, false };
_desiredFont = { _actualFont };
@@ -1493,7 +1405,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// problems (like what happens when you change the font size while the
// window is maximized?)
auto lock = _terminal->LockForWriting();
_DoResize(_swapChainPanel.ActualWidth(), _swapChainPanel.ActualHeight());
_DoResize(SwapChainPanel().ActualWidth(), SwapChainPanel().ActualHeight());
}
CATCH_LOG();
}
@@ -1562,7 +1474,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
terminalPosition.X = std::clamp<short>(terminalPosition.X, 0, lastVisibleCol);
// save location (for rendering) + render
_terminal->SetEndSelectionPosition(terminalPosition);
_terminal->SetSelectionEnd(terminalPosition);
_renderer->TriggerSelection();
}
@@ -1670,7 +1582,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(_scrollBar.Dispatcher());
co_await winrt::resume_foreground(Dispatcher());
// Even if we weren't closed/closing few lines above, we might be
// while waiting for this block of code to be dispatched.
@@ -1680,7 +1592,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (!_closing.load())
{
// Update our scrollbar
_ScrollbarUpdater(_scrollBar, viewTop, viewHeight, bufferSize);
_ScrollbarUpdater(ScrollBar(), viewTop, viewHeight, bufferSize);
}
}
}
@@ -1694,6 +1606,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
return hstr;
}
hstring TermControl::GetProfileName() const
{
return _settings.ProfileName();
}
// Method Description:
// - Given a copy-able selection, get the selected text from the buffer and send it to the
// Windows Clipboard (CascadiaWin32:main.cpp).
@@ -1734,6 +1651,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (!_terminal->IsCopyOnSelectActive())
{
_terminal->ClearSelection();
_renderer->TriggerSelection();
}
// send data up for clipboard
@@ -1765,7 +1683,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_connection.TerminalOutput(_connectionOutputEventToken);
_connectionStateChangedRevoker.revoke();
_tsfInputControl.Close(); // Disconnect the TSF input control so it doesn't receive EditContext events.
TSFInputControl().Close(); // Disconnect the TSF input control so it doesn't receive EditContext events.
if (auto localConnection{ std::exchange(_connection, nullptr) })
{
@@ -1797,7 +1715,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - viewTop: the viewTop to scroll to
void TermControl::ScrollViewport(int viewTop)
{
_scrollBar.Value(viewTop);
ScrollBar().Value(viewTop);
}
int TermControl::GetScrollOffset()
@@ -1832,7 +1750,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
// Initialize our font information.
const auto fontFace = settings.FontFace();
const short fontHeight = gsl::narrow<short>(settings.FontSize());
const short fontHeight = gsl::narrow_cast<short>(settings.FontSize());
// The font width doesn't terribly matter, we'll only be using the
// height to look it up
// The other params here also largely don't matter.
@@ -1866,8 +1784,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// the Terminal). At runtime, this is fine, as we'll transform
// everything by our scaling, so it'll work out. However, right now we
// need to get the exact pixel count.
const float fFontWidth = gsl::narrow<float>(fontSize.X * scale);
const float fFontHeight = gsl::narrow<float>(fontSize.Y * scale);
const float fFontWidth = gsl::narrow_cast<float>(fontSize.X * scale);
const float fFontHeight = gsl::narrow_cast<float>(fontSize.Y * scale);
// UWP XAML scrollbars aren't guaranteed to be the same size as the
// ComCtl scrollbars, but it's certainly close enough.
@@ -1912,7 +1830,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Return Value:
// - The minimum size that this terminal control can be resized to and still
// have a visible character.
winrt::Windows::Foundation::Size TermControl::MinimumSize() const
winrt::Windows::Foundation::Size TermControl::MinimumSize()
{
const auto fontSize = _actualFont.GetSize();
double width = fontSize.X;
@@ -1920,11 +1838,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Reserve additional space if scrollbar is intended to be visible
if (_settings.ScrollState() == ScrollbarState::Visible)
{
width += _scrollBar.ActualWidth();
width += ScrollBar().ActualWidth();
}
// Account for the size of any padding
const auto padding = _swapChainPanel.Margin();
const auto padding = SwapChainPanel().Margin();
width += padding.Left + padding.Right;
height += padding.Top + padding.Bottom;
@@ -1939,19 +1857,19 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - dimension: a dimension (width or height) to be snapped
// Return Value:
// - A dimension that would be aligned to the character grid.
float TermControl::SnapDimensionToGrid(const bool widthOrHeight, const float dimension) const
float TermControl::SnapDimensionToGrid(const bool widthOrHeight, const float dimension)
{
const auto fontSize = _actualFont.GetSize();
const auto fontDimension = widthOrHeight ? fontSize.X : fontSize.Y;
const auto padding = _swapChainPanel.Margin();
const auto padding = SwapChainPanel().Margin();
auto nonTerminalArea = gsl::narrow_cast<float>(widthOrHeight ?
padding.Left + padding.Right :
padding.Top + padding.Bottom);
if (widthOrHeight && _settings.ScrollState() == ScrollbarState::Visible)
{
nonTerminalArea += gsl::narrow_cast<float>(_scrollBar.ActualWidth());
nonTerminalArea += gsl::narrow_cast<float>(ScrollBar().ActualWidth());
}
const auto gridSize = dimension - nonTerminalArea;
@@ -2076,8 +1994,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
// Exclude padding from cursor position calculation
COORD terminalPosition = {
static_cast<SHORT>(cursorPosition.X - _swapChainPanel.Margin().Left),
static_cast<SHORT>(cursorPosition.Y - _swapChainPanel.Margin().Top)
static_cast<SHORT>(cursorPosition.X - SwapChainPanel().Margin().Left),
static_cast<SHORT>(cursorPosition.Y - SwapChainPanel().Margin().Top)
};
const auto fontSize = _actualFont.GetSize();
@@ -2113,7 +2031,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void TermControl::_CurrentCursorPositionHandler(const IInspectable& /*sender*/, const CursorPositionEventArgs& eventArgs)
{
const COORD cursorPos = _terminal->GetCursorPosition();
Windows::Foundation::Point p = { gsl::narrow<float>(cursorPos.X), gsl::narrow<float>(cursorPos.Y) };
Windows::Foundation::Point p = { gsl::narrow_cast<float>(cursorPos.X), gsl::narrow_cast<float>(cursorPos.Y) };
eventArgs.CurrentPosition(p);
}

View File

@@ -61,13 +61,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
winrt::fire_and_forget UpdateSettings(Settings::IControlSettings newSettings);
hstring Title();
hstring GetProfileName() const;
bool CopySelectionToClipboard(bool trimTrailingWhitespace);
void PasteTextFromClipboard();
void Close();
Windows::Foundation::Size CharacterDimensions() const;
Windows::Foundation::Size MinimumSize() const;
float SnapDimensionToGrid(const bool widthOrHeight, const float dimension) const;
Windows::Foundation::Size MinimumSize();
float SnapDimensionToGrid(const bool widthOrHeight, const float dimension);
void ScrollViewport(int viewTop);
int GetScrollOffset();
@@ -85,7 +86,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer();
::Microsoft::Console::Types::IUiaData* GetUiaData() const;
const FontInfo GetActualFont() const;
const Windows::UI::Xaml::Thickness GetPadding() const;
const Windows::UI::Xaml::Thickness GetPadding();
TerminalConnection::ConnectionState ConnectionState() const;
@@ -105,20 +106,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// clang-format on
private:
friend struct TermControlT<TermControl>; // friend our parent so it can bind private event handlers
TerminalConnection::ITerminalConnection _connection;
bool _initializedTerminal;
Windows::UI::Xaml::Controls::Grid _root;
Windows::UI::Xaml::Controls::Image _bgImageLayer;
Windows::UI::Xaml::Controls::SwapChainPanel _swapChainPanel;
Windows::UI::Xaml::Controls::Primitives::ScrollBar _scrollBar;
winrt::com_ptr<SearchBoxControl> _searchBox;
TSFInputControl _tsfInputControl;
event_token _connectionOutputEventToken;
TermControl::Tapped_revoker _tappedRevoker;
TerminalConnection::ITerminalConnection::StateChanged_revoker _connectionStateChangedRevoker;
std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal;
@@ -162,23 +156,18 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
Timestamp _lastMouseClick;
unsigned int _multiClickCounter;
std::optional<winrt::Windows::Foundation::Point> _lastMouseClickPos;
std::optional<winrt::Windows::Foundation::Point> _clickDragStartPos{ std::nullopt };
std::optional<winrt::Windows::Foundation::Point> _unfocusedClickPos;
bool _isClickDragSelection;
// Event revokers -- we need to deregister ourselves before we die,
// lest we get callbacks afterwards.
winrt::Windows::UI::Xaml::Controls::Control::SizeChanged_revoker _sizeChangedRevoker;
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::CompositionScaleChanged_revoker _compositionScaleChangedRevoker;
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker;
winrt::Windows::UI::Xaml::UIElement::LostFocus_revoker _lostFocusRevoker;
winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker;
void _Create();
void _ApplyUISettings();
void _InitializeBackgroundBrush();
winrt::fire_and_forget _BackgroundColorChanged(const uint32_t color);
bool _InitializeTerminal();
void _UpdateFont(const bool initialUpdate = false);
void _SetFontSize(int fontSize);
void _TappedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs const& e);
void _KeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
void _CharacterHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::CharacterReceivedRoutedEventArgs const& e);
void _PointerPressedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);

View File

@@ -0,0 +1,81 @@
<UserControl
x:Class="Microsoft.Terminal.TerminalControl.TermControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Microsoft.Terminal.TerminalControl"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
d:DesignHeight="768"
d:DesignWidth="1024"
TabNavigation="Cycle"
IsTabStop="True"
AllowFocusOnInteraction="True"
AllowDrop="True"
Drop="_DragDropHandler"
DragOver="_DragOverHandler"
Tapped="_TappedHandler"
PointerWheelChanged="_MouseWheelHandler"
PreviewKeyDown="_KeyDownHandler"
CharacterReceived="_CharacterHandler"
GotFocus="_GotFocusHandler"
LostFocus="_LostFocusHandler">
<!--
TODO GH#4031: We've investigated whether we should be using KeyDown or PreviewKeyDown
but not necessarily come to a consensus. It's possible that moving input to the SwapChainPanel
will solve a bunch of the search input issues we found last time we switched.
-->
<Grid x:Name="RootGrid">
<Image x:Name="BackgroundImage" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<SwapChainPanel x:Name="SwapChainPanel"
SizeChanged="_SwapChainSizeChanged"
CompositionScaleChanged="_SwapChainScaleChanged"
PointerPressed="_PointerPressedHandler"
PointerMoved="_PointerMovedHandler"
PointerReleased="_PointerReleasedHandler" />
<!-- Putting this in a grid w/ the SwapChainPanel
ensures that it's always aligned w/ the scrollbar -->
<local:SearchBoxControl x:Name="SearchBox"
x:Load="False"
Visibility="Collapsed"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Search="_Search"
Closed="_CloseSearchBoxControl" />
</Grid>
<ScrollBar Grid.Column="1"
x:Name="ScrollBar"
Orientation="Vertical"
IndicatorMode="MouseIndicator"
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
Maximum="1"
ViewportSize="10"
IsTabStop="False"
SmallChange="1"
LargeChange="4"
ValueChanged="_ScrollbarChangeHandler"
PointerPressed="_CapturePointer"
PointerReleased="_ReleasePointerCapture" />
</Grid>
<local:TSFInputControl x:Name="TSFInputControl"
CompositionCompleted="_CompositionCompleted"
CurrentCursorPosition="_CurrentCursorPositionHandler"
CurrentFontInfo="_FontInfoHandler" />
</Grid>
</UserControl>

View File

@@ -3,6 +3,7 @@
#include "pch.h"
#include <UIAutomationCore.h>
#include <LibraryResources.h>
#include "TermControlAutomationPeer.h"
#include "TermControl.h"
#include "TermControlAutomationPeer.g.cpp"
@@ -11,6 +12,7 @@
using namespace Microsoft::Console::Types;
using namespace winrt::Windows::UI::Xaml::Automation::Peers;
using namespace winrt::Windows::Graphics::Display;
namespace UIA
{
@@ -28,9 +30,10 @@ namespace XamlAutomation
namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
TermControlAutomationPeer::TermControlAutomationPeer(winrt::Microsoft::Terminal::TerminalControl::implementation::TermControl* owner) :
TermControlAutomationPeerT<TermControlAutomationPeer>(*owner) // pass owner to FrameworkElementAutomationPeer
TermControlAutomationPeerT<TermControlAutomationPeer>(*owner), // pass owner to FrameworkElementAutomationPeer
_termControl{ owner }
{
THROW_IF_FAILED(::Microsoft::WRL::MakeAndInitialize<::Microsoft::Terminal::TermControlUiaProvider>(&_uiaProvider, owner, std::bind(&TermControlAutomationPeer::GetBoundingRectWrapped, this)));
THROW_IF_FAILED(::Microsoft::WRL::MakeAndInitialize<::Microsoft::Terminal::TermControlUiaProvider>(&_uiaProvider, _termControl->GetUiaData(), this));
};
// Method Description:
@@ -75,7 +78,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
});
}
winrt::hstring TermControlAutomationPeer::GetClassNameCore() const
hstring TermControlAutomationPeer::GetClassNameCore() const
{
return L"TermControl";
}
@@ -85,13 +88,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
return AutomationControlType::Text;
}
winrt::hstring TermControlAutomationPeer::GetLocalizedControlTypeCore() const
hstring TermControlAutomationPeer::GetLocalizedControlTypeCore() const
{
// TODO GitHub #2142: Localize string
return L"TerminalControl";
return RS_(L"TerminalControl_ControlType");
}
winrt::Windows::Foundation::IInspectable TermControlAutomationPeer::GetPatternCore(PatternInterface patternInterface) const
Windows::Foundation::IInspectable TermControlAutomationPeer::GetPatternCore(PatternInterface patternInterface) const
{
switch (patternInterface)
{
@@ -103,15 +105,41 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
}
AutomationOrientation TermControlAutomationPeer::GetOrientationCore() const
{
return AutomationOrientation::Vertical;
}
hstring TermControlAutomationPeer::GetNameCore() const
{
// fallback to title if profile name is empty
auto profileName = _termControl->GetProfileName();
if (profileName.empty())
{
return _termControl->Title();
}
return profileName;
}
hstring TermControlAutomationPeer::GetHelpTextCore() const
{
return _termControl->Title();
}
AutomationLiveSetting TermControlAutomationPeer::GetLiveSettingCore() const
{
return AutomationLiveSetting::Polite;
}
#pragma region ITextProvider
winrt::com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::GetSelection()
com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::GetSelection()
{
SAFEARRAY* pReturnVal;
THROW_IF_FAILED(_uiaProvider->GetSelection(&pReturnVal));
return WrapArrayOfTextRangeProviders(pReturnVal);
}
winrt::com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::GetVisibleRanges()
com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::GetVisibleRanges()
{
SAFEARRAY* pReturnVal;
THROW_IF_FAILED(_uiaProvider->GetVisibleRanges(&pReturnVal));
@@ -150,7 +178,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
return xutr.as<XamlAutomation::ITextRangeProvider>();
}
Windows::UI::Xaml::Automation::SupportedTextSelection TermControlAutomationPeer::SupportedTextSelection()
XamlAutomation::SupportedTextSelection TermControlAutomationPeer::SupportedTextSelection()
{
UIA::SupportedTextSelection returnVal;
THROW_IF_FAILED(_uiaProvider->get_SupportedTextSelection(&returnVal));
@@ -159,27 +187,63 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
#pragma endregion
RECT TermControlAutomationPeer::GetBoundingRectWrapped()
#pragma region IControlAccessibilityInfo
COORD TermControlAutomationPeer::GetFontSize() const
{
return _termControl->GetActualFont().GetSize();
}
RECT TermControlAutomationPeer::GetBounds() const
{
auto rect = GetBoundingRectangle();
return {
gsl::narrow<LONG>(rect.X),
gsl::narrow<LONG>(rect.Y),
gsl::narrow<LONG>(rect.X + rect.Width),
gsl::narrow<LONG>(rect.Y + rect.Height)
gsl::narrow_cast<LONG>(rect.X),
gsl::narrow_cast<LONG>(rect.Y),
gsl::narrow_cast<LONG>(rect.X + rect.Width),
gsl::narrow_cast<LONG>(rect.Y + rect.Height)
};
}
HRESULT TermControlAutomationPeer::GetHostUiaProvider(IRawElementProviderSimple** provider)
{
RETURN_HR_IF(E_INVALIDARG, provider == nullptr);
*provider = nullptr;
return S_OK;
}
RECT TermControlAutomationPeer::GetPadding() const
{
auto padding = _termControl->GetPadding();
return {
gsl::narrow_cast<LONG>(padding.Left),
gsl::narrow_cast<LONG>(padding.Top),
gsl::narrow_cast<LONG>(padding.Right),
gsl::narrow_cast<LONG>(padding.Bottom)
};
}
double TermControlAutomationPeer::GetScaleFactor() const
{
return DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
}
void TermControlAutomationPeer::ChangeViewport(const SMALL_RECT NewWindow)
{
_termControl->ScrollViewport(NewWindow.Top);
}
#pragma endregion
// Method Description:
// - extracts the UiaTextRanges from the SAFEARRAY and converts them to Xaml ITextRangeProviders
// Arguments:
// - SAFEARRAY of UIA::UiaTextRange (ITextRangeProviders)
// Return Value:
// - com_array of Xaml Wrapped UiaTextRange (ITextRangeProviders)
winrt::com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::WrapArrayOfTextRangeProviders(SAFEARRAY* textRanges)
com_array<XamlAutomation::ITextRangeProvider> TermControlAutomationPeer::WrapArrayOfTextRangeProviders(SAFEARRAY* textRanges)
{
// transfer ownership of UiaTextRanges to this new vector
auto providers = SafeArrayToOwningVector<::Microsoft::Terminal::UiaTextRange>(textRanges);
auto providers = SafeArrayToOwningVector<::Microsoft::Terminal::TermControlUiaTextRange>(textRanges);
int count = gsl::narrow<int>(providers.size());
std::vector<XamlAutomation::ITextRangeProvider> vec;
@@ -187,11 +251,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
auto parentProvider = this->ProviderFromPeer(*this);
for (int i = 0; i < count; i++)
{
auto xutr = winrt::make_self<XamlUiaTextRange>(providers[i].detach(), parentProvider);
auto xutr = make_self<XamlUiaTextRange>(providers[i].detach(), parentProvider);
vec.emplace_back(xutr.as<XamlAutomation::ITextRangeProvider>());
}
winrt::com_array<XamlAutomation::ITextRangeProvider> result{ vec };
com_array<XamlAutomation::ITextRangeProvider> result{ vec };
return result;
}

View File

@@ -27,22 +27,30 @@ Author(s):
#include "TermControl.h"
#include "TermControlAutomationPeer.g.h"
#include <winrt/Microsoft.Terminal.TerminalControl.h>
#include "TermControlUiaProvider.hpp"
#include "../types/TermControlUiaProvider.hpp"
#include "../types/IUiaEventDispatcher.h"
#include "../types/IControlAccessibilityInfo.h"
namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
struct TermControlAutomationPeer :
public TermControlAutomationPeerT<TermControlAutomationPeer>,
::Microsoft::Console::Types::IUiaEventDispatcher
::Microsoft::Console::Types::IUiaEventDispatcher,
::Microsoft::Console::Types::IControlAccessibilityInfo
{
public:
TermControlAutomationPeer(winrt::Microsoft::Terminal::TerminalControl::implementation::TermControl* owner);
TermControlAutomationPeer(Microsoft::Terminal::TerminalControl::implementation::TermControl* owner);
winrt::hstring GetClassNameCore() const;
winrt::Windows::UI::Xaml::Automation::Peers::AutomationControlType GetAutomationControlTypeCore() const;
winrt::hstring GetLocalizedControlTypeCore() const;
winrt::Windows::Foundation::IInspectable GetPatternCore(winrt::Windows::UI::Xaml::Automation::Peers::PatternInterface patternInterface) const;
#pragma region FrameworkElementAutomationPeer
hstring GetClassNameCore() const;
Windows::UI::Xaml::Automation::Peers::AutomationControlType GetAutomationControlTypeCore() const;
hstring GetLocalizedControlTypeCore() const;
Windows::Foundation::IInspectable GetPatternCore(Windows::UI::Xaml::Automation::Peers::PatternInterface patternInterface) const;
Windows::UI::Xaml::Automation::Peers::AutomationOrientation GetOrientationCore() const;
hstring GetNameCore() const;
hstring GetHelpTextCore() const;
Windows::UI::Xaml::Automation::Peers::AutomationLiveSetting GetLiveSettingCore() const;
#pragma endregion
#pragma region IUiaEventDispatcher
void SignalSelectionChanged() override;
@@ -53,17 +61,27 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
#pragma region ITextProvider Pattern
Windows::UI::Xaml::Automation::Provider::ITextRangeProvider RangeFromPoint(Windows::Foundation::Point screenLocation);
Windows::UI::Xaml::Automation::Provider::ITextRangeProvider RangeFromChild(Windows::UI::Xaml::Automation::Provider::IRawElementProviderSimple childElement);
winrt::com_array<Windows::UI::Xaml::Automation::Provider::ITextRangeProvider> GetVisibleRanges();
winrt::com_array<Windows::UI::Xaml::Automation::Provider::ITextRangeProvider> GetSelection();
com_array<Windows::UI::Xaml::Automation::Provider::ITextRangeProvider> GetVisibleRanges();
com_array<Windows::UI::Xaml::Automation::Provider::ITextRangeProvider> GetSelection();
Windows::UI::Xaml::Automation::SupportedTextSelection SupportedTextSelection();
Windows::UI::Xaml::Automation::Provider::ITextRangeProvider DocumentRange();
#pragma endregion
#pragma region IControlAccessibilityInfo Pattern
// Inherited via IControlAccessibilityInfo
virtual COORD GetFontSize() const override;
virtual RECT GetBounds() const override;
virtual RECT GetPadding() const override;
virtual double GetScaleFactor() const override;
virtual void ChangeViewport(SMALL_RECT NewWindow) override;
virtual HRESULT GetHostUiaProvider(IRawElementProviderSimple** provider) override;
#pragma endregion
RECT GetBoundingRectWrapped();
private:
::Microsoft::WRL::ComPtr<::Microsoft::Terminal::TermControlUiaProvider> _uiaProvider;
winrt::Microsoft::Terminal::TerminalControl::implementation::TermControl* _termControl;
winrt::com_array<Windows::UI::Xaml::Automation::Provider::ITextRangeProvider> WrapArrayOfTextRangeProviders(SAFEARRAY* textRanges);
};
}

View File

@@ -36,9 +36,8 @@
<ClInclude Include="SearchBoxControl.h">
<DependentUpon>SearchBoxControl.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="TermControlUiaProvider.hpp" />
<ClInclude Include="TermControl.h">
<DependentUpon>TermControl.idl</DependentUpon>
<DependentUpon>TermControl.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="TermControlAutomationPeer.h">
<DependentUpon>TermControlAutomationPeer.idl</DependentUpon>
@@ -46,7 +45,6 @@
<ClInclude Include="TSFInputControl.h">
<DependentUpon>TSFInputControl.idl</DependentUpon>
</ClInclude>
<ClInclude Include="UiaTextRange.hpp" />
<ClInclude Include="XamlUiaTextRange.h" />
</ItemGroup>
<ItemGroup>
@@ -57,9 +55,8 @@
<ClCompile Include="SearchBoxControl.cpp">
<DependentUpon>SearchBoxControl.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="TermControlUiaProvider.cpp" />
<ClCompile Include="TermControl.cpp">
<DependentUpon>TermControl.idl</DependentUpon>
<DependentUpon>TermControl.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="TSFInputControl.cpp">
<DependentUpon>TSFInputControl.idl</DependentUpon>
@@ -68,14 +65,15 @@
<ClCompile Include="TermControlAutomationPeer.cpp">
<DependentUpon>TermControlAutomationPeer.idl</DependentUpon>
</ClCompile>
<ClCompile Include="UiaTextRange.cpp" />
<ClCompile Include="XamlUiaTextRange.cpp" />
</ItemGroup>
<ItemGroup>
<Midl Include="SearchBoxControl.idl">
<DependentUpon>SearchBoxControl.xaml</DependentUpon>
</Midl>
<Midl Include="TermControl.idl" />
<Midl Include="TermControl.idl">
<DependentUpon>TermControl.xaml</DependentUpon>
</Midl>
<Midl Include="TermControlAutomationPeer.idl" />
<Midl Include="TSFInputControl.idl" />
</ItemGroup>
@@ -84,7 +82,7 @@
<None Include="TerminalControl.def" />
</ItemGroup>
<ItemGroup>
<PRIResource Include="Resources/en-US/Resources.resw" />
<PRIResource Include="Resources/Resources.language-en.resw" />
</ItemGroup>
<!-- ========================= Project References ======================== -->
<ItemGroup>
@@ -113,6 +111,9 @@
<Page Include="SearchBoxControl.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="TermControl.xaml">
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />

View File

@@ -18,6 +18,7 @@
<ClCompile Include="TermControlUiaProvider.cpp" />
<ClCompile Include="UiaTextRange.cpp" />
<ClCompile Include="SearchBoxControl.cpp" />
<ClCompile Include="init.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@@ -26,12 +27,12 @@
<ClInclude Include="XamlUiaTextRange.h" />
<ClInclude Include="TermControlUiaProvider.hpp" />
<ClInclude Include="UiaTextRange.hpp" />
<ClCompile Include="SearchBoxControl.h" />
</ItemGroup>
<ItemGroup>
<Midl Include="TermControl.idl" />
<Midl Include="TermControlAutomationPeer.idl" />
<Midl Include="SearchBoxControl.idl" />
<Midl Include="TSFInputControl.idl" />
</ItemGroup>
<ItemGroup>
<None Include="TerminalControl.def" />
@@ -43,4 +44,9 @@
<ItemGroup>
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
</ItemGroup>
<ItemGroup>
<PRIResource Include="Resources/Resources.language-en.resw">
<Filter>Resources</Filter>
</PRIResource>
</ItemGroup>
</Project>

View File

@@ -1,188 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "UiaTextRange.hpp"
#include "TermControlUiaProvider.hpp"
using namespace Microsoft::Terminal;
using namespace Microsoft::Console::Types;
using namespace Microsoft::WRL;
using namespace winrt::Windows::Graphics::Display;
HRESULT UiaTextRange::GetSelectionRanges(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* pProvider,
_In_ const std::wstring_view wordDelimiters,
_Out_ std::deque<ComPtr<UiaTextRange>>& ranges)
{
try
{
typename std::remove_reference<decltype(ranges)>::type temporaryResult;
// get the selection rects
const auto rectangles = pData->GetSelectionRects();
// create a range for each row
for (const auto& rect : rectangles)
{
const auto start = rect.Origin();
const auto end = rect.EndExclusive();
ComPtr<UiaTextRange> range;
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&range, pData, pProvider, start, end, wordDelimiters));
temporaryResult.emplace_back(std::move(range));
}
std::swap(temporaryResult, ranges);
return S_OK;
}
CATCH_RETURN();
}
// degenerate range constructor.
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, _In_ const std::wstring_view wordDelimiters)
{
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, wordDelimiters);
}
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const Cursor& cursor,
const std::wstring_view wordDelimiters)
{
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, cursor, wordDelimiters);
}
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const COORD start,
const COORD end,
const std::wstring_view wordDelimiters)
{
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, start, end, wordDelimiters);
}
// returns a degenerate text range of the start of the row closest to the y value of point
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const UiaPoint point,
const std::wstring_view wordDelimiters)
{
RETURN_IF_FAILED(UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, wordDelimiters));
Initialize(point);
return S_OK;
}
HRESULT UiaTextRange::RuntimeClassInitialize(const UiaTextRange& a)
{
return UiaTextRangeBase::RuntimeClassInitialize(a);
}
IFACEMETHODIMP UiaTextRange::Clone(_Outptr_result_maybenull_ ITextRangeProvider** ppRetVal)
{
RETURN_HR_IF(E_INVALIDARG, ppRetVal == nullptr);
*ppRetVal = nullptr;
auto hr = MakeAndInitialize<UiaTextRange>(ppRetVal, *this);
if (hr != S_OK)
{
*ppRetVal = nullptr;
return hr;
}
#if defined(_DEBUG) && defined(UiaTextRangeBase_DEBUG_MSGS)
OutputDebugString(L"Clone\n");
std::wstringstream ss;
ss << _id << L" cloned to " << (static_cast<UiaTextRangeBase*>(*ppRetVal))->_id;
std::wstring str = ss.str();
OutputDebugString(str.c_str());
OutputDebugString(L"\n");
#endif
// TODO GitHub #1914: Re-attach Tracing to UIA Tree
// tracing
/*ApiMsgClone apiMsg;
apiMsg.CloneId = static_cast<UiaTextRangeBase*>(*ppRetVal)->GetId();
Tracing::s_TraceUia(this, ApiCall::Clone, &apiMsg);*/
return S_OK;
}
void UiaTextRange::_ChangeViewport(const SMALL_RECT NewWindow)
{
auto provider = static_cast<TermControlUiaProvider*>(_pProvider);
provider->ChangeViewport(NewWindow);
}
// Method Description:
// - Transform coordinates relative to the client to relative to the screen
// Arguments:
// - clientPoint: coordinates relative to the client where
// (0,0) is the top-left of the app window
// Return Value:
// - <none>
void UiaTextRange::_TranslatePointToScreen(LPPOINT clientPoint) const
{
auto provider = static_cast<TermControlUiaProvider*>(_pProvider);
auto includeOffsets = [](long clientPos, double termControlPos, double padding, double scaleFactor) {
auto result = base::ClampedNumeric<double>(clientPos);
result += padding;
result *= scaleFactor;
result += termControlPos;
return result;
};
// update based on TermControl location (important for Panes)
UiaRect boundingRect;
THROW_IF_FAILED(provider->get_BoundingRectangle(&boundingRect));
// update based on TermControl padding
const auto padding = provider->GetPadding();
// Get scale factor for display
const auto scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
clientPoint->x = includeOffsets(clientPoint->x, boundingRect.left, padding.Left, scaleFactor);
clientPoint->y = includeOffsets(clientPoint->y, boundingRect.top, padding.Top, scaleFactor);
}
// Method Description:
// - Transform coordinates relative to the screen to relative to the client
// Arguments:
// - screenPoint: coordinates relative to the screen where
// (0,0) is the top-left of the screen
// Return Value:
// - <none>
void UiaTextRange::_TranslatePointFromScreen(LPPOINT screenPoint) const
{
auto provider = static_cast<TermControlUiaProvider*>(_pProvider);
auto includeOffsets = [](long screenPos, double termControlPos, double padding, double scaleFactor) {
auto result = base::ClampedNumeric<double>(screenPos);
result -= termControlPos;
result /= scaleFactor;
result -= padding;
return result;
};
// update based on TermControl location (important for Panes)
UiaRect boundingRect;
THROW_IF_FAILED(provider->get_BoundingRectangle(&boundingRect));
// update based on TermControl padding
const auto padding = provider->GetPadding();
// Get scale factor for display
const auto scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
screenPoint->x = includeOffsets(screenPoint->x, boundingRect.left, padding.Left, scaleFactor);
screenPoint->y = includeOffsets(screenPoint->y, boundingRect.top, padding.Top, scaleFactor);
}
const COORD UiaTextRange::_getScreenFontSize() const
{
// Do NOT get the font info from IRenderData. It is a dummy font info.
// Instead, the font info is saved in the TermControl. So we have to
// ask our parent to get it for us.
auto provider = static_cast<TermControlUiaProvider*>(_pProvider);
return provider->GetFontSize();
}

View File

@@ -3,7 +3,7 @@
#include "pch.h"
#include "XamlUiaTextRange.h"
#include "UiaTextRange.hpp"
#include "../types/TermControlUiaTextRange.hpp"
#include <UIAutomationClient.h>
// the same as COR_E_NOTSUPPORTED

View File

@@ -22,7 +22,7 @@ Author(s):
#include "TermControlAutomationPeer.h"
#include <UIAutomationCore.h>
#include "UiaTextRange.hpp"
#include "../types/TermControlUiaTextRange.hpp"
namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{

View File

@@ -44,12 +44,10 @@ Terminal::Terminal() :
_pfnWriteInput{ nullptr },
_scrollOffset{ 0 },
_snapOnInput{ true },
_boxSelection{ false },
_selectionActive{ false },
_blockSelection{ false },
_selection{ std::nullopt },
_allowSingleCharSelection{ true },
_copyOnSelect{ false },
_selectionAnchor{ 0, 0 },
_endSelectionPosition{ 0, 0 }
_copyOnSelect{ false }
{
auto dispatch = std::make_unique<TerminalDispatch>(*this);
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
@@ -454,6 +452,22 @@ void Terminal::_WriteBuffer(const std::wstring_view& stringView)
// With well behaving shells during normal operation this safeguard should normally not be encountered.
proposedCursorPosition.X = 0;
proposedCursorPosition.Y++;
// Try the character again.
i--;
// Mark the line we're currently on 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.
//
// This is more WriteCharsLegacy2ElectricBoogaloo work. I'm
// leaving it like this for now - it'll break for lines that
// _exactly_ wrap, but we can't re-wrap lines now anyways, so it
// doesn't matter.
_buffer->GetRowByOffset(cursorPosBefore.Y).GetCharRow().SetWrapForced(true);
}
_AdjustCursorPosition(proposedCursorPosition);

View File

@@ -141,7 +141,8 @@ public:
const bool IsSelectionActive() const noexcept override;
void ClearSelection() override;
void SelectNewRegion(const COORD coordStart, const COORD coordEnd) override;
const COORD GetSelectionAnchor() const override;
const COORD GetSelectionAnchor() const noexcept override;
const COORD GetSelectionEnd() const noexcept override;
const std::wstring GetConsoleTitle() const noexcept override;
void ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute) override;
#pragma endregion
@@ -156,12 +157,17 @@ public:
#pragma region TextSelection
// These methods are defined in TerminalSelection.cpp
enum class SelectionExpansionMode
{
Cell,
Word,
Line
};
const bool IsCopyOnSelectActive() const noexcept;
void DoubleClickSelection(const COORD position);
void TripleClickSelection(const COORD position);
void MultiClickSelection(const COORD viewportPos, SelectionExpansionMode expansionMode);
void SetSelectionAnchor(const COORD position);
void SetEndSelectionPosition(const COORD position);
void SetBoxSelection(const bool isEnabled) noexcept;
void SetSelectionEnd(const COORD position, std::optional<SelectionExpansionMode> newExpansionMode = std::nullopt);
void SetBlockSelection(const bool isEnabled) noexcept;
const TextBuffer::TextAndColor RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace) const;
#pragma endregion
@@ -186,19 +192,20 @@ private:
bool _suppressApplicationTitle;
#pragma region Text Selection
enum class SelectionExpansionMode
// a selection is represented as a range between two COORDs (start and end)
// the pivot is the COORD that remains selected when you extend a selection in any direction
// this is particularly useful when a word selection is extended over its starting point
// see TerminalSelection.cpp for more information
struct SelectionAnchors
{
Cell,
Word,
Line
COORD start;
COORD end;
COORD pivot;
};
COORD _selectionAnchor;
COORD _endSelectionPosition;
bool _boxSelection;
bool _selectionActive;
std::optional<SelectionAnchors> _selection;
bool _blockSelection;
bool _allowSingleCharSelection;
bool _copyOnSelect;
SHORT _selectionVerticalOffset;
std::wstring _wordDelimiters;
SelectionExpansionMode _multiClickSelectionMode;
#pragma endregion
@@ -247,15 +254,10 @@ private:
#pragma region TextSelection
// These methods are defined in TerminalSelection.cpp
std::vector<SMALL_RECT> _GetSelectionRects() const noexcept;
SHORT _ExpandWideGlyphSelectionLeft(const SHORT xPos, const SHORT yPos) const;
SHORT _ExpandWideGlyphSelectionRight(const SHORT xPos, const SHORT yPos) const;
COORD _ExpandDoubleClickSelectionLeft(const COORD position) const;
COORD _ExpandDoubleClickSelectionRight(const COORD position) const;
std::pair<COORD, COORD> _PivotSelection(const COORD targetPos) const;
std::pair<COORD, COORD> _ExpandSelectionAnchors(std::pair<COORD, COORD> anchors) const;
COORD _ConvertToBufferCell(const COORD viewportPos) const;
const bool _IsSingleCellSelection() const noexcept;
std::tuple<COORD, COORD> _PreprocessSelectionCoords() const;
SMALL_RECT _GetSelectionRow(const SHORT row, const COORD higherCoord, const COORD lowerCoord) const;
void _ExpandSelectionRow(SMALL_RECT& selectionRow) const;
#pragma endregion
#ifdef UNIT_TESTING

View File

@@ -7,6 +7,38 @@
using namespace Microsoft::Terminal::Core;
/* Selection Pivot Description:
* The pivot helps properly update the selection when a user moves a selection over itself
* See SelectionTest::DoubleClickDrag_Left for an example of the functionality mentioned here
* As an example, consider the following scenario...
* 1. Perform a word selection (double-click) on a word
*
* |-position where we double-clicked
* _|_
* |word|
* |--|
* start & pivot-| |-end
*
* 2. Drag your mouse down a line
*
*
* start & pivot-|__________
* __|word_______|
* |______|
* |
* |-end & mouse position
*
* 3. Drag your mouse up two lines
*
* |-start & mouse position
* |________
* ____| ______|
* |___w|ord
* |-end & pivot
*
* The pivot never moves until a new selection is created. It ensures that that cell will always be selected.
*/
// Method Description:
// - Helper to determine the selected region of the buffer. Used for rendering.
// Return Value:
@@ -22,183 +54,32 @@ std::vector<SMALL_RECT> Terminal::_GetSelectionRects() const noexcept
try
{
// NOTE: (0,0) is the top-left of the screen
// the physically "higher" coordinate is closer to the top-left
// the physically "lower" coordinate is closer to the bottom-right
const auto [higherCoord, lowerCoord] = _PreprocessSelectionCoords();
SHORT selectionRectSize;
THROW_IF_FAILED(ShortSub(lowerCoord.Y, higherCoord.Y, &selectionRectSize));
THROW_IF_FAILED(ShortAdd(selectionRectSize, 1, &selectionRectSize));
std::vector<SMALL_RECT> selectionArea;
selectionArea.reserve(selectionRectSize);
for (auto row = higherCoord.Y; row <= lowerCoord.Y; row++)
{
SMALL_RECT selectionRow = _GetSelectionRow(row, higherCoord, lowerCoord);
_ExpandSelectionRow(selectionRow);
selectionArea.emplace_back(selectionRow);
}
result.swap(selectionArea);
return _buffer->GetTextRects(_selection->start, _selection->end, _blockSelection);
}
CATCH_LOG();
return result;
}
// Method Description:
// - convert selection anchors to proper coordinates for rendering
// NOTE: (0,0) is top-left so vertical comparison is inverted
// Arguments:
// - None
// Return Value:
// - tuple.first: the physically "higher" coordinate (closer to the top-left)
// - tuple.second: the physically "lower" coordinate (closer to the bottom-right)
std::tuple<COORD, COORD> Terminal::_PreprocessSelectionCoords() const
{
// create these new anchors for comparison and rendering
COORD selectionAnchorWithOffset{ _selectionAnchor };
COORD endSelectionPositionWithOffset{ _endSelectionPosition };
// Add anchor offset here to update properly on new buffer output
THROW_IF_FAILED(ShortAdd(selectionAnchorWithOffset.Y, _selectionVerticalOffset, &selectionAnchorWithOffset.Y));
THROW_IF_FAILED(ShortAdd(endSelectionPositionWithOffset.Y, _selectionVerticalOffset, &endSelectionPositionWithOffset.Y));
// clamp anchors to be within buffer bounds
const auto bufferSize = _buffer->GetSize();
bufferSize.Clamp(selectionAnchorWithOffset);
bufferSize.Clamp(endSelectionPositionWithOffset);
// NOTE: (0,0) is top-left so vertical comparison is inverted
// CompareInBounds returns whether A is to the left of (rv<0), equal to (rv==0), or to the right of (rv>0) B.
// Here, we want the "left"most coordinate to be the one "higher" on the screen. The other gets the dubious honor of
// being the "lower."
return bufferSize.CompareInBounds(selectionAnchorWithOffset, endSelectionPositionWithOffset) <= 0 ?
std::make_tuple(selectionAnchorWithOffset, endSelectionPositionWithOffset) :
std::make_tuple(endSelectionPositionWithOffset, selectionAnchorWithOffset);
}
// Method Description:
// - constructs the selection row at the given row
// NOTE: (0,0) is top-left so vertical comparison is inverted
// Arguments:
// - row: the buffer y-value under observation
// - higherCoord: the physically "higher" coordinate (closer to the top-left)
// - lowerCoord: the physically "lower" coordinate (closer to the bottom-right)
// Return Value:
// - the selection row needed for rendering
SMALL_RECT Terminal::_GetSelectionRow(const SHORT row, const COORD higherCoord, const COORD lowerCoord) const
{
SMALL_RECT selectionRow;
selectionRow.Top = row;
selectionRow.Bottom = row;
if (_boxSelection || higherCoord.Y == lowerCoord.Y)
{
selectionRow.Left = std::min(higherCoord.X, lowerCoord.X);
selectionRow.Right = std::max(higherCoord.X, lowerCoord.X);
}
else
{
selectionRow.Left = (row == higherCoord.Y) ? higherCoord.X : _buffer->GetSize().Left();
selectionRow.Right = (row == lowerCoord.Y) ? lowerCoord.X : _buffer->GetSize().RightInclusive();
}
return selectionRow;
}
// Method Description:
// - Get the current anchor position relative to the whole text buffer
// Arguments:
// - None
// Return Value:
// - None
const COORD Terminal::GetSelectionAnchor() const
const COORD Terminal::GetSelectionAnchor() const noexcept
{
COORD selectionAnchorPos{ _selectionAnchor };
THROW_IF_FAILED(ShortAdd(selectionAnchorPos.Y, _selectionVerticalOffset, &selectionAnchorPos.Y));
return selectionAnchorPos;
return _selection->start;
}
// Method Description:
// - Expand the selection row according to selection mode and wide glyphs
// - this is particularly useful for box selections (ALT + selection)
// - Get the current end anchor position relative to the whole text buffer
// Arguments:
// - selectionRow: the selection row to be expanded
// - None
// Return Value:
// - modifies selectionRow's Left and Right values to expand properly
void Terminal::_ExpandSelectionRow(SMALL_RECT& selectionRow) const
// - None
const COORD Terminal::GetSelectionEnd() const noexcept
{
const auto row = selectionRow.Top;
// expand selection for Double/Triple Click
if (_multiClickSelectionMode == SelectionExpansionMode::Word)
{
selectionRow.Left = _ExpandDoubleClickSelectionLeft({ selectionRow.Left, row }).X;
selectionRow.Right = _ExpandDoubleClickSelectionRight({ selectionRow.Right, row }).X;
}
else if (_multiClickSelectionMode == SelectionExpansionMode::Line)
{
selectionRow.Left = _buffer->GetSize().Left();
selectionRow.Right = _buffer->GetSize().RightInclusive();
}
// expand selection for Wide Glyphs
selectionRow.Left = _ExpandWideGlyphSelectionLeft(selectionRow.Left, row);
selectionRow.Right = _ExpandWideGlyphSelectionRight(selectionRow.Right, row);
}
// Method Description:
// - Expands the selection left-wards to cover a wide glyph, if necessary
// Arguments:
// - position: the (x,y) coordinate on the visible viewport
// Return Value:
// - updated x position to encapsulate the wide glyph
SHORT Terminal::_ExpandWideGlyphSelectionLeft(const SHORT xPos, const SHORT yPos) const
{
// don't change the value if at/outside the boundary
const auto bufferSize = _buffer->GetSize();
if (xPos <= bufferSize.Left() || xPos > bufferSize.RightInclusive())
{
return xPos;
}
COORD position{ xPos, yPos };
const auto attr = _buffer->GetCellDataAt(position)->DbcsAttr();
if (attr.IsTrailing())
{
// move off by highlighting the lead half too.
// alters position.X
bufferSize.DecrementInBounds(position);
}
return position.X;
}
// Method Description:
// - Expands the selection right-wards to cover a wide glyph, if necessary
// Arguments:
// - position: the (x,y) coordinate on the visible viewport
// Return Value:
// - updated x position to encapsulate the wide glyph
SHORT Terminal::_ExpandWideGlyphSelectionRight(const SHORT xPos, const SHORT yPos) const
{
// don't change the value if at/outside the boundary
const auto bufferSize = _buffer->GetSize();
if (xPos < bufferSize.Left() || xPos >= bufferSize.RightInclusive())
{
return xPos;
}
COORD position{ xPos, yPos };
const auto attr = _buffer->GetCellDataAt(position)->DbcsAttr();
if (attr.IsLeading())
{
// move off by highlighting the trailing half too.
// alters position.X
bufferSize.IncrementInBounds(position);
}
return position.X;
return _selection->end;
}
// Method Description:
@@ -207,7 +88,7 @@ SHORT Terminal::_ExpandWideGlyphSelectionRight(const SHORT xPos, const SHORT yPo
// - bool representing if selection is only a single cell. Used for copyOnSelect
const bool Terminal::_IsSingleCellSelection() const noexcept
{
return (_selectionAnchor == _endSelectionPosition);
return (_selection->start == _selection->end);
}
// Method Description:
@@ -222,7 +103,7 @@ const bool Terminal::IsSelectionActive() const noexcept
{
return false;
}
return _selectionActive;
return _selection.has_value();
}
// Method Description:
@@ -235,79 +116,60 @@ const bool Terminal::IsCopyOnSelectActive() const noexcept
}
// Method Description:
// - Select the sequence between delimiters defined in Settings
// - Perform a multi-click selection at viewportPos expanding according to the expansionMode
// Arguments:
// - position: the (x,y) coordinate on the visible viewport
void Terminal::DoubleClickSelection(const COORD position)
// - viewportPos: the (x,y) coordinate on the visible viewport
// - expansionMode: the SelectionExpansionMode to dictate the boundaries of the selection anchors
void Terminal::MultiClickSelection(const COORD viewportPos, SelectionExpansionMode expansionMode)
{
#pragma warning(suppress : 26496) // cpp core checks wants this const but .Clamp() can write it.
COORD positionWithOffsets = _ConvertToBufferCell(position);
// set the selection pivot to expand the selection using SetSelectionEnd()
_selection = SelectionAnchors{};
_selection->pivot = _ConvertToBufferCell(viewportPos);
// scan leftwards until delimiter is found and
// set selection anchor to one right of that spot
_selectionAnchor = _ExpandDoubleClickSelectionLeft(positionWithOffsets);
THROW_IF_FAILED(ShortSub(_selectionAnchor.Y, gsl::narrow<SHORT>(ViewStartIndex()), &_selectionAnchor.Y));
_selectionVerticalOffset = gsl::narrow<SHORT>(ViewStartIndex());
_multiClickSelectionMode = expansionMode;
SetSelectionEnd(viewportPos);
// scan rightwards until delimiter is found and
// set endSelectionPosition to one left of that spot
_endSelectionPosition = _ExpandDoubleClickSelectionRight(positionWithOffsets);
THROW_IF_FAILED(ShortSub(_endSelectionPosition.Y, gsl::narrow<SHORT>(ViewStartIndex()), &_endSelectionPosition.Y));
_selectionActive = true;
_multiClickSelectionMode = SelectionExpansionMode::Word;
}
// Method Description:
// - Select the entire row of the position clicked
// Arguments:
// - position: the (x,y) coordinate on the visible viewport
void Terminal::TripleClickSelection(const COORD position)
{
SetSelectionAnchor({ 0, position.Y });
SetEndSelectionPosition({ _buffer->GetSize().RightInclusive(), position.Y });
_multiClickSelectionMode = SelectionExpansionMode::Line;
// we need to set the _selectionPivot again
// for future shift+clicks
_selection->pivot = _selection->start;
}
// Method Description:
// - Record the position of the beginning of a selection
// Arguments:
// - position: the (x,y) coordinate on the visible viewport
void Terminal::SetSelectionAnchor(const COORD position)
void Terminal::SetSelectionAnchor(const COORD viewportPos)
{
_selectionAnchor = position;
_selection = SelectionAnchors{};
_selection->pivot = _ConvertToBufferCell(viewportPos);
// include _scrollOffset here to ensure this maps to the right spot of the original viewport
THROW_IF_FAILED(ShortSub(_selectionAnchor.Y, gsl::narrow<SHORT>(_scrollOffset), &_selectionAnchor.Y));
// copy value of ViewStartIndex to support scrolling
// and update on new buffer output (used in _GetSelectionRects())
_selectionVerticalOffset = gsl::narrow<SHORT>(ViewStartIndex());
_selectionActive = true;
_allowSingleCharSelection = (_copyOnSelect) ? false : true;
SetEndSelectionPosition(position);
_multiClickSelectionMode = SelectionExpansionMode::Cell;
SetSelectionEnd(viewportPos);
_selection->start = _selection->pivot;
}
// Method Description:
// - Record the position of the end of a selection
// - Update selection anchors when dragging to a position
// - based on the selection expansion mode
// Arguments:
// - position: the (x,y) coordinate on the visible viewport
void Terminal::SetEndSelectionPosition(const COORD position)
// - viewportPos: the (x,y) coordinate on the visible viewport
// - newExpansionMode: overwrites the _multiClickSelectionMode for this function call. Used for ShiftClick
void Terminal::SetSelectionEnd(const COORD viewportPos, std::optional<SelectionExpansionMode> newExpansionMode)
{
_endSelectionPosition = position;
const auto textBufferPos = _ConvertToBufferCell(viewportPos);
// include _scrollOffset here to ensure this maps to the right spot of the original viewport
THROW_IF_FAILED(ShortSub(_endSelectionPosition.Y, gsl::narrow<SHORT>(_scrollOffset), &_endSelectionPosition.Y));
// if this is a shiftClick action, we need to overwrite the _multiClickSelectionMode value (even if it's the same)
// Otherwise, we may accidentally expand during other selection-based actions
_multiClickSelectionMode = newExpansionMode.has_value() ? *newExpansionMode : _multiClickSelectionMode;
// copy value of ViewStartIndex to support scrolling
// and update on new buffer output (used in _GetSelectionRects())
_selectionVerticalOffset = gsl::narrow<SHORT>(ViewStartIndex());
const auto anchors = _PivotSelection(textBufferPos);
std::tie(_selection->start, _selection->end) = _ExpandSelectionAnchors(anchors);
// moving the endpoint of what used to be a single cell selection
// allows the user to drag back and select just one cell
if (_copyOnSelect && !_IsSingleCellSelection())
{
_allowSingleCharSelection = true;
@@ -315,25 +177,74 @@ void Terminal::SetEndSelectionPosition(const COORD position)
}
// Method Description:
// - enable/disable box selection (ALT + selection)
// - returns a new pair of selection anchors for selecting around the pivot
// - This ensures start < end when compared
// Arguments:
// - isEnabled: new value for _boxSelection
void Terminal::SetBoxSelection(const bool isEnabled) noexcept
// - targetPos: the (x,y) coordinate we are moving to on the text buffer
// Return Value:
// - the new start/end for a selection
std::pair<COORD, COORD> Terminal::_PivotSelection(const COORD targetPos) const
{
_boxSelection = isEnabled;
if (_buffer->GetSize().CompareInBounds(targetPos, _selection->pivot) <= 0)
{
// target is before pivot
// treat target as start
return std::make_pair(targetPos, _selection->pivot);
}
else
{
// target is after pivot
// treat pivot as start
return std::make_pair(_selection->pivot, targetPos);
}
}
// Method Description:
// - Update the selection anchors to expand according to the expansion mode
// Arguments:
// - anchors: a pair of selection anchors representing a desired selection
// Return Value:
// - the new start/end for a selection
std::pair<COORD, COORD> Terminal::_ExpandSelectionAnchors(std::pair<COORD, COORD> anchors) const
{
COORD start = anchors.first;
COORD end = anchors.second;
const auto bufferSize = _buffer->GetSize();
switch (_multiClickSelectionMode)
{
case SelectionExpansionMode::Line:
start = { bufferSize.Left(), start.Y };
end = { bufferSize.RightInclusive(), end.Y };
break;
case SelectionExpansionMode::Word:
start = _buffer->GetWordStart(start, _wordDelimiters);
end = _buffer->GetWordEnd(end, _wordDelimiters);
break;
case SelectionExpansionMode::Cell:
default:
// no expansion is necessary
break;
}
return std::make_pair(start, end);
}
// Method Description:
// - enable/disable block selection (ALT + selection)
// Arguments:
// - isEnabled: new value for _blockSelection
void Terminal::SetBlockSelection(const bool isEnabled) noexcept
{
_blockSelection = isEnabled;
}
// Method Description:
// - clear selection data and disable rendering it
#pragma warning(disable : 26440) // changing this to noexcept would require a change to ConHost's selection model
void Terminal::ClearSelection()
{
_selectionActive = false;
_allowSingleCharSelection = false;
_selectionAnchor = { 0, 0 };
_endSelectionPosition = { 0, 0 };
_selectionVerticalOffset = 0;
_buffer->GetRenderTarget().TriggerSelection();
_selection = std::nullopt;
}
// Method Description:
@@ -348,47 +259,13 @@ const TextBuffer::TextAndColor Terminal::RetrieveSelectedTextFromBuffer(bool tri
std::function<COLORREF(TextAttribute&)> GetForegroundColor = std::bind(&Terminal::GetForegroundColor, this, std::placeholders::_1);
std::function<COLORREF(TextAttribute&)> GetBackgroundColor = std::bind(&Terminal::GetBackgroundColor, this, std::placeholders::_1);
return _buffer->GetTextForClipboard(!_boxSelection,
return _buffer->GetTextForClipboard(!_blockSelection,
trimTrailingWhitespace,
_GetSelectionRects(),
GetForegroundColor,
GetBackgroundColor);
}
// Method Description:
// - expand the double click selection to the left
// - stopped by delimiter if started on delimiter
// Arguments:
// - position: buffer coordinate for selection
// Return Value:
// - updated copy of "position" to new expanded location (with vertical offset)
COORD Terminal::_ExpandDoubleClickSelectionLeft(const COORD position) const
{
// force position to be within bounds
#pragma warning(suppress : 26496) // cpp core checks wants this const but .Clamp() can write it.
COORD positionWithOffsets = position;
_buffer->GetSize().Clamp(positionWithOffsets);
return _buffer->GetWordStart(positionWithOffsets, _wordDelimiters);
}
// Method Description:
// - expand the double click selection to the right
// - stopped by delimiter if started on delimiter
// Arguments:
// - position: buffer coordinate for selection
// Return Value:
// - updated copy of "position" to new expanded location (with vertical offset)
COORD Terminal::_ExpandDoubleClickSelectionRight(const COORD position) const
{
// force position to be within bounds
#pragma warning(suppress : 26496) // cpp core checks wants this const but .Clamp() can write it.
COORD positionWithOffsets = position;
_buffer->GetSize().Clamp(positionWithOffsets);
return _buffer->GetWordEnd(positionWithOffsets, _wordDelimiters);
}
// Method Description:
// - convert viewport position to the corresponding location on the buffer
// Arguments:
@@ -397,13 +274,10 @@ COORD Terminal::_ExpandDoubleClickSelectionRight(const COORD position) const
// - the corresponding location on the buffer
COORD Terminal::_ConvertToBufferCell(const COORD viewportPos) const
{
// Force position to be valid
COORD positionWithOffsets = viewportPos;
_buffer->GetSize().Clamp(positionWithOffsets);
THROW_IF_FAILED(ShortSub(viewportPos.Y, gsl::narrow<SHORT>(_scrollOffset), &positionWithOffsets.Y));
THROW_IF_FAILED(ShortAdd(positionWithOffsets.Y, gsl::narrow<SHORT>(ViewStartIndex()), &positionWithOffsets.Y));
return positionWithOffsets;
const auto yPos = base::ClampedNumeric<short>(_VisibleStartIndex()) + viewportPos.Y;
COORD bufferPos = { viewportPos.X, yPos };
_buffer->GetSize().Clamp(bufferPos);
return bufferPos;
}
// Method Description:

View File

@@ -173,8 +173,7 @@ void Terminal::SelectNewRegion(const COORD coordStart, const COORD coordEnd)
realCoordEnd.Y -= gsl::narrow<short>(_VisibleStartIndex());
SetSelectionAnchor(realCoordStart);
SetEndSelectionPosition(realCoordEnd);
_buffer->GetRenderTarget().TriggerSelection();
SetSelectionEnd(realCoordEnd, SelectionExpansionMode::Cell);
}
const std::wstring Terminal::GetConsoleTitle() const noexcept

View File

@@ -12,6 +12,13 @@ namespace Microsoft.Terminal.Settings
Hidden
};
enum TextAntialiasingMode
{
Grayscale = 0,
Cleartype,
Aliased
};
// Class Description:
// TerminalSettings encapsulates all settings that control the
// TermControl's behavior. In these settings there is both the entirety
@@ -19,6 +26,8 @@ namespace Microsoft.Terminal.Settings
// for specifically the control.
interface IControlSettings requires Microsoft.Terminal.Settings.ICoreSettings
{
String ProfileName;
Boolean UseAcrylic;
Double TintOpacity;
ScrollbarState ScrollState;
@@ -41,5 +50,7 @@ namespace Microsoft.Terminal.Settings
UInt32 SelectionBackground;
Boolean RetroTerminalEffect;
TextAntialiasingMode AntialiasingMode;
};
}

View File

@@ -28,6 +28,7 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
_cursorHeight{ DEFAULT_CURSOR_HEIGHT },
_wordDelimiters{ DEFAULT_WORD_DELIMITERS },
_copyOnSelect{ false },
_profileName{},
_useAcrylic{ false },
_tintOpacity{ 0.5 },
_padding{ DEFAULT_PADDING },
@@ -39,7 +40,9 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
_backgroundImageHorizontalAlignment{ winrt::Windows::UI::Xaml::HorizontalAlignment::Center },
_backgroundImageVerticalAlignment{ winrt::Windows::UI::Xaml::VerticalAlignment::Center },
_keyBindings{ nullptr },
_scrollbarState{ ScrollbarState::Visible }
_scrollbarState{ ScrollbarState::Visible },
_antialiasingMode{ TextAntialiasingMode::Grayscale }
{
}
@@ -194,6 +197,16 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
_copyOnSelect = value;
}
void TerminalSettings::ProfileName(hstring const& value)
{
_profileName = value;
}
hstring TerminalSettings::ProfileName()
{
return _profileName;
}
bool TerminalSettings::UseAcrylic() noexcept
{
return _useAcrylic;
@@ -374,4 +387,14 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
_retroTerminalEffect = value;
}
Settings::TextAntialiasingMode TerminalSettings::AntialiasingMode() const noexcept
{
return _antialiasingMode;
}
void TerminalSettings::AntialiasingMode(const Settings::TextAntialiasingMode& value) noexcept
{
_antialiasingMode = value;
}
}

View File

@@ -55,6 +55,8 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
void CopyOnSelect(bool value) noexcept;
// ------------------------ End of Core Settings -----------------------
hstring ProfileName();
void ProfileName(hstring const& value);
bool UseAcrylic() noexcept;
void UseAcrylic(bool value) noexcept;
double TintOpacity() noexcept;
@@ -102,6 +104,9 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
bool RetroTerminalEffect() noexcept;
void RetroTerminalEffect(bool value) noexcept;
TextAntialiasingMode AntialiasingMode() const noexcept;
void AntialiasingMode(winrt::Microsoft::Terminal::Settings::TextAntialiasingMode const& value) noexcept;
private:
uint32_t _defaultForeground;
uint32_t _defaultBackground;
@@ -117,6 +122,7 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
uint32_t _cursorHeight;
hstring _wordDelimiters;
hstring _profileName;
bool _useAcrylic;
double _tintOpacity;
hstring _fontFace;
@@ -137,6 +143,8 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
Settings::ScrollbarState _scrollbarState;
bool _retroTerminalEffect;
Settings::TextAntialiasingMode _antialiasingMode;
};
}

View File

@@ -44,7 +44,7 @@ using namespace Microsoft::Terminal::Core;
namespace TerminalCoreUnitTests
{
class TerminalBufferTests;
class ConptyRoundtripTests;
};
using namespace TerminalCoreUnitTests;
@@ -152,8 +152,14 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
TEST_METHOD(SimpleWriteOutputTest);
TEST_METHOD(WriteTwoLinesUsesNewline);
TEST_METHOD(WriteAFewSimpleLines);
TEST_METHOD(PassthroughClearScrollback);
TEST_METHOD(TestWrappingALongString);
TEST_METHOD(TestAdvancedWrapping);
TEST_METHOD(TestExactWrappingWithoutSpaces);
TEST_METHOD(TestExactWrappingWithSpaces);
TEST_METHOD(MoveCursorAtEOL);
private:
@@ -348,6 +354,262 @@ void ConptyRoundtripTests::WriteAFewSimpleLines()
verifyData(termTb);
}
void ConptyRoundtripTests::TestWrappingALongString()
{
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
_flushFirstFrame();
_checkConptyOutput = false;
const auto initialTermView = term->GetViewport();
const auto charsToWrite = gsl::narrow_cast<short>(TestUtils::Test100CharsString.size());
VERIFY_ARE_EQUAL(100, charsToWrite);
VERIFY_ARE_EQUAL(0, initialTermView.Top());
VERIFY_ARE_EQUAL(32, initialTermView.BottomExclusive());
hostSm.ProcessString(TestUtils::Test100CharsString);
const auto secondView = term->GetViewport();
VERIFY_ARE_EQUAL(0, secondView.Top());
VERIFY_ARE_EQUAL(32, secondView.BottomExclusive());
auto verifyBuffer = [&](const TextBuffer& tb) {
auto& cursor = tb.GetCursor();
// Verify the cursor wrapped to the second line
VERIFY_ARE_EQUAL(charsToWrite % initialTermView.Width(), cursor.GetPosition().X);
VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y);
// Verify that we marked the 0th row as _wrapped_
const auto& row0 = tb.GetRowByOffset(0);
VERIFY_IS_TRUE(row0.GetCharRow().WasWrapForced());
const auto& row1 = tb.GetRowByOffset(1);
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());
TestUtils::VerifyExpectedString(tb, TestUtils::Test100CharsString, { 0, 0 });
};
verifyBuffer(hostTb);
VERIFY_SUCCEEDED(renderer.PaintFrame());
verifyBuffer(termTb);
}
void ConptyRoundtripTests::TestAdvancedWrapping()
{
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
const auto initialTermView = term->GetViewport();
_flushFirstFrame();
const auto charsToWrite = gsl::narrow_cast<short>(TestUtils::Test100CharsString.size());
VERIFY_ARE_EQUAL(100, charsToWrite);
hostSm.ProcessString(TestUtils::Test100CharsString);
hostSm.ProcessString(L"\n");
hostSm.ProcessString(L" ");
hostSm.ProcessString(L"1234567890");
auto verifyBuffer = [&](const TextBuffer& tb) {
auto& cursor = tb.GetCursor();
// Verify the cursor wrapped to the second line
VERIFY_ARE_EQUAL(2, cursor.GetPosition().Y);
VERIFY_ARE_EQUAL(20, cursor.GetPosition().X);
// Verify that we marked the 0th row as _wrapped_
const auto& row0 = tb.GetRowByOffset(0);
VERIFY_IS_TRUE(row0.GetCharRow().WasWrapForced());
const auto& row1 = tb.GetRowByOffset(1);
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());
TestUtils::VerifyExpectedString(tb, TestUtils::Test100CharsString, { 0, 0 });
TestUtils::VerifyExpectedString(tb, L" 1234567890", { 0, 2 });
};
verifyBuffer(hostTb);
// First write the first 80 characters from the string
expectedOutput.push_back(R"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)");
// Without line breaking, write the remaining 20 chars
expectedOutput.push_back(R"(qrstuvwxyz{|}~!"#$%&)");
// Clear the rest of row 1
expectedOutput.push_back("\x1b[K");
// This is the hard line break
expectedOutput.push_back("\r\n");
// Now write row 2 of the buffer
expectedOutput.push_back(" 1234567890");
// and clear everything after the text, because the buffer is empty.
expectedOutput.push_back("\x1b[K");
VERIFY_SUCCEEDED(renderer.PaintFrame());
verifyBuffer(termTb);
}
void ConptyRoundtripTests::TestExactWrappingWithoutSpaces()
{
// This test (and TestExactWrappingWitSpaces) reveals a bug in the old
// implementation.
//
// If a line _exactly_ wraps to the next line, we can't tell if the line
// should really wrap, or manually break. The client app is writing a line
// that's exactly the width of the buffer that manually linebreaked at the
// end of the line, followed by another line.
//
// With the old PaintBufferLine interface, there's no way to know if this
// case is because the line wrapped or not. Hence, the addition of the
// `lineWrapped` parameter
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
const auto initialTermView = term->GetViewport();
_flushFirstFrame();
const auto charsToWrite = initialTermView.Width();
VERIFY_ARE_EQUAL(80, charsToWrite);
for (auto i = 0; i < charsToWrite; i++)
{
// This is a handy way of just printing the printable characters that
// _aren't_ the space character.
const wchar_t wch = static_cast<wchar_t>(33 + (i % 94));
hostSm.ProcessCharacter(wch);
}
hostSm.ProcessString(L"\n");
hostSm.ProcessString(L"1234567890");
auto verifyBuffer = [&](const TextBuffer& tb, const bool isTerminal) {
auto& cursor = tb.GetCursor();
// Verify the cursor wrapped to the second line
VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y);
VERIFY_ARE_EQUAL(10, cursor.GetPosition().X);
// TODO: GH#780 - In the Terminal, neither line should be wrapped.
// Unfortunately, until WriteCharsLegacy2ElectricBoogaloo is complete,
// the Terminal will still treat the first line as wrapped. When #780 is
// implemented, these tests will fail, and should again expect the first
// line to not be wrapped.
// Verify that we marked the 0th row as _not wrapped_
const auto& row0 = tb.GetRowByOffset(0);
VERIFY_ARE_EQUAL(isTerminal, row0.GetCharRow().WasWrapForced());
const auto& row1 = tb.GetRowByOffset(1);
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());
TestUtils::VerifyExpectedString(tb, LR"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)", { 0, 0 });
TestUtils::VerifyExpectedString(tb, L"1234567890", { 0, 1 });
};
verifyBuffer(hostTb, false);
// First write the first 80 characters from the string
expectedOutput.push_back(R"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)");
// This is the hard line break
expectedOutput.push_back("\r\n");
// Now write row 2 of the buffer
expectedOutput.push_back("1234567890");
// and clear everything after the text, because the buffer is empty.
expectedOutput.push_back("\x1b[K");
VERIFY_SUCCEEDED(renderer.PaintFrame());
verifyBuffer(termTb, true);
}
void ConptyRoundtripTests::TestExactWrappingWithSpaces()
{
// This test is also explained by the comment at the top of TestExactWrappingWithoutSpaces
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& hostSm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
const auto initialTermView = term->GetViewport();
_flushFirstFrame();
const auto charsToWrite = initialTermView.Width();
VERIFY_ARE_EQUAL(80, charsToWrite);
for (auto i = 0; i < charsToWrite; i++)
{
// This is a handy way of just printing the printable characters that
// _aren't_ the space character.
const wchar_t wch = static_cast<wchar_t>(33 + (i % 94));
hostSm.ProcessCharacter(wch);
}
hostSm.ProcessString(L"\n");
hostSm.ProcessString(L" ");
hostSm.ProcessString(L"1234567890");
auto verifyBuffer = [&](const TextBuffer& tb, const bool isTerminal) {
auto& cursor = tb.GetCursor();
// Verify the cursor wrapped to the second line
VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y);
VERIFY_ARE_EQUAL(20, cursor.GetPosition().X);
// TODO: GH#780 - In the Terminal, neither line should be wrapped.
// Unfortunately, until WriteCharsLegacy2ElectricBoogaloo is complete,
// the Terminal will still treat the first line as wrapped. When #780 is
// implemented, these tests will fail, and should again expect the first
// line to not be wrapped.
// Verify that we marked the 0th row as _not wrapped_
const auto& row0 = tb.GetRowByOffset(0);
VERIFY_ARE_EQUAL(isTerminal, row0.GetCharRow().WasWrapForced());
const auto& row1 = tb.GetRowByOffset(1);
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());
TestUtils::VerifyExpectedString(tb, LR"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)", { 0, 0 });
TestUtils::VerifyExpectedString(tb, L" 1234567890", { 0, 1 });
};
verifyBuffer(hostTb, false);
// First write the first 80 characters from the string
expectedOutput.push_back(R"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)");
// This is the hard line break
expectedOutput.push_back("\r\n");
// Now write row 2 of the buffer
expectedOutput.push_back(" 1234567890");
// and clear everything after the text, because the buffer is empty.
expectedOutput.push_back("\x1b[K");
VERIFY_SUCCEEDED(renderer.PaintFrame());
verifyBuffer(termTb, true);
}
void ConptyRoundtripTests::MoveCursorAtEOL()
{
// This is a test for GH#1245
@@ -360,7 +622,6 @@ void ConptyRoundtripTests::MoveCursorAtEOL()
auto& hostSm = si.GetStateMachine();
auto& hostTb = si.GetTextBuffer();
auto& termTb = *term->_buffer;
_flushFirstFrame();
Log::Comment(NoThrowString().Format(

View File

@@ -72,7 +72,7 @@ namespace TerminalCoreUnitTests
term.SetSelectionAnchor({ 5, rowValue });
// Simulate move to (x,y) = (15,20)
term.SetEndSelectionPosition({ 15, 20 });
term.SetSelectionEnd({ 15, 20 });
// Simulate renderer calling TriggerSelection and acquiring selection area
auto selectionRects = term.GetSelectionRects();
@@ -110,19 +110,19 @@ namespace TerminalCoreUnitTests
{
const COORD maxCoord = { SHRT_MAX, SHRT_MAX };
// Test SetSelectionAnchor(COORD) and SetEndSelectionPosition(COORD)
// Test SetSelectionAnchor(COORD) and SetSelectionEnd(COORD)
// Behavior: clamp coord to viewport.
auto ValidateSingleClickSelection = [&](SHORT scrollback, SMALL_RECT expected) {
Terminal term;
DummyRenderTarget emptyRT;
term.Create({ 10, 10 }, scrollback, emptyRT);
// NOTE: SetEndSelectionPosition(COORD) is called within SetSelectionAnchor(COORD)
// NOTE: SetSelectionEnd(COORD) is called within SetSelectionAnchor(COORD)
term.SetSelectionAnchor(maxCoord);
ValidateSingleRowSelection(term, expected);
};
// Test DoubleClickSelection(COORD)
// Test a Double Click Selection
// Behavior: clamp coord to viewport.
// Then, do double click selection.
auto ValidateDoubleClickSelection = [&](SHORT scrollback, SMALL_RECT expected) {
@@ -130,11 +130,11 @@ namespace TerminalCoreUnitTests
DummyRenderTarget emptyRT;
term.Create({ 10, 10 }, scrollback, emptyRT);
term.DoubleClickSelection(maxCoord);
term.MultiClickSelection(maxCoord, Terminal::SelectionExpansionMode::Word);
ValidateSingleRowSelection(term, expected);
};
// Test TripleClickSelection(COORD)
// Test a Triple Click Selection
// Behavior: clamp coord to viewport.
// Then, do triple click selection.
auto ValidateTripleClickSelection = [&](SHORT scrollback, SMALL_RECT expected) {
@@ -142,7 +142,7 @@ namespace TerminalCoreUnitTests
DummyRenderTarget emptyRT;
term.Create({ 10, 10 }, scrollback, emptyRT);
term.TripleClickSelection(maxCoord);
term.MultiClickSelection(maxCoord, Terminal::SelectionExpansionMode::Line);
ValidateSingleRowSelection(term, expected);
};
@@ -226,17 +226,17 @@ namespace TerminalCoreUnitTests
// Case 1: Move out of right boundary
Log::Comment(L"Out of bounds: X-value too large");
term.SetEndSelectionPosition({ 20, 5 });
term.SetSelectionEnd({ 20, 5 });
ValidateSingleRowSelection(term, SMALL_RECT({ 5, 5, rightBoundary, 5 }));
// Case 2: Move out of left boundary
Log::Comment(L"Out of bounds: X-value negative");
term.SetEndSelectionPosition({ -20, 5 });
term.SetSelectionEnd({ -20, 5 });
ValidateSingleRowSelection(term, { leftBoundary, 5, 5, 5 });
// Case 3: Move out of top boundary
Log::Comment(L"Out of bounds: Y-value negative");
term.SetEndSelectionPosition({ 5, -20 });
term.SetSelectionEnd({ 5, -20 });
{
auto selectionRects = term.GetSelectionRects();
@@ -267,7 +267,7 @@ namespace TerminalCoreUnitTests
// Case 4: Move out of bottom boundary
Log::Comment(L"Out of bounds: Y-value too large");
term.SetEndSelectionPosition({ 5, 20 });
term.SetSelectionEnd({ 5, 20 });
{
auto selectionRects = term.GetSelectionRects();
@@ -310,10 +310,10 @@ namespace TerminalCoreUnitTests
// Simulate ALT + click at (x,y) = (5,10)
term.SetSelectionAnchor({ 5, rowValue });
term.SetBoxSelection(true);
term.SetBlockSelection(true);
// Simulate move to (x,y) = (15,20)
term.SetEndSelectionPosition({ 15, 20 });
term.SetSelectionEnd({ 15, 20 });
// Simulate renderer calling TriggerSelection and acquiring selection area
auto selectionRects = term.GetSelectionRects();
@@ -349,7 +349,7 @@ namespace TerminalCoreUnitTests
term.SetSelectionAnchor({ 5, rowValue });
// Simulate move to (x,y) = (15,20)
term.SetEndSelectionPosition({ 15, 20 });
term.SetSelectionEnd({ 15, 20 });
// Simulate renderer calling TriggerSelection and acquiring selection area
auto selectionRects = term.GetSelectionRects();
@@ -449,10 +449,10 @@ namespace TerminalCoreUnitTests
// Simulate ALT + click at (x,y) = (5,8)
term.SetSelectionAnchor({ 5, 8 });
term.SetBoxSelection(true);
term.SetBlockSelection(true);
// Simulate move to (x,y) = (7,12)
term.SetEndSelectionPosition({ 7, 12 });
term.SetSelectionEnd({ 7, 12 });
// Simulate renderer calling TriggerSelection and acquiring selection area
auto selectionRects = term.GetSelectionRects();
@@ -501,7 +501,7 @@ namespace TerminalCoreUnitTests
// Simulate double click at (x,y) = (5,10)
auto clickPos = COORD{ 5, 10 };
term.DoubleClickSelection(clickPos);
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Word);
// Validate selection area
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, (4 + gsl::narrow<SHORT>(text.size()) - 1), 10 }));
@@ -519,7 +519,7 @@ namespace TerminalCoreUnitTests
// Simulate click at (x,y) = (5,10)
auto clickPos = COORD{ 5, 10 };
term.DoubleClickSelection(clickPos);
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Word);
// Simulate renderer calling TriggerSelection and acquiring selection area
auto selectionRects = term.GetSelectionRects();
@@ -546,7 +546,7 @@ namespace TerminalCoreUnitTests
// Simulate click at (x,y) = (15,10)
// this is over the '>' char
auto clickPos = COORD{ 15, 10 };
term.DoubleClickSelection(clickPos);
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Word);
// ---Validate selection area---
// "Terminal" is in class 2
@@ -572,14 +572,14 @@ namespace TerminalCoreUnitTests
term.Write(text);
// Simulate double click at (x,y) = (5,10)
term.DoubleClickSelection({ 5, 10 });
term.MultiClickSelection({ 5, 10 }, Terminal::SelectionExpansionMode::Word);
// Simulate move to (x,y) = (21,10)
//
// buffer: doubleClickMe dragThroughHere
// ^ ^
// start finish
term.SetEndSelectionPosition({ 21, 10 });
term.SetSelectionEnd({ 21, 10 });
// Validate selection area
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 32, 10 }));
@@ -601,14 +601,14 @@ namespace TerminalCoreUnitTests
term.Write(text);
// Simulate double click at (x,y) = (21,10)
term.DoubleClickSelection({ 21, 10 });
term.MultiClickSelection({ 21, 10 }, Terminal::SelectionExpansionMode::Word);
// Simulate move to (x,y) = (5,10)
//
// buffer: doubleClickMe dragThroughHere
// ^ ^
// finish start
term.SetEndSelectionPosition({ 5, 10 });
term.SetSelectionEnd({ 5, 10 });
// Validate selection area
ValidateSingleRowSelection(term, SMALL_RECT({ 4, 10, 32, 10 }));
@@ -622,7 +622,7 @@ namespace TerminalCoreUnitTests
// Simulate click at (x,y) = (5,10)
auto clickPos = COORD{ 5, 10 };
term.TripleClickSelection(clickPos);
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Line);
// Validate selection area
ValidateSingleRowSelection(term, SMALL_RECT({ 0, 10, 99, 10 }));
@@ -636,10 +636,10 @@ namespace TerminalCoreUnitTests
// Simulate click at (x,y) = (5,10)
auto clickPos = COORD{ 5, 10 };
term.TripleClickSelection(clickPos);
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Line);
// Simulate move to (x,y) = (7,10)
term.SetEndSelectionPosition({ 7, 10 });
term.SetSelectionEnd({ 7, 10 });
// Validate selection area
ValidateSingleRowSelection(term, SMALL_RECT({ 0, 10, 99, 10 }));
@@ -653,10 +653,10 @@ namespace TerminalCoreUnitTests
// Simulate click at (x,y) = (5,10)
auto clickPos = COORD{ 5, 10 };
term.TripleClickSelection(clickPos);
term.MultiClickSelection(clickPos, Terminal::SelectionExpansionMode::Line);
// Simulate move to (x,y) = (5,11)
term.SetEndSelectionPosition({ 5, 11 });
term.SetSelectionEnd({ 5, 11 });
// Simulate renderer calling TriggerSelection and acquiring selection area
auto selectionRects = term.GetSelectionRects();
@@ -689,7 +689,7 @@ namespace TerminalCoreUnitTests
// Simulate move to (x,y) = (5,10)
// (So, no movement)
term.SetEndSelectionPosition({ 5, 10 });
term.SetSelectionEnd({ 5, 10 });
// Case 1: single cell selection not allowed
{
@@ -705,12 +705,12 @@ namespace TerminalCoreUnitTests
}
// Case 2: move off of single cell
term.SetEndSelectionPosition({ 6, 10 });
term.SetSelectionEnd({ 6, 10 });
ValidateSingleRowSelection(term, { 5, 10, 6, 10 });
VERIFY_IS_TRUE(term.IsSelectionActive());
// Case 3: move back onto single cell (now allowed)
term.SetEndSelectionPosition({ 5, 10 });
term.SetSelectionEnd({ 5, 10 });
ValidateSingleRowSelection(term, { 5, 10, 5, 10 });
// single cell selection should now be allowed

View File

@@ -29,6 +29,9 @@ class TerminalCoreUnitTests::TerminalBufferTests final
TEST_METHOD(TestSimpleBufferWriting);
TEST_METHOD(TestWrappingCharByChar);
TEST_METHOD(TestWrappingALongString);
TEST_METHOD_SETUP(MethodSetup)
{
// STEP 1: Set up the Terminal
@@ -66,3 +69,76 @@ void TerminalBufferTests::TestSimpleBufferWriting()
TestUtils::VerifyExpectedString(termTb, L"Hello World", { 0, 0 });
}
void TerminalBufferTests::TestWrappingCharByChar()
{
auto& termTb = *term->_buffer;
auto& termSm = *term->_stateMachine;
const auto initialView = term->GetViewport();
auto& cursor = termTb.GetCursor();
const auto charsToWrite = gsl::narrow_cast<short>(TestUtils::Test100CharsString.size());
VERIFY_ARE_EQUAL(0, initialView.Top());
VERIFY_ARE_EQUAL(32, initialView.BottomExclusive());
for (auto i = 0; i < charsToWrite; i++)
{
// This is a handy way of just printing the printable characters that
// _aren't_ the space character.
const wchar_t wch = static_cast<wchar_t>(33 + (i % 94));
termSm.ProcessCharacter(wch);
}
const auto secondView = term->GetViewport();
VERIFY_ARE_EQUAL(0, secondView.Top());
VERIFY_ARE_EQUAL(32, secondView.BottomExclusive());
// Verify the cursor wrapped to the second line
VERIFY_ARE_EQUAL(charsToWrite % initialView.Width(), cursor.GetPosition().X);
VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y);
// Verify that we marked the 0th row as _wrapped_
const auto& row0 = termTb.GetRowByOffset(0);
VERIFY_IS_TRUE(row0.GetCharRow().WasWrapForced());
const auto& row1 = termTb.GetRowByOffset(1);
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());
TestUtils::VerifyExpectedString(termTb, TestUtils::Test100CharsString, { 0, 0 });
}
void TerminalBufferTests::TestWrappingALongString()
{
auto& termTb = *term->_buffer;
auto& termSm = *term->_stateMachine;
const auto initialView = term->GetViewport();
auto& cursor = termTb.GetCursor();
const auto charsToWrite = gsl::narrow_cast<short>(TestUtils::Test100CharsString.size());
VERIFY_ARE_EQUAL(100, charsToWrite);
VERIFY_ARE_EQUAL(0, initialView.Top());
VERIFY_ARE_EQUAL(32, initialView.BottomExclusive());
termSm.ProcessString(TestUtils::Test100CharsString);
const auto secondView = term->GetViewport();
VERIFY_ARE_EQUAL(0, secondView.Top());
VERIFY_ARE_EQUAL(32, secondView.BottomExclusive());
// Verify the cursor wrapped to the second line
VERIFY_ARE_EQUAL(charsToWrite % initialView.Width(), cursor.GetPosition().X);
VERIFY_ARE_EQUAL(1, cursor.GetPosition().Y);
// Verify that we marked the 0th row as _wrapped_
const auto& row0 = termTb.GetRowByOffset(0);
VERIFY_IS_TRUE(row0.GetCharRow().WasWrapForced());
const auto& row1 = termTb.GetRowByOffset(1);
VERIFY_IS_FALSE(row1.GetCharRow().WasWrapForced());
TestUtils::VerifyExpectedString(termTb, TestUtils::Test100CharsString, { 0, 0 });
}

View File

@@ -21,6 +21,10 @@ namespace TerminalCoreUnitTests
class TerminalCoreUnitTests::TestUtils
{
public:
static constexpr std::wstring_view Test100CharsString{
LR"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&)"
};
// Function Description:
// - Helper function to validate that a number of characters in a row are all
// the same. Validates that the next end-start characters are all equal to the

View File

@@ -28,10 +28,10 @@ inline winrt::Windows::UI::Color ColorRefToColor(const COLORREF& colorref)
// - Rect scaled by scale
inline winrt::Windows::Foundation::Rect ScaleRect(winrt::Windows::Foundation::Rect rect, double scale)
{
const float scaleLocal = gsl::narrow_cast<float>(scale);
rect.X *= scaleLocal;
rect.Y *= scaleLocal;
rect.Width *= scaleLocal;
rect.Height *= scaleLocal;
const auto scaleLocal = base::ClampedNumeric<float>(scale);
rect.X = base::ClampMul(rect.X, scaleLocal);
rect.Y = base::ClampMul(rect.Y, scaleLocal);
rect.Width = base::ClampMul(rect.Width, scaleLocal);
rect.Height = base::ClampMul(rect.Height, scaleLocal);
return rect;
}

View File

@@ -7,6 +7,7 @@ namespace Microsoft.Terminal.Wpf
{
using System;
using System.Runtime.InteropServices;
using System.Windows.Automation.Provider;
#pragma warning disable SA1600 // Elements should be documented
internal static class NativeMethods
@@ -36,6 +37,8 @@ namespace Microsoft.Terminal.Wpf
/// </summary>
WM_MOUSEACTIVATE = 0x0021,
WM_GETOBJECT = 0x003D,
/// <summary>
/// The WM_WINDOWPOSCHANGED message is sent to a window whose size, position, or place in the Z order has changed as a result of a call to the SetWindowPos function or another window-management function.
/// </summary>

View File

@@ -26,7 +26,7 @@ namespace Microsoft.Terminal.Wpf
private DispatcherTimer blinkTimer;
private NativeMethods.ScrollCallback scrollCallback;
private NativeMethods.WriteCallback writeCallback;
/// <summary>
/// Initializes a new instance of the <see cref="TerminalContainer"/> class.
/// </summary>
@@ -35,7 +35,7 @@ namespace Microsoft.Terminal.Wpf
this.MessageHook += this.TerminalContainer_MessageHook;
this.GotFocus += this.TerminalContainer_GotFocus;
this.Focusable = true;
var blinkTime = NativeMethods.GetCaretBlinkTime();
if (blinkTime != uint.MaxValue)
@@ -72,6 +72,8 @@ namespace Microsoft.Terminal.Wpf
/// </summary>
internal int Columns { get; private set; }
internal IntPtr Hwnd => this.hwnd;
/// <summary>
/// Sets the connection to the terminal backend.
/// </summary>
@@ -172,7 +174,6 @@ namespace Microsoft.Terminal.Wpf
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
{
var dpiScale = VisualTreeHelper.GetDpi(this);
NativeMethods.CreateTerminal(hwndParent.Handle, out this.hwnd, out this.terminal);
this.scrollCallback = this.OnScroll;
@@ -229,23 +230,6 @@ namespace Microsoft.Terminal.Wpf
this.Focus();
NativeMethods.SetFocus(this.hwnd);
break;
case NativeMethods.WindowMessage.WM_LBUTTONDOWN:
this.LeftClickHandler((int)lParam);
break;
case NativeMethods.WindowMessage.WM_RBUTTONDOWN:
if (NativeMethods.TerminalIsSelectionActive(this.terminal))
{
Clipboard.SetText(NativeMethods.TerminalGetSelection(this.terminal));
}
else
{
this.connection.WriteInput(Clipboard.GetText());
}
break;
case NativeMethods.WindowMessage.WM_MOUSEMOVE:
this.MouseMoveHandler((int)wParam, (int)lParam);
break;
case NativeMethods.WindowMessage.WM_KEYDOWN:
NativeMethods.TerminalSetCursorVisible(this.terminal, true);
NativeMethods.TerminalClearSelection(this.terminal);

View File

@@ -9,7 +9,7 @@ namespace Microsoft.Terminal.Wpf
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
/// <summary>
/// A basic terminal control. This control can receive and render standard VT100 sequences.
/// </summary>

View File

@@ -12,7 +12,7 @@
#pragma hdrstop
using namespace Microsoft::Console::Types;
using namespace Microsoft::Console::Interactivity::Win32;
using namespace Microsoft::Console::Interactivity;
using Microsoft::Console::Interactivity::ServiceLocator;
#pragma region IBaseData
@@ -389,11 +389,50 @@ void RenderData::SelectNewRegion(const COORD coordStart, const COORD coordEnd)
// - none
// Return Value:
// - current selection anchor
const COORD RenderData::GetSelectionAnchor() const
const COORD RenderData::GetSelectionAnchor() const noexcept
{
return Selection::Instance().GetSelectionAnchor();
}
// Routine Description:
// - Gets the current end selection anchor position
// Arguments:
// - none
// Return Value:
// - current selection anchor
const COORD RenderData::GetSelectionEnd() const noexcept
{
// The selection area in ConHost is encoded as two things...
// - SelectionAnchor: the initial position where the selection was started
// - SelectionRect: the rectangular region denoting a portion of the buffer that is selected
// The following is an exerpt from Selection::s_GetSelectionRects
// if the anchor (start of select) was in the top right or bottom left of the box,
// we need to remove rectangular overlap in the middle.
// e.g.
// For selections with the anchor in the top left (A) or bottom right (B),
// it is valid to maintain the inner rectangle (+) as part of the selection
// A+++++++================
// ==============++++++++B
// + and = are valid highlights in this scenario.
// For selections with the anchor in in the top right (A) or bottom left (B),
// we must remove a portion of the first/last line that lies within the rectangle (+)
// +++++++A=================
// ==============B+++++++
// Only = is valid for highlight in this scenario.
// This is only needed for line selection. Box selection doesn't need to account for this.
const auto selectionRect = Selection::Instance().GetSelectionRectangle();
// To extract the end anchor from this rect, we need to know which corner of the rect is the SelectionAnchor
// Then choose the opposite corner.
const auto anchor = Selection::Instance().GetSelectionAnchor();
const short x_pos = (selectionRect.Left == anchor.X) ? selectionRect.Right : selectionRect.Left;
const short y_pos = (selectionRect.Top == anchor.Y) ? selectionRect.Bottom : selectionRect.Top;
return { x_pos, y_pos };
}
// Routine Description:
// - Given two points in the buffer space, color the selection between the two with the given attribute.
// - This will create an internal selection rectangle covering the two points, assume a line selection,

View File

@@ -60,7 +60,8 @@ public:
const bool IsSelectionActive() const override;
void ClearSelection() override;
void SelectNewRegion(const COORD coordStart, const COORD coordEnd) override;
const COORD GetSelectionAnchor() const;
const COORD GetSelectionAnchor() const noexcept;
const COORD GetSelectionEnd() const noexcept;
void ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute attr);
#pragma endregion
};

View File

@@ -34,111 +34,6 @@ Selection& Selection::Instance()
return *_instance;
}
// Routine Description:
// - Determines the line-by-line selection rectangles based on global selection state.
// Arguments:
// - selectionRect - The selection rectangle outlining the region to be selected
// - selectionAnchor - The corner of the selection rectangle that selection started from
// - lineSelection - True to process in line mode. False to process in block mode.
// Return Value:
// - Returns a vector where each SMALL_RECT is one Row worth of the area to be selected.
// - Returns empty vector if no rows are selected.
// - Throws exceptions for out of memory issues
std::vector<SMALL_RECT> Selection::s_GetSelectionRects(const SMALL_RECT& selectionRect,
const COORD selectionAnchor,
const bool lineSelection)
{
std::vector<SMALL_RECT> selectionAreas;
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& screenInfo = gci.GetActiveOutputBuffer();
// if the anchor (start of select) was in the top right or bottom left of the box,
// we need to remove rectangular overlap in the middle.
// e.g.
// For selections with the anchor in the top left (A) or bottom right (B),
// it is valid to maintain the inner rectangle (+) as part of the selection
// A+++++++================
// ==============++++++++B
// + and = are valid highlights in this scenario.
// For selections with the anchor in in the top right (A) or bottom left (B),
// we must remove a portion of the first/last line that lies within the rectangle (+)
// +++++++A=================
// ==============B+++++++
// Only = is valid for highlight in this scenario.
// This is only needed for line selection. Box selection doesn't need to account for this.
bool removeRectPortion = false;
if (lineSelection)
{
const auto selectionStart = selectionAnchor;
// only if top and bottom aren't the same line... we need the whole rectangle if we're on the same line.
// e.g. A++++++++++++++B
// All the + are valid select points.
if (selectionRect.Top != selectionRect.Bottom)
{
if ((selectionStart.X == selectionRect.Right && selectionStart.Y == selectionRect.Top) ||
(selectionStart.X == selectionRect.Left && selectionStart.Y == selectionRect.Bottom))
{
removeRectPortion = true;
}
}
}
// for each row within the selection rectangle
for (short i = selectionRect.Top; i <= selectionRect.Bottom; i++)
{
// create a rectangle representing the highlight on one row
SMALL_RECT highlightRow;
highlightRow.Top = i;
highlightRow.Bottom = i;
highlightRow.Left = selectionRect.Left;
highlightRow.Right = selectionRect.Right;
// compensate for line selection by extending one or both ends of the rectangle to the edge
if (lineSelection)
{
// if not the first row, pad the left selection to the buffer edge
if (i != selectionRect.Top)
{
highlightRow.Left = 0;
}
// if not the last row, pad the right selection to the buffer edge
if (i != selectionRect.Bottom)
{
highlightRow.Right = screenInfo.GetBufferSize().RightInclusive();
}
// if we've determined we're in a scenario where we must remove the inner rectangle from the lines...
if (removeRectPortion)
{
if (i == selectionRect.Top)
{
// from the top row, move the left edge of the highlight line to the right edge of the rectangle
highlightRow.Left = selectionRect.Right;
}
else if (i == selectionRect.Bottom)
{
// from the bottom row, move the right edge of the highlight line to the left edge of the rectangle
highlightRow.Right = selectionRect.Left;
}
}
}
// compensate for double width characters by calling double-width measuring/limiting function
const COORD targetPoint{ highlightRow.Left, highlightRow.Top };
const SHORT stringLength = highlightRow.Right - highlightRow.Left + 1;
highlightRow = s_BisectSelection(stringLength, targetPoint, screenInfo, highlightRow);
selectionAreas.emplace_back(highlightRow);
}
return selectionAreas;
}
// Routine Description:
// - Determines the line-by-line selection rectangles based on global selection state.
// Arguments:
@@ -154,65 +49,17 @@ std::vector<SMALL_RECT> Selection::GetSelectionRects() const
return std::vector<SMALL_RECT>();
}
return s_GetSelectionRects(_srSelectionRect, _coordSelectionAnchor, IsLineSelection());
}
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& screenInfo = gci.GetActiveOutputBuffer();
// Routine Description:
// - This routine checks to ensure that clipboard selection isn't trying to cut a double byte character in half.
// It will adjust the SmallRect rectangle size to ensure this.
// Arguments:
// - sStringLength - The length of the string we're attempting to clip.
// - coordTargetPoint - The row/column position within the text buffer that we're about to try to clip.
// - screenInfo - Screen information structure containing relevant text and dimension information.
// - rect - The region of the text that we want to clip, and then adjusted to the region that should be
// clipped without splicing double-width characters.
// Return Value:
// - the clipped region
SMALL_RECT Selection::s_BisectSelection(const short sStringLength,
const COORD coordTargetPoint,
const SCREEN_INFORMATION& screenInfo,
const SMALL_RECT rect)
{
SMALL_RECT outRect = rect;
try
{
auto iter = screenInfo.GetCellDataAt(coordTargetPoint);
if (iter->DbcsAttr().IsTrailing())
{
if (coordTargetPoint.X == 0)
{
outRect.Left++;
}
else
{
outRect.Left--;
}
}
// _coordSelectionAnchor is at one of the corners of _srSelectionRects
// endSelectionAnchor is at the exact opposite corner
COORD endSelectionAnchor;
endSelectionAnchor.X = (_coordSelectionAnchor.X == _srSelectionRect.Left) ? _srSelectionRect.Right : _srSelectionRect.Left;
endSelectionAnchor.Y = (_coordSelectionAnchor.Y == _srSelectionRect.Top) ? _srSelectionRect.Bottom : _srSelectionRect.Top;
// Check end position of strings
if (coordTargetPoint.X + sStringLength < screenInfo.GetBufferSize().Width())
{
iter += sStringLength;
if (iter->DbcsAttr().IsTrailing())
{
outRect.Right++;
}
}
else
{
if (coordTargetPoint.Y + 1 < screenInfo.GetBufferSize().Height())
{
const auto nextLineIter = screenInfo.GetCellDataAt({ 0, coordTargetPoint.Y + 1 });
if (nextLineIter->DbcsAttr().IsTrailing())
{
outRect.Right--;
}
}
}
}
CATCH_LOG();
return outRect;
const auto blockSelection = !IsLineSelection();
return screenInfo.GetTextBuffer().GetTextRects(_coordSelectionAnchor, endSelectionAnchor, blockSelection);
}
// Routine Description:
@@ -564,18 +411,13 @@ void Selection::ColorSelection(const SMALL_RECT& srRect, const TextAttribute att
// - attr - Color to apply to region.
void Selection::ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute attr)
{
// Make a rectangle for the region as if it were selected by a mouse.
// We will use the first one as the "anchor" to represent where the mouse went down.
SMALL_RECT srSelection;
srSelection.Top = std::min(coordSelectionStart.Y, coordSelectionEnd.Y);
srSelection.Bottom = std::max(coordSelectionStart.Y, coordSelectionEnd.Y);
srSelection.Left = std::min(coordSelectionStart.X, coordSelectionEnd.X);
srSelection.Right = std::max(coordSelectionStart.X, coordSelectionEnd.X);
// Extract row-by-row selection rectangles for the selection area.
try
{
const auto rectangles = s_GetSelectionRects(srSelection, coordSelectionStart, true);
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& screenInfo = gci.GetActiveOutputBuffer();
const auto rectangles = screenInfo.GetTextBuffer().GetTextRects(coordSelectionStart, coordSelectionEnd);
for (const auto& rect : rectangles)
{
ColorSelection(rect, attr);

View File

@@ -72,15 +72,6 @@ private:
void _PaintSelection() const;
static SMALL_RECT s_BisectSelection(const short sStringLength,
const COORD coordTargetPoint,
const SCREEN_INFORMATION& screenInfo,
const SMALL_RECT rect);
static std::vector<SMALL_RECT> s_GetSelectionRects(const SMALL_RECT& selectionRect,
const COORD selectionAnchor,
const bool lineSelection);
void _CancelMarkSelection();
void _CancelMouseSelection();

View File

@@ -9,7 +9,6 @@
#include "../types/WindowUiaProviderBase.hpp"
using namespace Microsoft::Console::Types;
using namespace Microsoft::Console::Interactivity::Win32;
enum TraceKeywords
{
@@ -398,718 +397,3 @@ void __stdcall Tracing::TraceFailure(const wil::FailureInfo& failure) noexcept
TraceLoggingString(failure.pszCode, "Code"),
TraceLoggingLevel(WINEVENT_LEVEL_ERROR));
}
// TODO GitHub #1914: Re-attach Tracing to UIA Tree
#if 0
void Tracing::s_TraceUia(const UiaTextRange* const range,
const UiaTextRangeTracing::ApiCall apiCall,
const UiaTextRangeTracing::IApiMsg* const apiMsg)
{
unsigned long long id = 0u;
bool degenerate = true;
Endpoint start = 0u;
Endpoint end = 0u;
if (range)
{
id = range->GetId();
degenerate = range->IsDegenerate();
start = range->GetStart();
end = range->GetEnd();
}
switch (apiCall)
{
case UiaTextRangeTracing::ApiCall::Constructor:
{
id = static_cast<const UiaTextRangeTracing::ApiMsgConstructor* const>(apiMsg)->Id;
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::Constructor",
TraceLoggingValue(id, "_id"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
}
case UiaTextRangeTracing::ApiCall::AddRef:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::AddRef",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case UiaTextRangeTracing::ApiCall::Release:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::Release",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case UiaTextRangeTracing::ApiCall::QueryInterface:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::QueryInterface",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case UiaTextRangeTracing::ApiCall::Clone:
{
if (apiMsg == nullptr)
{
return;
}
auto cloneId = reinterpret_cast<const UiaTextRangeTracing::ApiMsgClone* const>(apiMsg)->CloneId;
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::Clone",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingValue(cloneId, "clone's _id"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
}
case UiaTextRangeTracing::ApiCall::Compare:
{
const UiaTextRangeTracing::ApiMsgCompare* const msg = static_cast<const UiaTextRangeTracing::ApiMsgCompare* const>(apiMsg);
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::Compare",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingValue(msg->OtherId, "Other's Id"),
TraceLoggingValue(msg->Equal, "Equal"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
}
case UiaTextRangeTracing::ApiCall::CompareEndpoints:
{
const UiaTextRangeTracing::ApiMsgCompareEndpoints* const msg = static_cast<const UiaTextRangeTracing::ApiMsgCompareEndpoints* const>(apiMsg);
const wchar_t* const pEndpoint = _textPatternRangeEndpointToString(msg->Endpoint);
const wchar_t* const pTargetEndpoint = _textPatternRangeEndpointToString(msg->TargetEndpoint);
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::CompareEndpoints",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingValue(msg->OtherId, "Other's Id"),
TraceLoggingValue(pEndpoint, "endpoint"),
TraceLoggingValue(pTargetEndpoint, "targetEndpoint"),
TraceLoggingValue(msg->Result, "Result"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
}
case UiaTextRangeTracing::ApiCall::ExpandToEnclosingUnit:
{
const UiaTextRangeTracing::ApiMsgExpandToEnclosingUnit* const msg = static_cast<const UiaTextRangeTracing::ApiMsgExpandToEnclosingUnit* const>(apiMsg);
const wchar_t* const pUnitName = _textUnitToString(msg->Unit);
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::ExpandToEnclosingUnit",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingValue(pUnitName, "Unit"),
TraceLoggingValue(msg->OriginalStart, "Original Start"),
TraceLoggingValue(msg->OriginalEnd, "Original End"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
}
case UiaTextRangeTracing::ApiCall::FindAttribute:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::FindAttribute",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case UiaTextRangeTracing::ApiCall::FindText:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::FindText",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case UiaTextRangeTracing::ApiCall::GetAttributeValue:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::GetAttributeValue",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case UiaTextRangeTracing::ApiCall::GetBoundingRectangles:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::GetBoundingRectangles",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case UiaTextRangeTracing::ApiCall::GetEnclosingElement:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::GetEnclosingElement",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case UiaTextRangeTracing::ApiCall::GetText:
{
const UiaTextRangeTracing::ApiMsgGetText* const msg = static_cast<const UiaTextRangeTracing::ApiMsgGetText* const>(apiMsg);
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::GetText",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingValue(msg->Text, "Text"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
}
case UiaTextRangeTracing::ApiCall::Move:
{
const UiaTextRangeTracing::ApiMsgMove* const msg = static_cast<const UiaTextRangeTracing::ApiMsgMove* const>(apiMsg);
const wchar_t* const unitStr = _textUnitToString(msg->Unit);
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::Move",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingValue(msg->OriginalStart, "Original Start"),
TraceLoggingValue(msg->OriginalEnd, "Original End"),
TraceLoggingValue(unitStr, "unit"),
TraceLoggingValue(msg->RequestedCount, "Requested Count"),
TraceLoggingValue(msg->MovedCount, "Moved Count"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
}
case UiaTextRangeTracing::ApiCall::MoveEndpointByUnit:
{
const UiaTextRangeTracing::ApiMsgMoveEndpointByUnit* const msg = static_cast<const UiaTextRangeTracing::ApiMsgMoveEndpointByUnit* const>(apiMsg);
const wchar_t* const pEndpoint = _textPatternRangeEndpointToString(msg->Endpoint);
const wchar_t* const unitStr = _textUnitToString(msg->Unit);
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::MoveEndpointByUnit",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingValue(msg->OriginalStart, "Original Start"),
TraceLoggingValue(msg->OriginalEnd, "Original End"),
TraceLoggingValue(pEndpoint, "endpoint"),
TraceLoggingValue(unitStr, "unit"),
TraceLoggingValue(msg->RequestedCount, "Requested Count"),
TraceLoggingValue(msg->MovedCount, "Moved Count"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
}
case UiaTextRangeTracing::ApiCall::MoveEndpointByRange:
{
const UiaTextRangeTracing::ApiMsgMoveEndpointByRange* const msg = static_cast<const UiaTextRangeTracing::ApiMsgMoveEndpointByRange* const>(apiMsg);
const wchar_t* const pEndpoint = _textPatternRangeEndpointToString(msg->Endpoint);
const wchar_t* const pTargetEndpoint = _textPatternRangeEndpointToString(msg->TargetEndpoint);
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::MoveEndpointByRange",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingValue(msg->OriginalStart, "Original Start"),
TraceLoggingValue(msg->OriginalEnd, "Original End"),
TraceLoggingValue(pEndpoint, "endpoint"),
TraceLoggingValue(pTargetEndpoint, "targetEndpoint"),
TraceLoggingValue(msg->OtherId, "Other's _id"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
}
case UiaTextRangeTracing::ApiCall::Select:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::Select",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case UiaTextRangeTracing::ApiCall::AddToSelection:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::AddToSelection",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case UiaTextRangeTracing::ApiCall::RemoveFromSelection:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::RemoveFromSelection",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case UiaTextRangeTracing::ApiCall::ScrollIntoView:
{
const UiaTextRangeTracing::ApiMsgScrollIntoView* const msg = static_cast<const UiaTextRangeTracing::ApiMsgScrollIntoView* const>(apiMsg);
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::ScrollIntoView",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingValue(msg->AlignToTop, "alignToTop"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
}
case UiaTextRangeTracing::ApiCall::GetChildren:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"UiaTextRange::GetChildren",
TraceLoggingValue(id, "_id"),
TraceLoggingValue(start, "_start"),
TraceLoggingValue(end, "_end"),
TraceLoggingValue(degenerate, "_degenerate"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
default:
break;
}
}
void Tracing::s_TraceUia(const Microsoft::Console::Interactivity::Win32::ScreenInfoUiaProvider* const /*pProvider*/,
const ScreenInfoUiaProviderTracing::ApiCall apiCall,
const ScreenInfoUiaProviderTracing::IApiMsg* const apiMsg)
{
switch (apiCall)
{
case ScreenInfoUiaProviderTracing::ApiCall::Constructor:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::Constructor",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case ScreenInfoUiaProviderTracing::ApiCall::Signal:
{
const ScreenInfoUiaProviderTracing::ApiMsgSignal* const msg = static_cast<const ScreenInfoUiaProviderTracing::ApiMsgSignal* const>(apiMsg);
const wchar_t* const signalName = _eventIdToString(msg->Signal);
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::Signal",
TraceLoggingValue(msg->Signal),
TraceLoggingValue(signalName, "Event Name"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
}
case ScreenInfoUiaProviderTracing::ApiCall::AddRef:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::AddRef",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case ScreenInfoUiaProviderTracing::ApiCall::Release:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::Release",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case ScreenInfoUiaProviderTracing::ApiCall::QueryInterface:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::QueryInterface",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case ScreenInfoUiaProviderTracing::ApiCall::GetProviderOptions:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::GetProviderOptions",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case ScreenInfoUiaProviderTracing::ApiCall::GetPatternProvider:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::GetPatternProvider",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case ScreenInfoUiaProviderTracing::ApiCall::GetPropertyValue:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::GetPropertyValue",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case ScreenInfoUiaProviderTracing::ApiCall::GetHostRawElementProvider:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::GetHostRawElementProvider",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case ScreenInfoUiaProviderTracing::ApiCall::Navigate:
{
const ScreenInfoUiaProviderTracing::ApiMsgNavigate* const msg = static_cast<const ScreenInfoUiaProviderTracing::ApiMsgNavigate* const>(apiMsg);
const wchar_t* const direction = _directionToString(msg->Direction);
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::Navigate",
TraceLoggingValue(direction, "direction"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
}
case ScreenInfoUiaProviderTracing::ApiCall::GetRuntimeId:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::GetRuntimeId",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case ScreenInfoUiaProviderTracing::ApiCall::GetBoundingRectangle:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::GetBoundingRectangles",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case ScreenInfoUiaProviderTracing::ApiCall::GetEmbeddedFragmentRoots:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::GetEmbeddedFragmentRoots",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case ScreenInfoUiaProviderTracing::ApiCall::SetFocus:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::SetFocus",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case ScreenInfoUiaProviderTracing::ApiCall::GetFragmentRoot:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::GetFragmentRoot",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case ScreenInfoUiaProviderTracing::ApiCall::GetSelection:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::GetSelection",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case ScreenInfoUiaProviderTracing::ApiCall::GetVisibleRanges:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::GetVisibleRanges",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case ScreenInfoUiaProviderTracing::ApiCall::RangeFromChild:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::RangeFromChild",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case ScreenInfoUiaProviderTracing::ApiCall::RangeFromPoint:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::RangeFromPoint",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case ScreenInfoUiaProviderTracing::ApiCall::GetDocumentRange:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::GetDocumentRange",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case ScreenInfoUiaProviderTracing::ApiCall::GetSupportedTextSelection:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"ScreenInfoUiaProvider::GetSupportedTextSelection",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
default:
break;
}
}
void Tracing::s_TraceUia(const Microsoft::Console::Types::WindowUiaProvider* const /*pProvider*/,
const Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall apiCall,
const Microsoft::Console::Types::WindowUiaProviderTracing::IApiMsg* const apiMsg)
{
switch (apiCall)
{
case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::Create:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"WindowUiaProvider::Create",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::Signal:
{
const Microsoft::Console::Types::WindowUiaProviderTracing::ApiMessageSignal* const msg = static_cast<const Microsoft::Console::Types::WindowUiaProviderTracing::ApiMessageSignal* const>(apiMsg);
const wchar_t* const eventName = _eventIdToString(msg->Signal);
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"WindowUiaProvider::Signal",
TraceLoggingValue(msg->Signal, "Signal"),
TraceLoggingValue(eventName, "Signal Name"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
}
case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::AddRef:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"WindowUiaProvider::AddRef",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::Release:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"WindowUiaProvider::Release",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::QueryInterface:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"WindowUiaProvider::QueryInterface",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::GetProviderOptions:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"WindowUiaProvider::GetProviderOptions",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::GetPatternProvider:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"WindowUiaProvider::GetPatternProvider",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::GetPropertyValue:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"WindowUiaProvider::GetPropertyValue",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::GetHostRawElementProvider:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"WindowUiaProvider::GetHostRawElementProvider",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::Navigate:
{
const Microsoft::Console::Types::WindowUiaProviderTracing::ApiMsgNavigate* const msg = static_cast<const Microsoft::Console::Types::WindowUiaProviderTracing::ApiMsgNavigate* const>(apiMsg);
const wchar_t* const direction = _directionToString(msg->Direction);
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"WindowUiaProvider::Navigate",
TraceLoggingValue(direction, "direction"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
}
case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::GetRuntimeId:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"WindowUiaProvider::GetRuntimeId",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::GetBoundingRectangle:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"WindowUiaProvider::GetBoundingRectangle",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::GetEmbeddedFragmentRoots:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"WindowUiaProvider::GetEmbeddedFragmentRoots",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::SetFocus:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"WindowUiaProvider::SetFocus",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::GetFragmentRoot:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"WindowUiaProvider::GetFragmentRoot",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::ElementProviderFromPoint:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"WindowUiaProvider::ElementProviderFromPoint",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
case Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall::GetFocus:
TraceLoggingWrite(
g_hConhostV2EventTraceProvider,
"WindowUiaProvider::GetFocus",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TraceKeywords::UIA));
break;
default:
break;
}
}
#endif
const wchar_t* const Tracing::_textPatternRangeEndpointToString(int endpoint)
{
switch (endpoint)
{
case TextPatternRangeEndpoint::TextPatternRangeEndpoint_Start:
return L"Start";
case TextPatternRangeEndpoint::TextPatternRangeEndpoint_End:
return L"End";
default:
return L"Unknown";
}
}
const wchar_t* const Tracing::_textUnitToString(int unit)
{
switch (unit)
{
case TextUnit::TextUnit_Character:
return L"TextUnit_Character";
case TextUnit::TextUnit_Format:
return L"TextUnit_Format";
case TextUnit::TextUnit_Word:
return L"TextUnit_Word";
case TextUnit::TextUnit_Line:
return L"TextUnit_Line";
case TextUnit::TextUnit_Paragraph:
return L"TextUnit_Paragraph";
case TextUnit::TextUnit_Page:
return L"TextUnit_Page";
case TextUnit::TextUnit_Document:
return L"TextUnit_Document";
default:
return L"Unknown";
}
}
const wchar_t* const Tracing::_eventIdToString(long eventId)
{
switch (eventId)
{
case UIA_AutomationFocusChangedEventId:
return L"UIA_AutomationFocusChangedEventId";
case UIA_Text_TextChangedEventId:
return L"UIA_Text_TextChangedEventId";
case UIA_Text_TextSelectionChangedEventId:
return L"UIA_Text_TextSelectionChangedEventId";
default:
return L"Unknown";
}
}
const wchar_t* const Tracing::_directionToString(int direction)
{
switch (direction)
{
case NavigateDirection::NavigateDirection_FirstChild:
return L"NavigateDirection_FirstChild";
case NavigateDirection::NavigateDirection_LastChild:
return L"NavigateDirection_LastChild";
case NavigateDirection::NavigateDirection_NextSibling:
return L"NavigateDirection_NextSibling";
case NavigateDirection::NavigateDirection_Parent:
return L"NavigateDirection_Parent";
case NavigateDirection::NavigateDirection_PreviousSibling:
return L"NavigateDirection_PreviousSibling";
default:
return L"Unknown";
}
}

View File

@@ -21,25 +21,6 @@ Author(s):
#include "../types/inc/Viewport.hpp"
namespace Microsoft::Console::Interactivity::Win32
{
class UiaTextRange;
namespace UiaTextRangeTracing
{
enum class ApiCall;
struct IApiMsg;
}
class ScreenInfoUiaProvider;
namespace ScreenInfoUiaProviderTracing
{
enum class ApiCall;
struct IApiMsg;
}
}
#if DBG
#define DBGCHARS(_params_) \
{ \
@@ -83,30 +64,10 @@ public:
static void __stdcall TraceFailure(const wil::FailureInfo& failure) noexcept;
// TODO GitHub #1914: Re-attach Tracing to UIA Tree
#if 0
static void s_TraceUia(const Microsoft::Console::Interactivity::Win32::UiaTextRange* const range,
const Microsoft::Console::Interactivity::Win32::UiaTextRangeTracing::ApiCall apiCall,
const Microsoft::Console::Interactivity::Win32::UiaTextRangeTracing::IApiMsg* const apiMsg);
static void s_TraceUia(const Microsoft::Console::Interactivity::Win32::ScreenInfoUiaProvider* const pProvider,
const Microsoft::Console::Interactivity::Win32::ScreenInfoUiaProviderTracing::ApiCall apiCall,
const Microsoft::Console::Interactivity::Win32::ScreenInfoUiaProviderTracing::IApiMsg* const apiMsg);
static void s_TraceUia(const Microsoft::Console::Types::WindowUiaProvider* const pProvider,
const Microsoft::Console::Types::WindowUiaProviderTracing::ApiCall apiCall,
const Microsoft::Console::Types::WindowUiaProviderTracing::IApiMsg* const apiMsg);
#endif
private:
static ULONG s_ulDebugFlag;
Tracing(std::function<void()> onExit);
std::function<void()> _onExit;
static const wchar_t* const _textPatternRangeEndpointToString(int endpoint);
static const wchar_t* const _textUnitToString(int unit);
static const wchar_t* const _eventIdToString(long eventId);
static const wchar_t* const _directionToString(int direction);
};

View File

@@ -323,7 +323,7 @@ class SelectionTests
// selection rectangle starts from the target and goes for the length requested
srSelection.Left = coordTargetPoint.X;
srSelection.Right = coordTargetPoint.X + sStringLength - 1;
srSelection.Right = coordTargetPoint.X + sStringLength;
// save original for comparison
srOriginal.Top = srSelection.Top;
@@ -331,7 +331,12 @@ class SelectionTests
srOriginal.Left = srSelection.Left;
srOriginal.Right = srSelection.Right;
srSelection = Selection::s_BisectSelection(sStringLength, coordTargetPoint, screenInfo, srSelection);
COORD startPos{ sTargetX, sTargetY };
COORD endPos{ base::ClampAdd(sTargetX, sLength), sTargetY };
const auto selectionRects = screenInfo.GetTextBuffer().GetTextRects(startPos, endPos);
VERIFY_ARE_EQUAL(static_cast<size_t>(1), selectionRects.size());
srSelection = selectionRects.at(0);
VERIFY_ARE_EQUAL(srOriginal.Top, srSelection.Top);
VERIFY_ARE_EQUAL(srOriginal.Bottom, srSelection.Bottom);
@@ -378,10 +383,10 @@ class SelectionTests
// start from position 10 before end of row (80 length row)
// row is 2
// selection is 10 characters long
// selection is 9 characters long
// the left edge shouldn't move
// the right edge should move one to the left (-1) to not select the leading byte
TestBisectSelectionDelta(70, 2, 10, 0, -1);
TestBisectSelectionDelta(70, 2, 9, 0, -1);
// 2b. End position is leading half and is elsewhere in the row
@@ -389,16 +394,16 @@ class SelectionTests
// row is 2
// selection is 10 characters long
// the left edge shouldn't move
// the right edge should move one to the right (+1) to add the trailing byte to the selection
TestBisectSelectionDelta(58, 2, 10, 0, 1);
// the right edge should not move, because it is already on the trailing byte
TestBisectSelectionDelta(58, 2, 10, 0, 0);
// 2c. End position is leading half and is at end of buffer
// start from position 10 before end of row (80 length row)
// row is 300 (or 299 for the index)
// selection is 10 characters long
// selection is 9 characters long
// the left edge shouldn't move
// the right edge shouldn't move
TestBisectSelectionDelta(70, 299, 10, 0, 0);
// the right edge should move one to the left (-1) to not select the leading byte
TestBisectSelectionDelta(70, 299, 9, 0, -1);
}
};

View File

@@ -148,6 +148,8 @@ class TextBufferTests
void WriteLinesToBuffer(const std::vector<std::wstring>& text, TextBuffer& buffer);
TEST_METHOD(GetWordBoundaries);
TEST_METHOD(GetTextRects);
};
void TextBufferTests::TestBufferCreate()
@@ -2136,3 +2138,68 @@ void TextBufferTests::GetWordBoundaries()
VERIFY_ARE_EQUAL(expected, result);
}
}
void TextBufferTests::GetTextRects()
{
// GetTextRects() is used to...
// - Represent selection rects
// - Represent UiaTextRanges for accessibility
// This is the burrito emoji: 🌯
// It's encoded in UTF-16, as needed by the buffer.
const auto burrito = std::wstring(L"\xD83C\xDF2F");
COORD bufferSize{ 20, 50 };
UINT cursorSize = 12;
TextAttribute attr{ 0x7f };
auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
// Setup: Write lines of text to the buffer
const std::vector<std::wstring> text = { L"0123456789",
L" " + burrito + L"3456" + burrito,
L" " + burrito + L"45" + burrito,
burrito + L"234567" + burrito,
L"0123456789" };
WriteLinesToBuffer(text, *_buffer);
// - - - Text Buffer Contents - - -
// |0123456789
// | 🌯3456🌯
// | 🌯45🌯
// |🌯234567🌯
// |0123456789
// - - - - - - - - - - - - - - - -
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:blockSelection", L"{false, true}")
END_TEST_METHOD_PROPERTIES();
bool blockSelection;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"blockSelection", blockSelection), L"Get 'blockSelection' variant");
std::vector<SMALL_RECT> expected{};
if (blockSelection)
{
expected.push_back({ 1, 0, 7, 0 });
expected.push_back({ 1, 1, 8, 1 }); // expand right
expected.push_back({ 1, 2, 7, 2 });
expected.push_back({ 0, 3, 7, 3 }); // expand left
expected.push_back({ 1, 4, 7, 4 });
}
else
{
expected.push_back({ 1, 0, 19, 0 });
expected.push_back({ 0, 1, 19, 1 });
expected.push_back({ 0, 2, 19, 2 });
expected.push_back({ 0, 3, 19, 3 });
expected.push_back({ 0, 4, 7, 4 });
}
COORD start{ 1, 0 };
COORD end{ 7, 4 };
const auto result = _buffer->GetTextRects(start, end, blockSelection);
VERIFY_ARE_EQUAL(expected.size(), result.size());
for (size_t i = 0; i < expected.size(); ++i)
{
VERIFY_ARE_EQUAL(expected.at(i), result.at(i));
}
}

View File

@@ -604,7 +604,7 @@ void VtRendererTest::Xterm256TestCursor()
clusters.emplace_back(std::wstring_view{ &line[i], 1 }, static_cast<size_t>(rgWidths[i]));
}
VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters.data(), clusters.size() }, { 1, 1 }, false));
VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters.data(), clusters.size() }, { 1, 1 }, false, false));
qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL);
VERIFY_SUCCEEDED(engine->_MoveCursor({ 10, 1 }));
@@ -1020,7 +1020,7 @@ void VtRendererTest::XtermTestCursor()
clusters.emplace_back(std::wstring_view{ &line[i], 1 }, static_cast<size_t>(rgWidths[i]));
}
VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters.data(), clusters.size() }, { 1, 1 }, false));
VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters.data(), clusters.size() }, { 1, 1 }, false, false));
qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL);
VERIFY_SUCCEEDED(engine->_MoveCursor({ 10, 1 }));
@@ -1250,7 +1250,7 @@ void VtRendererTest::WinTelnetTestCursor()
clusters.emplace_back(std::wstring_view{ &line[i], 1 }, static_cast<size_t>(rgWidths[i]));
}
VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters.data(), clusters.size() }, { 1, 1 }, false));
VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters.data(), clusters.size() }, { 1, 1 }, false, false));
qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL);
VERIFY_SUCCEEDED(engine->_MoveCursor({ 10, 1 }));
@@ -1315,8 +1315,8 @@ void VtRendererTest::TestWrapping()
clusters2.emplace_back(std::wstring_view{ &line2[i], 1 }, static_cast<size_t>(rgWidths[i]));
}
VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters1.data(), clusters1.size() }, { 0, 0 }, false));
VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters2.data(), clusters2.size() }, { 0, 1 }, false));
VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters1.data(), clusters1.size() }, { 0, 0 }, false, false));
VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters2.data(), clusters2.size() }, { 0, 1 }, false, false));
});
}

View File

@@ -84,8 +84,8 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
if ((*backIter & _Utf8BitMasks::MaskAsciiByte) > _Utf8BitMasks::IsAsciiByte)
{
// Check only up to 3 last bytes, if no Lead Byte was found then the byte before must be the Lead Byte and no partials are in the string
const size_t stopLen{ std::min(in.length(), gsl::narrow_cast<size_t>(4u)) };
for (size_t sequenceLen{ 1u }; sequenceLen < stopLen; ++sequenceLen, --backIter)
const size_t stopLen{ std::min(in.length(), gsl::narrow_cast<size_t>(3u)) };
for (size_t sequenceLen{ 1u }; sequenceLen <= stopLen; ++sequenceLen, --backIter)
{
// If Lead Byte found
if ((*backIter & _Utf8BitMasks::MaskContinuationByte) > _Utf8BitMasks::IsContinuationByte)

View File

@@ -147,7 +147,8 @@ BgfxEngine::BgfxEngine(PVOID SharedViewBase, LONG DisplayHeight, LONG DisplayWid
[[nodiscard]] HRESULT BgfxEngine::PaintBufferLine(const std::basic_string_view<Cluster> clusters,
const COORD coord,
const bool /*trimLeft*/) noexcept
const bool /*trimLeft*/,
const bool /*lineWrapped*/) noexcept
{
try
{

View File

@@ -51,7 +51,8 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT PaintBackground() noexcept override;
[[nodiscard]] HRESULT PaintBufferLine(const std::basic_string_view<Cluster> clusters,
const COORD coord,
const bool trimLeft) noexcept override;
const bool trimLeft,
const bool lineWrapped) noexcept override;
[[nodiscard]] HRESULT PaintBufferGridLines(GridLines const lines, COLORREF const color, size_t const cchLine, COORD const coordTarget) noexcept override;
[[nodiscard]] HRESULT PaintSelection(const SMALL_RECT rect) noexcept override;

View File

@@ -97,26 +97,22 @@ void ScreenInfoUiaProvider::ChangeViewport(const SMALL_RECT NewWindow)
_pUiaParent->ChangeViewport(NewWindow);
}
HRESULT ScreenInfoUiaProvider::GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _Out_ std::deque<ComPtr<UiaTextRangeBase>>& result)
HRESULT ScreenInfoUiaProvider::GetSelectionRange(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** ppUtr)
{
try
{
result.clear();
typename std::remove_reference<decltype(result)>::type temporaryResult;
RETURN_HR_IF_NULL(E_INVALIDARG, ppUtr);
*ppUtr = nullptr;
std::deque<ComPtr<UiaTextRange>> ranges;
RETURN_IF_FAILED(UiaTextRange::GetSelectionRanges(_pData, pProvider, wordDelimiters, ranges));
const auto start = _pData->GetSelectionAnchor();
while (!ranges.empty())
{
temporaryResult.emplace_back(std::move(ranges.back()));
ranges.pop_back();
}
// we need to make end exclusive
auto end = _pData->GetSelectionEnd();
_pData->GetTextBuffer().GetSize().IncrementInBounds(end, true);
std::swap(result, temporaryResult);
return S_OK;
}
CATCH_RETURN();
// TODO GH #4509: Box Selection is misrepresented here as a line selection.
UiaTextRange* result;
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&result, _pData, pProvider, start, end, wordDelimiters));
*ppUtr = result;
return S_OK;
}
HRESULT ScreenInfoUiaProvider::CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ UiaTextRangeBase** ppUtr)

View File

@@ -44,7 +44,7 @@ namespace Microsoft::Console::Interactivity::Win32
void ChangeViewport(const SMALL_RECT NewWindow) override;
protected:
HRESULT GetSelectionRanges(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _Out_ std::deque<WRL::ComPtr<Microsoft::Console::Types::UiaTextRangeBase>>& selectionRanges) override;
HRESULT GetSelectionRange(_In_ IRawElementProviderSimple* pProvider, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** ppUtr) override;
// degenerate range
HRESULT CreateTextRange(_In_ IRawElementProviderSimple* const pProvider, const std::wstring_view wordDelimiters, _COM_Outptr_result_maybenull_ Microsoft::Console::Types::UiaTextRangeBase** ppUtr) override;

View File

@@ -12,36 +12,8 @@ using namespace Microsoft::Console::Interactivity::Win32;
using namespace Microsoft::WRL;
using Microsoft::Console::Interactivity::ServiceLocator;
HRESULT UiaTextRange::GetSelectionRanges(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* pProvider,
const std::wstring_view wordDelimiters,
_Out_ std::deque<ComPtr<UiaTextRange>>& ranges)
{
try
{
typename std::remove_reference<decltype(ranges)>::type temporaryResult;
// get the selection rects
const auto rectangles = pData->GetSelectionRects();
// create a range for each row
for (const auto& rect : rectangles)
{
const auto start = rect.Origin();
const auto end = rect.EndExclusive();
ComPtr<UiaTextRange> range;
RETURN_IF_FAILED(MakeAndInitialize<UiaTextRange>(&range, pData, pProvider, start, end, wordDelimiters));
temporaryResult.emplace_back(std::move(range));
}
std::swap(temporaryResult, ranges);
return S_OK;
}
CATCH_RETURN();
}
// degenerate range constructor.
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, _In_ const std::wstring_view wordDelimiters)
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElementProviderSimple* const pProvider, _In_ const std::wstring_view wordDelimiters) noexcept
{
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, wordDelimiters);
}
@@ -50,7 +22,7 @@ HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData, _In_ IRawElem
HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const Cursor& cursor,
const std::wstring_view wordDelimiters)
const std::wstring_view wordDelimiters) noexcept
{
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, cursor, wordDelimiters);
}
@@ -60,7 +32,7 @@ HRESULT UiaTextRange::RuntimeClassInitialize(_In_ IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const COORD start,
const COORD end,
const std::wstring_view wordDelimiters)
const std::wstring_view wordDelimiters) noexcept
{
return UiaTextRangeBase::RuntimeClassInitialize(pData, pProvider, start, end, wordDelimiters);
}

View File

@@ -24,30 +24,25 @@ namespace Microsoft::Console::Interactivity::Win32
class UiaTextRange final : public Microsoft::Console::Types::UiaTextRangeBase
{
public:
static HRESULT GetSelectionRanges(_In_ Microsoft::Console::Types::IUiaData* pData,
_In_ IRawElementProviderSimple* pProvider,
_In_ const std::wstring_view wordDelimiters,
_Out_ std::deque<WRL::ComPtr<UiaTextRange>>& ranges);
UiaTextRange() = default;
// degenerate range
HRESULT RuntimeClassInitialize(_In_ Microsoft::Console::Types::IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
_In_ const std::wstring_view wordDelimiters = DefaultWordDelimiter);
_In_ const std::wstring_view wordDelimiters = DefaultWordDelimiter) noexcept override;
// degenerate range at cursor position
HRESULT RuntimeClassInitialize(_In_ Microsoft::Console::Types::IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
const Cursor& cursor,
_In_ const std::wstring_view wordDelimiters = DefaultWordDelimiter);
_In_ const std::wstring_view wordDelimiters = DefaultWordDelimiter) noexcept override;
// specific endpoint range
HRESULT RuntimeClassInitialize(_In_ Microsoft::Console::Types::IUiaData* pData,
_In_ IRawElementProviderSimple* const pProvider,
_In_ const COORD start,
_In_ const COORD end,
_In_ const std::wstring_view wordDelimiters = DefaultWordDelimiter);
_In_ const std::wstring_view wordDelimiters = DefaultWordDelimiter) noexcept override;
// range from a UiaPoint
HRESULT RuntimeClassInitialize(_In_ Microsoft::Console::Types::IUiaData* pData,

View File

@@ -597,15 +597,23 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
// Retrieve the cell information iterator limited to just this line we want to redraw.
auto it = buffer.GetCellDataAt(bufferLine.Origin(), bufferLine);
// Calculate if two things are true:
// 1. this row wrapped
// 2. We're painting the last col of the row.
// In that case, set lineWrapped=true for the _PaintBufferOutputHelper call.
const auto lineWrapped = (buffer.GetRowByOffset(bufferLine.Origin().Y).GetCharRow().WasWrapForced()) &&
(bufferLine.RightExclusive() == buffer.GetSize().Width());
// Ask the helper to paint through this specific line.
_PaintBufferOutputHelper(pEngine, it, screenLine.Origin());
_PaintBufferOutputHelper(pEngine, it, screenLine.Origin(), lineWrapped);
}
}
}
void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
TextBufferCellIterator it,
const COORD target)
const COORD target,
const bool lineWrapped)
{
// If we have valid data, let's figure out how to draw it.
if (it)
@@ -664,7 +672,7 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
// Do the painting.
// TODO: Calculate when trim left should be TRUE
THROW_IF_FAILED(pEngine->PaintBufferLine({ clusters.data(), clusters.size() }, screenPoint, false));
THROW_IF_FAILED(pEngine->PaintBufferLine({ clusters.data(), clusters.size() }, screenPoint, false, lineWrapped));
// If we're allowed to do grid drawing, draw that now too (since it will be coupled with the color data)
if (_pData->IsGridLineDrawingAllowed())
@@ -813,7 +821,7 @@ void Renderer::_PaintOverlay(IRenderEngine& engine,
auto it = overlay.buffer.GetCellLineDataAt(source);
_PaintBufferOutputHelper(&engine, it, target);
_PaintBufferOutputHelper(&engine, it, target, false);
}
}
}

View File

@@ -96,7 +96,8 @@ namespace Microsoft::Console::Render
void _PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
TextBufferCellIterator it,
const COORD target);
const COORD target,
const bool lineWrapped);
static IRenderEngine::GridLines s_GetGridlines(const TextAttribute& textAttribute) noexcept;

View File

@@ -15,10 +15,10 @@ RenderThread::RenderThread() :
_hEvent(nullptr),
_hPaintCompletedEvent(nullptr),
_fKeepRunning(true),
_hPaintEnabledEvent(nullptr)
_hPaintEnabledEvent(nullptr),
_fNextFrameRequested(false),
_fWaiting(false)
{
_fNextFrameRequested.clear();
_fPainting.clear();
}
RenderThread::~RenderThread()
@@ -161,20 +161,45 @@ DWORD WINAPI RenderThread::_ThreadProc()
{
WaitForSingleObject(_hPaintEnabledEvent, INFINITE);
// Skip waiting if next frame is requested.
if (_fNextFrameRequested.test_and_set(std::memory_order_relaxed))
if (!_fNextFrameRequested.exchange(false))
{
_fNextFrameRequested.clear(std::memory_order_relaxed);
}
else
{
WaitForSingleObject(_hEvent, INFINITE);
// <--
// If `NotifyPaint` is called at this point, then it will not
// set the event because `_fWaiting` is not `true` yet so we have
// to check again below.
_fWaiting.store(true);
// check again now (see comment above)
if (!_fNextFrameRequested.exchange(false))
{
// Wait until a next frame is requested.
WaitForSingleObject(_hEvent, INFINITE);
}
// <--
// If `NotifyPaint` is called at this point, then it _will_ set
// the event because `_fWaiting` is `true`, but we're not waiting
// anymore!
// This can probably happen quite often: imagine a scenario where
// we are waiting, and the terminal calls `NotifyPaint` twice
// very quickly.
// In that case, both calls might end up calling `SetEvent`. The
// first one will resume this thread and the second one will
// `SetEvent` the event. So the next time we wait, the event will
// already be set and we won't actually wait.
// Because it can happen often, and because rendering is an
// expensive operation, we should reset the event to not render
// again if nothing changed.
_fWaiting.store(false);
// see comment above
ResetEvent(_hEvent);
}
ResetEvent(_hPaintCompletedEvent);
_fPainting.test_and_set(std::memory_order_acquire);
LOG_IF_FAILED(_pRenderer->PaintFrame());
SetEvent(_hPaintCompletedEvent);
@@ -184,8 +209,6 @@ DWORD WINAPI RenderThread::_ThreadProc()
{
Sleep(s_FrameLimitMilliseconds);
}
_fPainting.clear(std::memory_order_release);
}
return S_OK;
@@ -193,15 +216,14 @@ DWORD WINAPI RenderThread::_ThreadProc()
void RenderThread::NotifyPaint()
{
// If we are currently painting a frame, set _fNextFrameRequested flag
// to indicate we want to paint next frame immediately.
if (_fPainting.test_and_set(std::memory_order_acquire))
if (_fWaiting.load())
{
_fNextFrameRequested.test_and_set(std::memory_order_relaxed);
return;
SetEvent(_hEvent);
}
else
{
_fNextFrameRequested.store(true);
}
SetEvent(_hEvent);
}
void RenderThread::EnablePainting()

View File

@@ -47,7 +47,7 @@ namespace Microsoft::Console::Render
IRenderer* _pRenderer; // Non-ownership pointer
bool _fKeepRunning;
std::atomic_flag _fNextFrameRequested;
std::atomic_flag _fPainting;
std::atomic<bool> _fNextFrameRequested;
std::atomic<bool> _fWaiting;
};
}

View File

@@ -20,6 +20,8 @@
using namespace DirectX;
std::atomic<size_t> Microsoft::Console::Render::DxEngine::_tracelogCount{ 0 };
#pragma warning(suppress : 26477)
TRACELOGGING_DEFINE_PROVIDER(g_hDxRenderProvider,
"Microsoft.Windows.Terminal.Renderer.DirectX",
// {c93e739e-ae50-5a14-78e7-f171e947535d}
@@ -80,15 +82,19 @@ DxEngine::DxEngine() :
_glyphCell{ 0 },
_haveDeviceResources{ false },
_retroTerminalEffects{ false },
_antialiasingMode{ D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE },
_hwndTarget{ static_cast<HWND>(INVALID_HANDLE_VALUE) },
_sizeTarget{ 0 },
_dpi{ USER_DEFAULT_SCREEN_DPI },
_scale{ 1.0f },
_chainMode{ SwapChainMode::ForComposition },
_customRenderer{ ::Microsoft::WRL::Make<CustomTextRenderer>() },
_hasEverPresented{ false }
_customRenderer{ ::Microsoft::WRL::Make<CustomTextRenderer>() }
{
TraceLoggingRegister(g_hDxRenderProvider);
const auto was = _tracelogCount.fetch_add(1);
if (0 == was)
{
TraceLoggingRegister(g_hDxRenderProvider);
}
THROW_IF_FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, IID_PPV_ARGS(&_d2dFactory)));
@@ -108,7 +114,11 @@ DxEngine::~DxEngine()
{
_ReleaseDeviceResources();
TraceLoggingUnregister(g_hDxRenderProvider);
const auto was = _tracelogCount.fetch_sub(1);
if (1 == was)
{
TraceLoggingUnregister(g_hDxRenderProvider);
}
}
// Routine Description:
@@ -154,10 +164,6 @@ DxEngine::~DxEngine()
{
_ReleaseDeviceResources();
}
else
{
RETURN_IF_FAILED(_CreateDeviceResources(true));
}
return S_OK;
}
@@ -280,12 +286,24 @@ HRESULT DxEngine::_SetupTerminalEffects()
RETURN_IF_FAILED(_d3dDevice->CreateBuffer(&bd, &InitData, &_screenQuadVertexBuffer));
D3D11_BUFFER_DESC pixelShaderSettingsBufferDesc{};
pixelShaderSettingsBufferDesc.Usage = D3D11_USAGE_DEFAULT;
pixelShaderSettingsBufferDesc.ByteWidth = sizeof(_pixelShaderSettings);
pixelShaderSettingsBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
_ComputePixelShaderSettings();
D3D11_SUBRESOURCE_DATA pixelShaderSettingsInitData{};
pixelShaderSettingsInitData.pSysMem = &_pixelShaderSettings;
RETURN_IF_FAILED(_d3dDevice->CreateBuffer(&pixelShaderSettingsBufferDesc, &pixelShaderSettingsInitData, &_pixelShaderSettingsBuffer));
// Sampler state is needed to use texture as input to shader.
D3D11_SAMPLER_DESC samplerDesc{};
samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
samplerDesc.MipLODBias = 0.0f;
samplerDesc.MaxAnisotropy = 1;
samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
@@ -302,6 +320,22 @@ HRESULT DxEngine::_SetupTerminalEffects()
return S_OK;
}
// Routine Description:
// - Puts the correct values in _pixelShaderSettings, so the struct can be
// passed the GPU.
// Arguments:
// - <none>
// Return Value:
// - <none>
void DxEngine::_ComputePixelShaderSettings() noexcept
{
// Retro scan lines alternate every pixel row at 100% scaling.
_pixelShaderSettings.ScaledScanLinePeriod = _scale * 1.0f;
// Gaussian distribution sigma used for blurring.
_pixelShaderSettings.ScaledGaussianSigma = _scale * 2.0f;
}
// Routine Description;
// - Creates device-specific resources required for drawing
// which generally means those that are represented on the GPU and can
@@ -336,7 +370,7 @@ HRESULT DxEngine::_SetupTerminalEffects()
// You can find out how to install it here:
// https://docs.microsoft.com/en-us/windows/uwp/gaming/use-the-directx-runtime-and-visual-studio-graphics-diagnostic-features
// clang-format on
D3D11_CREATE_DEVICE_DEBUG |
// D3D11_CREATE_DEVICE_DEBUG |
D3D11_CREATE_DEVICE_SINGLETHREADED;
const std::array<D3D_FEATURE_LEVEL, 5> FeatureLevels{ D3D_FEATURE_LEVEL_11_1,
@@ -390,7 +424,8 @@ HRESULT DxEngine::_SetupTerminalEffects()
{
switch (_chainMode)
{
case SwapChainMode::ForHwnd: {
case SwapChainMode::ForHwnd:
{
// use the HWND's dimensions for the swap chain dimensions.
RECT rect = { 0 };
RETURN_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &rect));
@@ -419,7 +454,8 @@ HRESULT DxEngine::_SetupTerminalEffects()
break;
}
case SwapChainMode::ForComposition: {
case SwapChainMode::ForComposition:
{
// Use the given target size for compositions.
SwapChainDesc.Width = _displaySizePixels.cx;
SwapChainDesc.Height = _displaySizePixels.cy;
@@ -439,8 +475,6 @@ HRESULT DxEngine::_SetupTerminalEffects()
THROW_HR(E_NOTIMPL);
}
_hasEverPresented = false;
if (_retroTerminalEffects)
{
const HRESULT hr = _SetupTerminalEffects();
@@ -499,7 +533,8 @@ HRESULT DxEngine::_SetupTerminalEffects()
&props,
&_d2dRenderTarget));
_d2dRenderTarget->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
_d2dRenderTarget->SetTextAntialiasMode(_antialiasingMode);
RETURN_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::DarkRed),
&_d2dBrushBackground));
@@ -532,9 +567,6 @@ HRESULT DxEngine::_SetupTerminalEffects()
RETURN_IF_FAILED(sc2->SetMatrixTransform(&inverseScale));
}
_hasEverPresented = false;
return S_OK;
}
CATCH_RETURN();
@@ -563,7 +595,6 @@ void DxEngine::_ReleaseDeviceResources() noexcept
_dxgiSurface.Reset();
_dxgiSwapChain.Reset();
_hasEverPresented = false;
if (nullptr != _d3dDeviceContext.Get())
{
@@ -795,7 +826,8 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
{
switch (_chainMode)
{
case SwapChainMode::ForHwnd: {
case SwapChainMode::ForHwnd:
{
RECT clientRect = { 0 };
LOG_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &clientRect));
@@ -805,7 +837,8 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
return clientSize;
}
case SwapChainMode::ForComposition: {
case SwapChainMode::ForComposition:
{
SIZE size = _sizeTarget;
size.cx = static_cast<LONG>(size.cx * _scale);
size.cy = static_cast<LONG>(size.cy * _scale);
@@ -938,19 +971,24 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
// - Any DirectX error, a memory error, etc.
[[nodiscard]] HRESULT DxEngine::StartPaint() noexcept
{
FAIL_FAST_IF_FAILED(InvalidateAll());
RETURN_HR_IF(E_NOT_VALID_STATE, _isPainting); // invalid to start a paint while painting.
// TODO: not sure why this is happening at all, but it is
RETURN_HR_IF(S_FALSE, IsRectEmpty(&_invalidRect) && _invalidScroll.cx == 0 && _invalidScroll.cy == 0);
#pragma warning(suppress : 26477 26485 26494 26482 26446 26447) // We don't control TraceLoggingWrite
TraceLoggingWrite(g_hDxRenderProvider,
"Invalid",
TraceLoggingInt32(_invalidRect.bottom - _invalidRect.top, "InvalidHeight"),
TraceLoggingInt32((_invalidRect.bottom - _invalidRect.top) / _glyphCell.cy, "InvalidHeightChars"),
TraceLoggingInt32(_invalidRect.right - _invalidRect.left, "InvalidWidth"),
TraceLoggingInt32((_invalidRect.right - _invalidRect.left) / _glyphCell.cx, "InvalidWidthChars"),
TraceLoggingInt32(_invalidRect.left, "InvalidX"),
TraceLoggingInt32(_invalidRect.left / _glyphCell.cx, "InvalidXChars"),
TraceLoggingInt32(_invalidRect.top, "InvalidY"),
TraceLoggingInt32(_invalidRect.top / _glyphCell.cy, "InvalidYChars"),
TraceLoggingInt32(_invalidScroll.cx, "ScrollWidth"),
TraceLoggingInt32(_invalidScroll.cy, "ScrollHeight"));
TraceLoggingInt32(_invalidScroll.cx / _glyphCell.cx, "ScrollWidthChars"),
TraceLoggingInt32(_invalidScroll.cy, "ScrollHeight"),
TraceLoggingInt32(_invalidScroll.cy / _glyphCell.cy, "ScrollHeightChars"));
if (_isEnabled)
{
@@ -1014,18 +1052,18 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
if (SUCCEEDED(hr))
{
_presentDirty = _invalidRect;
_presentParams.DirtyRectsCount = 1;
_presentParams.pDirtyRects = &_presentDirty;
if (_invalidScroll.cy != 0 || _invalidScroll.cx != 0)
{
_presentDirty = _invalidRect;
const RECT display = _GetDisplayRect();
SubtractRect(&_presentScroll, &display, &_presentDirty);
_presentOffset.x = _invalidScroll.cx;
_presentOffset.y = _invalidScroll.cy;
_presentParams.DirtyRectsCount = 1;
_presentParams.pDirtyRects = &_presentDirty;
_presentParams.pScrollOffset = &_presentOffset;
_presentParams.pScrollRect = &_presentScroll;
@@ -1087,31 +1125,24 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
// - S_OK on success, E_PENDING to indicate a retry or a relevant DirectX error
[[nodiscard]] HRESULT DxEngine::Present() noexcept
{
if (_retroTerminalEffects)
{
const HRESULT hr2 = _PaintTerminalEffects();
if (FAILED(hr2))
{
_retroTerminalEffects = false;
LOG_HR_MSG(hr2, "Failed to paint terminal effects. Disabling.");
}
}
if (_presentReady)
{
if (_retroTerminalEffects)
{
const HRESULT hr2 = _PaintTerminalEffects();
if (FAILED(hr2))
{
_retroTerminalEffects = false;
LOG_HR_MSG(hr2, "Failed to paint terminal effects. Disabling.");
}
}
try
{
HRESULT hr = S_OK;
if (_hasEverPresented)
{
hr = _dxgiSwapChain->Present1(1, 0, &_presentParams);
}
else
{
hr = _dxgiSwapChain->Present(1, 0);
_hasEverPresented = true;
}
hr = _dxgiSwapChain->Present(1, 0);
/*hr = _dxgiSwapChain->Present1(1, 0, &_presentParams);*/
if (FAILED(hr))
{
@@ -1160,23 +1191,19 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
// - S_OK
[[nodiscard]] HRESULT DxEngine::PaintBackground() noexcept
{
const auto rect = D2D1::RectF(static_cast<float>(_invalidRect.left),
static_cast<float>(_invalidRect.top),
static_cast<float>(_invalidRect.right),
static_cast<float>(_invalidRect.bottom));
switch (_chainMode)
{
case SwapChainMode::ForHwnd:
_d2dRenderTarget->FillRectangle(D2D1::RectF(static_cast<float>(_invalidRect.left),
static_cast<float>(_invalidRect.top),
static_cast<float>(_invalidRect.right),
static_cast<float>(_invalidRect.bottom)),
_d2dBrushBackground.Get());
break;
case SwapChainMode::ForComposition:
_d2dRenderTarget->PushAxisAlignedClip(rect,
D2D1_ANTIALIAS_MODE::D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
D2D1_COLOR_F nothing = { 0 };
_d2dRenderTarget->Clear(nothing);
_d2dRenderTarget->PopAxisAlignedClip();
break;
}
@@ -1193,7 +1220,8 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
// - S_OK or relevant DirectX error
[[nodiscard]] HRESULT DxEngine::PaintBufferLine(std::basic_string_view<Cluster> const clusters,
COORD const coord,
const bool /*trimLeft*/) noexcept
const bool /*trimLeft*/,
const bool /*lineWrapped*/) noexcept
{
try
{
@@ -1383,7 +1411,8 @@ enum class CursorPaintType
switch (options.cursorType)
{
case CursorType::Legacy: {
case CursorType::Legacy:
{
// Enforce min/max cursor height
ULONG ulHeight = std::clamp(options.ulCursorHeightPercent, s_ulMinCursorHeightPercent, s_ulMaxCursorHeightPercent);
@@ -1391,22 +1420,26 @@ enum class CursorPaintType
rect.top = rect.bottom - ulHeight;
break;
}
case CursorType::VerticalBar: {
case CursorType::VerticalBar:
{
// It can't be wider than one cell or we'll have problems in invalidation, so restrict here.
// It's either the left + the proposed width from the ease of access setting, or
// it's the right edge of the block cursor as a maximum.
rect.right = std::min(rect.right, rect.left + options.cursorPixelWidth);
break;
}
case CursorType::Underscore: {
case CursorType::Underscore:
{
rect.top = rect.bottom - 1;
break;
}
case CursorType::EmptyBox: {
case CursorType::EmptyBox:
{
paintType = CursorPaintType::Outline;
break;
}
case CursorType::FullBox: {
case CursorType::FullBox:
{
break;
}
default:
@@ -1423,11 +1456,13 @@ enum class CursorPaintType
switch (paintType)
{
case CursorPaintType::Fill: {
case CursorPaintType::Fill:
{
_d2dRenderTarget->FillRectangle(rect, brush.Get());
break;
}
case CursorPaintType::Outline: {
case CursorPaintType::Outline:
{
// DrawRectangle in straddles physical pixels in an attempt to draw a line
// between them. To avoid this, bump the rectangle around by half the stroke width.
rect.top += 0.5f;
@@ -1486,6 +1521,7 @@ try
_d3dDeviceContext->PSSetShader(_pixelShader.Get(), nullptr, 0);
_d3dDeviceContext->PSSetShaderResources(0, 1, shaderResource.GetAddressOf());
_d3dDeviceContext->PSSetSamplers(0, 1, _samplerState.GetAddressOf());
_d3dDeviceContext->PSSetConstantBuffers(0, 1, _pixelShaderSettingsBuffer.GetAddressOf());
_d3dDeviceContext->Draw(ARRAYSIZE(_screenQuadVertices), 0);
return S_OK;
@@ -1583,6 +1619,16 @@ CATCH_RETURN()
RETURN_IF_FAILED(InvalidateAll());
if (_retroTerminalEffects && _d3dDeviceContext && _pixelShaderSettingsBuffer)
{
_ComputePixelShaderSettings();
try
{
_d3dDeviceContext->UpdateSubresource(_pixelShaderSettingsBuffer.Get(), 0, NULL, &_pixelShaderSettings, 0, 0);
}
CATCH_RETURN();
}
return S_OK;
}
@@ -2081,10 +2127,12 @@ float DxEngine::GetScaling() const noexcept
switch (_chainMode)
{
case SwapChainMode::ForHwnd: {
case SwapChainMode::ForHwnd:
{
return D2D1::ColorF(rgb);
}
case SwapChainMode::ForComposition: {
case SwapChainMode::ForComposition:
{
// Get the A value we've snuck into the highest byte
const BYTE a = ((color >> 24) & 0xFF);
const float aFloat = a / 255.0f;
@@ -2109,3 +2157,17 @@ void DxEngine::SetSelectionBackground(const COLORREF color) noexcept
GetBValue(color) / 255.0f,
0.5f);
}
// Routine Description:
// - Changes the antialiasing mode of the renderer. This must be called before
// _PrepareRenderTarget, otherwise the renderer will default to
// D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE.
// Arguments:
// - antialiasingMode: a value from the D2D1_TEXT_ANTIALIAS_MODE enum. See:
// https://docs.microsoft.com/en-us/windows/win32/api/d2d1/ne-d2d1-d2d1_text_antialias_mode
// Return Value:
// - N/A
void DxEngine::SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept
{
_antialiasingMode = antialiasingMode;
}

View File

@@ -76,7 +76,8 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT PaintBackground() noexcept override;
[[nodiscard]] HRESULT PaintBufferLine(std::basic_string_view<Cluster> const clusters,
COORD const coord,
bool const fTrimLeft) noexcept override;
bool const fTrimLeft,
const bool lineWrapped) noexcept override;
[[nodiscard]] HRESULT PaintBufferGridLines(GridLines const lines, COLORREF const color, size_t const cchLine, COORD const coordTarget) noexcept override;
[[nodiscard]] HRESULT PaintSelection(const SMALL_RECT rect) noexcept override;
@@ -104,6 +105,7 @@ namespace Microsoft::Console::Render
float GetScaling() const noexcept;
void SetSelectionBackground(const COLORREF color) noexcept;
void SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept;
protected:
[[nodiscard]] HRESULT _DoUpdateTitle(_In_ const std::wstring& newTitle) noexcept override;
@@ -149,13 +151,14 @@ namespace Microsoft::Console::Render
void _InvalidOffset(POINT pt);
bool _hasEverPresented;
bool _presentReady;
RECT _presentDirty;
RECT _presentScroll;
POINT _presentOffset;
DXGI_PRESENT_PARAMETERS _presentParams;
static std::atomic<size_t> _tracelogCount;
static const ULONG s_ulMinCursorHeightPercent = 25;
static const ULONG s_ulMaxCursorHeightPercent = 100;
@@ -186,11 +189,23 @@ namespace Microsoft::Console::Render
::Microsoft::WRL::ComPtr<ID3D11PixelShader> _pixelShader;
::Microsoft::WRL::ComPtr<ID3D11InputLayout> _vertexLayout;
::Microsoft::WRL::ComPtr<ID3D11Buffer> _screenQuadVertexBuffer;
::Microsoft::WRL::ComPtr<ID3D11Buffer> _pixelShaderSettingsBuffer;
::Microsoft::WRL::ComPtr<ID3D11SamplerState> _samplerState;
::Microsoft::WRL::ComPtr<ID3D11Texture2D> _framebufferCapture;
D2D1_TEXT_ANTIALIAS_MODE _antialiasingMode;
// DirectX constant buffers need to be a multiple of 16; align to pad the size.
__declspec(align(16)) struct
{
float ScaledScanLinePeriod;
float ScaledGaussianSigma;
#pragma warning(suppress : 4324) // structure was padded due to __declspec(align())
} _pixelShaderSettings;
[[nodiscard]] HRESULT _CreateDeviceResources(const bool createSwapChain) noexcept;
HRESULT _SetupTerminalEffects();
void _ComputePixelShaderSettings() noexcept;
[[nodiscard]] HRESULT _PrepareRenderTarget() noexcept;

View File

@@ -7,8 +7,13 @@ const char screenPixelShaderString[] = R"(
Texture2D shaderTexture;
SamplerState samplerState;
cbuffer PixelShaderSettings
{
float ScaledScanLinePeriod;
float ScaledGaussianSigma;
};
#define SCANLINE_FACTOR 0.5
#define SCANLINE_PERIOD 1
static const float M_PI = 3.14159265f;
@@ -26,7 +31,6 @@ float4 Blur(Texture2D input, float2 tex_coord, float sigma)
float texelHeight = 1.0f/height;
float4 color = { 0, 0, 0, 0 };
float factor = 1;
int sampleCount = 13;
@@ -49,7 +53,7 @@ float4 Blur(Texture2D input, float2 tex_coord, float sigma)
float SquareWave(float y)
{
return 1 - (floor(y / SCANLINE_PERIOD) % 2) * SCANLINE_FACTOR;
return 1 - (floor(y / ScaledScanLinePeriod) % 2) * SCANLINE_FACTOR;
}
float4 Scanline(float4 color, float4 pos)
@@ -74,7 +78,7 @@ float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
// TODO:GH#3930 Make these configurable in some way.
float4 color = input.Sample(samplerState, tex);
color += Blur(input, tex, 2)*0.3;
color += Blur(input, tex, ScaledGaussianSigma)*0.3;
color = Scanline(color, pos);
return color;

View File

@@ -44,7 +44,8 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT PaintBackground() noexcept override;
[[nodiscard]] HRESULT PaintBufferLine(std::basic_string_view<Cluster> const clusters,
const COORD coord,
const bool trimLeft) noexcept override;
const bool trimLeft,
const bool lineWrapped) noexcept override;
[[nodiscard]] HRESULT PaintBufferGridLines(const GridLines lines,
const COLORREF color,
const size_t cchLine,

View File

@@ -286,7 +286,8 @@ using namespace Microsoft::Console::Render;
//#define MAX_POLY_LINES 80
[[nodiscard]] HRESULT GdiEngine::PaintBufferLine(std::basic_string_view<Cluster> const clusters,
const COORD coord,
const bool trimLeft) noexcept
const bool trimLeft,
const bool /*lineWrapped*/) noexcept
{
try
{

View File

@@ -93,7 +93,8 @@ namespace Microsoft::Console::Render
[[nodiscard]] virtual HRESULT PaintBackground() noexcept = 0;
[[nodiscard]] virtual HRESULT PaintBufferLine(std::basic_string_view<Cluster> const clusters,
const COORD coord,
const bool fTrimLeft) noexcept = 0;
const bool fTrimLeft,
const bool lineWrapped) noexcept = 0;
[[nodiscard]] virtual HRESULT PaintBufferGridLines(const GridLines lines,
const COLORREF color,
const size_t cchLine,

View File

@@ -276,7 +276,8 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) :
// - S_FALSE
[[nodiscard]] HRESULT UiaEngine::PaintBufferLine(std::basic_string_view<Cluster> const /*clusters*/,
COORD const /*coord*/,
const bool /*trimLeft*/) noexcept
const bool /*trimLeft*/,
const bool /*lineWrapped*/) noexcept
{
return S_FALSE;
}

View File

@@ -53,7 +53,8 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT PaintBackground() noexcept override;
[[nodiscard]] HRESULT PaintBufferLine(std::basic_string_view<Cluster> const clusters,
COORD const coord,
bool const fTrimLeft) noexcept override;
bool const fTrimLeft,
const bool lineWrapped) noexcept override;
[[nodiscard]] HRESULT PaintBufferGridLines(GridLines const lines, COLORREF const color, size_t const cchLine, COORD const coordTarget) noexcept override;
[[nodiscard]] HRESULT PaintSelection(const SMALL_RECT rect) noexcept override;

View File

@@ -19,7 +19,6 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
_ColorTable(ColorTable),
_cColorTable(cColorTable),
_fUseAsciiOnly(fUseAsciiOnly),
_previousLineWrapped(false),
_usingUnderLine(false),
_needToDisableCursor(false),
_lastCursorIsVisible(false),
@@ -235,6 +234,8 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
{
HRESULT hr = S_OK;
_trace.TraceMoveCursor(_lastText, coord);
if (coord.X != _lastText.X || coord.Y != _lastText.Y)
{
if (coord.X == 0 && coord.Y == 0)
@@ -248,8 +249,15 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
// If the previous line wrapped, then the cursor is already at this
// position, we just don't know it yet. Don't emit anything.
if (_previousLineWrapped)
bool previousLineWrapped = false;
if (_wrappedRow.has_value())
{
previousLineWrapped = coord.Y == _wrappedRow.value() + 1;
}
if (previousLineWrapped)
{
_trace.TraceWrapped();
hr = S_OK;
}
else
@@ -312,7 +320,11 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
_newBottomLine = false;
}
_deferredCursorPos = INVALID_COORDS;
_wrappedRow = std::nullopt;
_delayedEolWrap = false;
return hr;
}
@@ -430,15 +442,19 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
// - trimLeft - This specifies whether to trim one character width off the left
// side of the output. Used for drawing the right-half only of a
// double-wide character.
// - lineWrapped: true if this run we're painting is the end of a line that
// wrapped. If we're not painting the last column of a wrapped line, then this
// will be false.
// Return Value:
// - S_OK or suitable HRESULT error from writing pipe.
[[nodiscard]] HRESULT XtermEngine::PaintBufferLine(std::basic_string_view<Cluster> const clusters,
const COORD coord,
const bool /*trimLeft*/) noexcept
const bool /*trimLeft*/,
const bool lineWrapped) noexcept
{
return _fUseAsciiOnly ?
VtEngine::_PaintAsciiBufferLine(clusters, coord) :
VtEngine::_PaintUtf8BufferLine(clusters, coord);
VtEngine::_PaintUtf8BufferLine(clusters, coord, lineWrapped);
}
// Method Description:

View File

@@ -48,7 +48,8 @@ namespace Microsoft::Console::Render
const bool isSettingDefaultBrushes) noexcept override;
[[nodiscard]] HRESULT PaintBufferLine(std::basic_string_view<Cluster> const clusters,
const COORD coord,
const bool trimLeft) noexcept override;
const bool trimLeft,
const bool lineWrapped) noexcept override;
[[nodiscard]] HRESULT ScrollFrame() noexcept override;
[[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override;
@@ -59,7 +60,6 @@ namespace Microsoft::Console::Render
const COLORREF* const _ColorTable;
const WORD _cColorTable;
const bool _fUseAsciiOnly;
bool _previousLineWrapped;
bool _usingUnderLine;
bool _needToDisableCursor;
bool _lastCursorIsVisible;

View File

@@ -115,11 +115,15 @@ using namespace Microsoft::Console::Types;
// - trimLeft - This specifies whether to trim one character width off the left
// side of the output. Used for drawing the right-half only of a
// double-wide character.
// - lineWrapped: true if this run we're painting is the end of a line that
// wrapped. If we're not painting the last column of a wrapped line, then this
// will be false.
// Return Value:
// - S_OK or suitable HRESULT error from writing pipe.
[[nodiscard]] HRESULT VtEngine::PaintBufferLine(std::basic_string_view<Cluster> const clusters,
const COORD coord,
const bool /*trimLeft*/) noexcept
const bool /*trimLeft*/,
const bool /*lineWrapped*/) noexcept
{
return VtEngine::_PaintAsciiBufferLine(clusters, coord);
}
@@ -149,6 +153,8 @@ using namespace Microsoft::Console::Types;
// - S_OK or suitable HRESULT error from writing pipe.
[[nodiscard]] HRESULT VtEngine::PaintCursor(const IRenderEngine::CursorOptions& options) noexcept
{
_trace.TracePaintCursor(options.coordCursor);
// MSFT:15933349 - Send the terminal the updated cursor information, if it's changed.
LOG_IF_FAILED(_MoveCursor(options.coordCursor));
@@ -369,15 +375,14 @@ using namespace Microsoft::Console::Types;
// Return Value:
// - S_OK or suitable HRESULT error from writing pipe.
[[nodiscard]] HRESULT VtEngine::_PaintUtf8BufferLine(std::basic_string_view<Cluster> const clusters,
const COORD coord) noexcept
const COORD coord,
const bool lineWrapped) noexcept
{
if (coord.Y < _virtualTop)
{
return S_OK;
}
RETURN_IF_FAILED(_MoveCursor(coord));
std::wstring unclusteredString;
unclusteredString.reserve(clusters.size());
short totalWidth = 0;
@@ -445,10 +450,37 @@ using namespace Microsoft::Console::Types;
(totalWidth - numSpaces) :
totalWidth;
if (cchActual == 0)
{
// If the previous row wrapped, but this line is empty, then we actually
// do want to move the cursor down. Otherwise, we'll possibly end up
// accidentally erasing the last character from the previous line, as
// the cursor is still waiting on that character for the next character
// to follow it.
_wrappedRow = std::nullopt;
}
// Move the cursor to the start of this run.
RETURN_IF_FAILED(_MoveCursor(coord));
// Write the actual text string
std::wstring wstr = std::wstring(unclusteredString.data(), cchActual);
RETURN_IF_FAILED(VtEngine::_WriteTerminalUtf8(wstr));
// If we've written text to the last column of the viewport, then mark
// that we've wrapped this line. The next time we attempt to move the
// cursor, if we're trying to move it to the start of the next line,
// we'll remember that this line was wrapped, and not manually break the
// line.
// Don't do this if the last character we're writing is a space - The last
// char will always be a space, but if we see that, we shouldn't wrap.
const short lastWrittenChar = base::ClampAdd(_lastText.X, base::ClampSub(totalWidth, numSpaces));
if (lineWrapped &&
lastWrittenChar > _lastViewport.RightInclusive())
{
_wrappedRow = coord.Y;
}
// Update our internal tracker of the cursor's position.
// See MSFT:20266233 (which is also GH#357)
// If the cursor is at the rightmost column of the terminal, and we write a

View File

@@ -225,3 +225,48 @@ void RenderTracing::TraceLastText(const COORD lastTextPos) const
UNREFERENCED_PARAMETER(lastTextPos);
#endif UNIT_TESTING
}
void RenderTracing::TraceMoveCursor(const COORD lastTextPos, const COORD cursor) const
{
#ifndef UNIT_TESTING
const auto lastTextStr = _CoordToString(lastTextPos);
const auto lastText = lastTextStr.c_str();
const auto cursorStr = _CoordToString(cursor);
const auto cursorPos = cursorStr.c_str();
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TraceMoveCursor",
TraceLoggingString(lastText),
TraceLoggingString(cursorPos),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
#else
UNREFERENCED_PARAMETER(lastTextPos);
UNREFERENCED_PARAMETER(cursor);
#endif UNIT_TESTING
}
void RenderTracing::TraceWrapped() const
{
#ifndef UNIT_TESTING
const auto* const msg = "Wrapped instead of \\r\\n";
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TraceWrapped",
TraceLoggingString(msg),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
#else
#endif UNIT_TESTING
}
void RenderTracing::TracePaintCursor(const COORD coordCursor) const
{
#ifndef UNIT_TESTING
const auto cursorPosString = _CoordToString(coordCursor);
const auto cursorPos = cursorPosString.c_str();
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TracePaintCursor",
TraceLoggingString(cursorPos),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
#else
UNREFERENCED_PARAMETER(coordCursor);
#endif UNIT_TESTING
}

View File

@@ -29,6 +29,9 @@ namespace Microsoft::Console::VirtualTerminal
void TraceString(const std::string_view& str) const;
void TraceInvalidate(const Microsoft::Console::Types::Viewport view) const;
void TraceLastText(const COORD lastText) const;
void TraceMoveCursor(const COORD lastText, const COORD cursor) const;
void TraceWrapped() const;
void TracePaintCursor(const COORD coordCursor) const;
void TraceInvalidateAll(const Microsoft::Console::Types::Viewport view) const;
void TraceTriggerCircling(const bool newFrame) const;
void TraceStartPaint(const bool quickReturn,

View File

@@ -65,7 +65,8 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT PaintBackground() noexcept override;
[[nodiscard]] virtual HRESULT PaintBufferLine(std::basic_string_view<Cluster> const clusters,
const COORD coord,
const bool trimLeft) noexcept override;
const bool trimLeft,
const bool lineWrapped) noexcept override;
[[nodiscard]] HRESULT PaintBufferGridLines(const GridLines lines,
const COLORREF color,
const size_t cchLine,
@@ -144,6 +145,8 @@ namespace Microsoft::Console::Render
Microsoft::Console::VirtualTerminal::RenderTracing _trace;
bool _inResizeRequest{ false };
std::optional<short> _wrappedRow{ std::nullopt };
bool _delayedEolWrap{ false };
[[nodiscard]] HRESULT _Write(std::string_view const str) noexcept;
@@ -214,7 +217,8 @@ namespace Microsoft::Console::Render
bool _WillWriteSingleChar() const;
[[nodiscard]] HRESULT _PaintUtf8BufferLine(std::basic_string_view<Cluster> const clusters,
const COORD coord) noexcept;
const COORD coord,
const bool lineWrapped) noexcept;
[[nodiscard]] HRESULT _PaintAsciiBufferLine(std::basic_string_view<Cluster> const clusters,
const COORD coord) noexcept;

View File

@@ -260,7 +260,8 @@ bool WddmConEngine::IsInitialized()
[[nodiscard]] HRESULT WddmConEngine::PaintBufferLine(std::basic_string_view<Cluster> const clusters,
const COORD coord,
const bool /*trimLeft*/) noexcept
const bool /*trimLeft*/,
const bool /*lineWrapped*/) noexcept
{
try
{

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